/*-*- c++ -*-********************************************************
 * wxFTCanvas: a canvas for editing formatted text                  *
 *                                                                  *
 * (C) 1998 by Karsten Ballüder (Ballueder@usa.net)                 *
 *                                                                  *
 * $Id$       *
 *******************************************************************/

/*
  - each Object knows its size and how to draw itself
  - the list is responsible for calculating positions
  - the draw coordinates for each object are the top left corner
  - coordinates only get calculated when things get redrawn
  - during redraw each line gets iterated over twice, first just
    calculating baselines and positions, second to actually draw it
  - the cursor position is the position before an object, i.e. if the
    buffer starts with a text-object, cursor 0,0 is just before the
    first character
*/

#ifdef __GNUG__
#pragma implementation "wxllist.h"
#endif

#include   "wxllist.h"
#include   "iostream"

#define   VAR(x)   cerr << #x"=" << x << endl;
#define   DBG_POINT(p)   cerr << #p << ": " << p.x << ',' << p.y << endl
#define   TRACE(f)   cerr << #f":" << endl;

#ifdef DEBUG
static const char *_t[] = { "invalid", "text", "cmd", "icon",
                             "linebreak"};

void
wxLayoutObjectBase::Debug(void)
{
   CoordType bl = 0;
   cerr << _t[GetType()] << ": size=" << GetSize(&bl).x << ","
        << GetSize(&bl).y << " bl=" << bl; 
}
#endif

//-------------------------- wxLayoutObjectText

wxLayoutObjectText::wxLayoutObjectText(const wxString &txt)
{
   m_Text = txt;
   m_Width = 0;
   m_Height = 0;
}


wxPoint
wxLayoutObjectText::GetSize(CoordType *baseLine) const
{
   if(baseLine) *baseLine = m_BaseLine;
   return wxPoint(m_Width, m_Height);
}

void
wxLayoutObjectText::Draw(wxDC &dc, wxPoint position, CoordType baseLine,
                    bool draw)
{
   long descent = 0l;
   dc.GetTextExtent(m_Text,&m_Width, &m_Height, &descent);
   //FIXME: wxGTK does not set descent to a descent value yet.
   if(descent == 0)
      descent = (2*m_Height)/10;  // crude fix
   m_BaseLine = m_Height - descent;
   position.y += baseLine-m_BaseLine;
   if(draw)
      dc.DrawText(m_Text,position.x,position.y);
#   ifdef   DEBUG
//   dc.DrawRectangle(position.x, position.y, m_Width, m_Height);
#   endif
}

#ifdef DEBUG
void
wxLayoutObjectText::Debug(void)
{
   wxLayoutObjectBase::Debug();
   cerr << " `" << m_Text << '\'';
}
#endif

//-------------------------- wxLayoutObjectIcon

wxLayoutObjectIcon::wxLayoutObjectIcon(wxIcon *icon)
{
   m_Icon = icon;
}

void
wxLayoutObjectIcon::Draw(wxDC &dc, wxPoint position, CoordType baseLine,
                    bool draw)
{
   position.y += baseLine - m_Icon->GetHeight();
   if(draw)
      dc.DrawIcon(m_Icon,position.x,position.y);
}

wxPoint
wxLayoutObjectIcon::GetSize(CoordType *baseLine) const
{
   wxASSERT(baseLine);
   *baseLine = m_Icon->GetHeight();
   return wxPoint(m_Icon->GetWidth(), m_Icon->GetHeight());
}

//-------------------------- wxLayoutObjectCmd


wxLayoutObjectCmd::wxLayoutObjectCmd(int size, int family, int style, int
                           weight, bool underline,
                           wxColour const *fg, wxColour const *bg)
   
{
   m_font = new wxFont(size,family,style,weight,underline);
   m_ColourFG = fg;
   m_ColourBG = bg;
}

wxLayoutObjectCmd::~wxLayoutObjectCmd()
{
   delete m_font;
}

wxLayoutStyleInfo *
wxLayoutObjectCmd::GetStyle(void) const
{
   wxLayoutStyleInfo *si = new wxLayoutStyleInfo();


   si->size = m_font->GetPointSize();
   si->family = m_font->GetFamily();
   si->style = m_font->GetStyle();
   si->underline = m_font->GetUnderlined();
   si->weight = m_font->GetWeight();

   si->fg_red = m_ColourFG->Red();
   si->fg_green = m_ColourFG->Green();
   si->fg_blue = m_ColourFG->Blue();
   si->bg_red = m_ColourBG->Red();
   si->bg_green = m_ColourBG->Green();
   si->bg_blue = m_ColourBG->Blue();

   return si;
}

void
wxLayoutObjectCmd::Draw(wxDC &dc, wxPoint position, CoordType lineHeight,
                   bool draw)
{
   wxASSERT(m_font);
   // this get called even when draw==false, so that recalculation
   // uses right font sizes
   dc.SetFont(m_font);
   if(m_ColourFG)
      dc.SetTextForeground(*m_ColourFG);
   if(m_ColourBG)
      dc.SetTextBackground(*m_ColourBG);
}

//-------------------------- wxwxLayoutList

wxLayoutList::wxLayoutList()
{
   Clear();
}

wxLayoutList::~wxLayoutList()
{
}


void
wxLayoutList::LineBreak(void)
{
   Insert(new wxLayoutObjectLineBreak);
   m_CursorPosition.x = 0; m_CursorPosition.y++;
}

void
wxLayoutList::SetFont(int family, int size, int style, int weight,
                      int underline, wxColour const *fg,
                      wxColour const *bg)
{
   if(family != -1)    m_FontFamily = family;
   if(size != -1)      m_FontPtSize = size;
   if(style != -1)     m_FontStyle = style;
   if(weight != -1)    m_FontWeight = weight;
   if(underline != -1) m_FontUnderline = underline;

   if(fg != NULL)     m_ColourFG = fg;
   if(bg != NULL)     m_ColourBG = bg;
   
   Insert(
      new wxLayoutObjectCmd(m_FontPtSize,m_FontFamily,m_FontStyle,m_FontWeight,m_FontUnderline,
                       m_ColourFG, m_ColourBG));
}

void
wxLayoutList::SetFont(int family, int size, int style, int weight,
                      int underline, char const *fg, char const *bg)
{
   wxColour const
      * cfg = NULL,
      * cbg = NULL;

   if( fg )
      cfg = wxTheColourDatabase->FindColour(fg);
   if( bg )
      cbg = wxTheColourDatabase->FindColour(bg);
   
   SetFont(family,size,style,weight,underline,cfg,cbg);
}


/// for access by wxLayoutWindow:
void
wxLayoutList::GetSize(CoordType *max_x, CoordType *max_y,
                      CoordType *lineHeight)
{
   wxASSERT(max_x); wxASSERT(max_y); wxASSERT(lineHeight);
   *max_x = m_MaxX;
   *max_y = m_MaxY;
   *lineHeight = m_LineHeight;
}

wxLayoutObjectBase *
wxLayoutList::Draw(wxDC &dc, bool findObject, wxPoint const &findCoords)
{
   wxLayoutObjectList::iterator i;

   // in case we need to look for an object
   wxLayoutObjectBase *foundObject = NULL;
   
   // first object in current line
   wxLayoutObjectList::iterator headOfLine;

   // do we need to recalculate current line?
   bool recalculate = false;

   // do we calculate or draw? Either true or false.
   bool draw = false;
   // drawing parameters:
   wxPoint position = wxPoint(0,0);
   wxPoint position_HeadOfLine;
   CoordType baseLine = m_FontPtSize;
   CoordType baseLineSkip = (12 * baseLine)/10;

   // we trace the objects' cursor positions so we can draw the cursor
   wxPoint cursor = wxPoint(0,0);
   // the cursor position inside the object
   CoordType cursorOffset = 0;
   // object under cursor
   wxLayoutObjectList::iterator cursorObject = FindCurrentObject(&cursorOffset);
   
   // queried from each object:
   wxPoint       size = wxPoint(0,0);
   CoordType     objBaseLine = baseLine;
   wxLayoutObjectType type;

   VAR(findObject); VAR(findCoords.x); VAR(findCoords.y);
   // if the cursorobject is a cmd, we need to find the first
   // printable object:
   while(cursorObject != end()
         && (*cursorObject)->GetType() == WXLO_TYPE_CMD)
      cursorObject++;

   headOfLine = begin();
   position_HeadOfLine = position;

   // setting up the default:
   dc.SetTextForeground( *wxBLACK );
   dc.SetFont( *wxNORMAL_FONT );

   // we calculate everything for drawing a line, then rewind to the
   // begin of line and actually draw it
   i = begin();
   for(;;)
   {
      recalculate = false;

      if(i == end())
         break;
      type = (*i)->GetType();

      // to initialise sizes of objects, we need to call Draw
      (*i)->Draw(dc, position, baseLine, draw);

      // update coordinates for next object:
      size = (*i)->GetSize(&objBaseLine);
      if(findObject && draw)  // we need to look for an object
      {
         if(findCoords.y >= position.y
            && findCoords.y <= position.y+size.y
            && findCoords.x >= position.x
            && findCoords.x <= position.x+size.x)
         {
            foundObject = *i;
            findObject = false; // speeds things up
         }
      }
      // draw the cursor
      if(m_Editable && draw && i == cursorObject)
      {
         if(type == WXLO_TYPE_TEXT) // special treatment
         {
            long descent = 0l; long width, height;
            wxLayoutObjectText *tobj = (wxLayoutObjectText *)*i;
            wxString  str = tobj->GetText();
            VAR(m_CursorPosition.x); VAR(cursor.x);
            str = str.substr(0, cursorOffset);
            VAR(str);
            dc.GetTextExtent(str, &width,&height, &descent);
            VAR(height);
            VAR(width); VAR(descent);
            dc.DrawLine(position.x+width,
                        position.y+(baseLineSkip-height),
                        position.x+width, position.y+baseLineSkip);
         }
         else
         {
            if(type == WXLO_TYPE_LINEBREAK)
                dc.DrawLine(0, position.y+baseLineSkip, 0, position.y+2*baseLineSkip);
            else
            {
               if(size.x == 0)
               {
                  if(size.y == 0)
                     dc.DrawLine(position.x, position.y, position.x, position.y+baseLineSkip);
                  else
                     dc.DrawLine(position.x, position.y, position.x, position.y+size.y);
               }
               else
                  dc.DrawRectangle(position.x, position.y, size.x, size.y);
            }
         }
      }

      // calculate next object's position:
      position.x += size.x;
      
      // do we need to increase the line's height?
      if(size.y > baseLineSkip)
      {
         baseLineSkip = size.y;
         recalculate = true;
      }
      if(objBaseLine > baseLine)
      {
         baseLine = objBaseLine;
         recalculate = true;
      }

      // now check whether we have finished handling this line:
      if(type == WXLO_TYPE_LINEBREAK || i == tail())
      {
         if(recalculate)  // do this line again
         {
            position.x = position_HeadOfLine.x;
            i = headOfLine;
            continue;
         }

         if(! draw) // finished calculating sizes
         {  // do this line again, this time drawing it
            position = position_HeadOfLine;
            draw = true;
            i = headOfLine;
            continue;
         }
         else // we have drawn a line, so continue calculating next one
            draw = false;
      }

      if(position.x+size.x > m_MaxX)
         m_MaxX = position.x+size.x;
      // is it a linebreak?
      if(type == WXLO_TYPE_LINEBREAK || i == tail())
      {
         position.x = 0;
         position.y += baseLineSkip;
         baseLine = m_FontPtSize;
         baseLineSkip = (12 * baseLine)/10;
         headOfLine = i;
         headOfLine++;
         position_HeadOfLine = position;
      }
      i++;
   }
   m_MaxY = position.y;
   return foundObject;
}

#ifdef DEBUG
void
wxLayoutList::Debug(void)
{
   CoordType               offs;
   wxLayoutObjectList::iterator i;

   cerr <<
      "------------------------debug start-------------------------" << endl;
   for(i = begin(); i != end(); i++)
   {
      (*i)->Debug();
      cerr << endl;
   }
   cerr <<
      "-----------------------debug end----------------------------"
        << endl;
   // show current object:
   cerr << "Cursor: "
        << m_CursorPosition.x << ','
        << m_CursorPosition.y;
   
   i = FindCurrentObject(&offs);
   cerr << " line length: " << GetLineLength(i) << "  ";
   if(i == end())
   {
      cerr << "<<no object found>>" << endl;
      return;  // FIXME we should set cursor position to maximum allowed
      // value then
   }
   if((*i)->GetType() == WXLO_TYPE_TEXT)
   {
      cerr << " \"" << ((wxLayoutObjectText *)(*i))->GetText() << "\", offs: "
           << offs << endl;
   }
   else
      cerr << ' ' << _t[(*i)->GetType()] << endl;

}
#endif

/******************** editing stuff ********************/

wxLayoutObjectList::iterator 
wxLayoutList::FindObjectCursor(wxPoint const &cpos, CoordType *offset)
{
   wxPoint cursor = wxPoint(0,0);  // runs along the objects
   CoordType width;
   wxLayoutObjectList::iterator i;

#ifdef DEBUG
   cerr << "Looking for object at " << cpos.x << ',' << cpos.y <<
      endl;
#endif
   for(i = begin(); i != end() && cursor.y <= cpos.y; i++)
   {
      width = 0;
      if((*i)->GetType() == WXLO_TYPE_LINEBREAK)
      {
         if(cpos.y == cursor.y)
         {
            --i;
            if(offset)
               *offset = (*i)->CountPositions();
            return i;
         }
         cursor.x = 0; cursor.y ++;
      }
      else
         cursor.x += (width = (*i)->CountPositions());
      if(cursor.y == cpos.y && (cursor.x > cpos.x ||
                                ((*i)->GetType() != WXLO_TYPE_CMD && cursor.x == cpos.x))
         ) // found it ?   
      {
         if(offset)
            *offset = cpos.x-(cursor.x-width);  // 0==cursor before
         // the object
#ifdef DEBUG
         cerr << "   found object at " << cursor.x-width << ',' <<
            cursor.y << ", type:" << _t[(*i)->GetType()] <<endl;
#endif   
         return i;
      }
   }
#ifdef DEBUG
   cerr << "   not found" << endl;
#endif
   return end(); // not found
}

wxLayoutObjectList::iterator 
wxLayoutList::FindCurrentObject(CoordType *offset)
{
   wxLayoutObjectList::iterator obj = end();

   obj = FindObjectCursor(m_CursorPosition, offset);
   if(obj == end())  // not ideal yet
   {
      obj = tail();
      if(obj != end()) // tail really exists
         *offset = (*obj)->CountPositions(); // at the end of it
   }
   return obj;
}

void
wxLayoutList::MoveCursor(int dx, int dy)
{
   CoordType offs, lineLength;
   wxLayoutObjectList::iterator i;


   if(dy > 0 && m_CursorPosition.y < m_MaxLine)
      m_CursorPosition.y += dy;
   else if(dy < 0 && m_CursorPosition.y > 0)
      m_CursorPosition.y += dy; // dy is negative
   if(m_CursorPosition.y < 0)
      m_CursorPosition.y = 0;
   else if (m_CursorPosition.y > m_MaxLine)
      m_CursorPosition.y = m_MaxLine;
   
   while(dx > 0)
   {
      i = FindCurrentObject(&offs);
      lineLength = GetLineLength(i);
      if(m_CursorPosition.x < lineLength)
      {
         m_CursorPosition.x ++;
         dx--;
         continue;
      }
      else
      {
         if(m_CursorPosition.y < m_MaxLine)
         {
            m_CursorPosition.y++;
            m_CursorPosition.x = 0;
            dx--;
         }
         else
            break; // cannot move there
      }
   }
   while(dx < 0)
   {
      if(m_CursorPosition.x > 0)
      {
         m_CursorPosition.x --;
         dx++;
      }
      else
      {
         if(m_CursorPosition.y > 0)
         {
            m_CursorPosition.y --;
            m_CursorPosition.x = 0;
            i = FindCurrentObject(&offs);
            lineLength = GetLineLength(i);
            m_CursorPosition.x = lineLength;
            dx++;
            continue;
         }
         else
            break; // cannot move left any more
      }
   }
   // final adjustment:
   i = FindCurrentObject(&offs);
   lineLength = GetLineLength(i);
   if(m_CursorPosition.x > lineLength)
      m_CursorPosition.x = lineLength;
      
#ifdef   DEBUG
   i = FindCurrentObject(&offs);
   cerr << "Cursor: "
        << m_CursorPosition.x << ','
        << m_CursorPosition.y;

   if(i == end())
   {
      cerr << "<<no object found>>" << endl;
      return;  // FIXME we should set cursor position to maximum allowed
      // value then
   }
   if((*i)->GetType() == WXLO_TYPE_TEXT)
   {
      cerr << " \"" << ((wxLayoutObjectText *)(*i))->GetText() << "\", offs: "
           << offs << endl;
   }
   else
      cerr << ' ' << _t[(*i)->GetType()] << endl;
#endif
}

void
wxLayoutList::Delete(CoordType count)
{
   TRACE(Delete);

   if(!m_Editable)
      return;

   VAR(count);

   CoordType offs, len;
   wxLayoutObjectList::iterator i;
      
   do
   {
      i  = FindCurrentObject(&offs);
      if(i == end())
         return;
#ifdef DEBUG
      cerr << "trying to delete: " << _t[(*i)->GetType()] << endl;
#endif
      if((*i)->GetType() == WXLO_TYPE_LINEBREAK)
         m_MaxLine--;
      if((*i)->GetType() == WXLO_TYPE_TEXT)
      {
         wxLayoutObjectText *tobj = (wxLayoutObjectText *)*i;
         len = tobj->CountPositions();
         // If we find the end of a text object, this means that we
         // have to delete from the object following it.
         if(offs == len)
         {
            i++;
            if((*i)->GetType() == WXLO_TYPE_TEXT)
            {
               offs = 0;  // delete from begin of next string
               tobj = (wxLayoutObjectText *)*i;
               len = tobj->CountPositions();
            }
            else
            {
               erase(i);
               return;
            }
         }
         if(len <= count) // delete this object
         {
            count -= len;
            erase(i);
         }
         else
         {
            len = count;
            VAR(offs); VAR(len);
            tobj->GetText().erase(offs,len);
            return; // we are done
         }
      }
      else // delete the object
      {
         len = (*i)->CountPositions();
         erase(i); // after this, i is the iterator for the following object
         if(count > len)
            count -= len;
         else
            count = 0;
      }
   }
   while(count && i != end());      
}

void
wxLayoutList::Insert(wxLayoutObjectBase *obj)
{
   wxASSERT(obj);
   CoordType offs;
   wxLayoutObjectList::iterator i = FindCurrentObject(&offs);

   TRACE(Insert(obj));

   if(i == end())
      push_back(obj);
   else
   {      
      // do we have to split a text object?
      if((*i)->GetType() == WXLO_TYPE_TEXT && offs != 0 && offs != (*i)->CountPositions())
      {
         wxLayoutObjectText *tobj = (wxLayoutObjectText *) *i;
#ifdef DEBUG
         cerr << "text: '" << tobj->GetText() << "'" << endl;
         VAR(offs);
#endif
         wxString left = tobj->GetText().substr(0,offs); // get part before cursor
         VAR(left);
         tobj->GetText() = tobj->GetText().substr(offs,(*i)->CountPositions()-offs); // keeps the right half
         VAR(tobj->GetText());
         insert(i,obj);
         insert(i,new wxLayoutObjectText(left)); // inserts before
      }
      else
      {
         wxLayoutObjectList::iterator j = i; // we want to apend after this object
         j++;
         if(j != end())
            insert(j, obj);
         else
            push_back(obj);
      }  
   }
   m_CursorPosition.x += obj->CountPositions();
   if(obj->GetType() == WXLO_TYPE_LINEBREAK)
      m_MaxLine++;
}

void
wxLayoutList::Insert(wxString const &text)
{
   TRACE(Insert(text));

   if(! m_Editable)
      return;

   CoordType offs;
   wxLayoutObjectList::iterator i = FindCurrentObject(&offs);

   if(i != end() && (*i)->GetType() == WXLO_TYPE_TEXT)
   {  // insert into an existing text object:
      TRACE(inserting into existing object);
      wxLayoutObjectText *tobj = (wxLayoutObjectText *)*i;
      tobj->GetText().insert(offs,text);
   }
   else
   {
      // check whether the previous object is text:
      wxLayoutObjectList::iterator j = i;
      j--;
      TRACE(checking previous object);
      if(0 && j != end() && (*j)->GetType() == WXLO_TYPE_TEXT)
      {
         wxLayoutObjectText *tobj = (wxLayoutObjectText *)*i;
         tobj->GetText()+=text;
      }
      else  // insert a new text object
      {
         TRACE(creating new object);
         Insert(new wxLayoutObjectText(text));  //FIXME not too optimal, slow
         return;  // position gets incremented in Insert(obj)
      }
   }
   m_CursorPosition.x += strlen(text.c_str());
}

CoordType
wxLayoutList::GetLineLength(wxLayoutObjectList::iterator i)
{
   if(i == end())
      return 0;

   CoordType len = 0;

   // search backwards for beginning of line:
   while(i != begin() && (*i)->GetType() != WXLO_TYPE_LINEBREAK)
      i--;
   if((*i)->GetType() == WXLO_TYPE_LINEBREAK)
      i++;
   // now we can start counting:
   while(i != end() && (*i)->GetType() != WXLO_TYPE_LINEBREAK)
   {
      len += (*i)->CountPositions();
      i++;
   }
   return len;
}

void
wxLayoutList::Clear(void)
{
   wxLayoutObjectList::iterator i = begin();

   while(i != end()) // == while valid
      erase(i);

   // set defaults
   m_FontPtSize = 12;
   m_FontUnderline = false;
   m_FontFamily = wxDEFAULT;
   m_FontStyle = wxNORMAL;
   m_FontWeight = wxNORMAL;
   m_ColourFG = wxTheColourDatabase->FindColour("BLACK");
   m_ColourBG = wxTheColourDatabase->FindColour("WHITE");

   m_Position = wxPoint(0,0);
   m_CursorPosition = wxPoint(0,0);
   m_MaxLine = 0;
   m_LineHeight = (12*m_FontPtSize)/10;
   m_MaxX = 0; m_MaxY = 0;
}
