/*
 *  File:        math_inset.C
 *  Purpose:     Implementation of insets for mathed
 *  Author:      Alejandro Aguilar Sierra <asierra@servidor.unam.mx> 
 *  Created:     January 1996
 *  Description: 
 *
 *  Dependencies: Xlib, XForms
 *
 *  Copyright: (c) 1996, Alejandro Aguilar Sierra
 *
 *   Version: 0.5beta.
 *
 *   You are free to use and modify this code under the terms of
 *   the GNU General Public Licence version 2 or later.
 */

#include "math_inset.h"
#include "symbol_def.h"

inline
char *strnew(const char* s)
{
   char *s1 = new char[strlen(s)+1];
   return strcpy(s1, s);
}


void LyxMathIter::Reset()
{
   if (array->last>0 && MathIsFont(array->bf[0])) {
      fcode = array->bf[0];
      pos = 1;
   } else {
     fcode = -1;
     pos = 0;
   }
}

byte LyxMathIter::GetChar()
{
   if (MathIsFont(array->bf[pos])) 
      fcode = array->bf[pos++];
   return array->bf[pos];
}

byte* LyxMathIter::GetString(int& len)
{
   if (IsFont()) pos++;
   byte *s = &array->bf[pos];
   len = pos;
   while (array->bf[pos]>=' ' && pos<array->last) pos++;
   len = pos-len;   
   
   return s;
}

LyxMathInset* LyxMathIter::GetInset()
{
   if (IsInset()) {
      LyxMathInset* p;
      memcpy(&p, &array->bf[pos+1], sizeof(p));
      return p;
   } else {
      fprintf(stderr,"Math Error: This is not an inset[%d]\n", array->bf[pos]);
     return NULL;
   }
}

// An active math inset MUST be derived from MathParInset
MathParInset* LyxMathIter::GetActiveInset()
{
   if (IsActive()) {
      MathParInset* p;
      memcpy(&p, &array->bf[pos+1], sizeof(p));
      return p;
   } else {
      fprintf(stderr,"Math Error: This is not an active inset\n");
     return NULL;
   }
}

Bool LyxMathIter::Next()
{  
   if (!OK()) return False;

   if (array->bf[pos]==LM_TC_TAB) 
      fcode = -1;     
   
   if (IsInset()) {
      pos += sizeof(char*) + 2;
      fcode = -1;
   } else 
     pos++;
   
   if (IsFont()) 
     fcode = array->bf[pos++];

   return True;   
}

Bool LyxMathIter::Prev()
{
   if (pos==0 || (pos==1 && array->bf[pos]>=' ' && MathIsFont(array->bf[0]))) 
     return False;
   pos--;
      
   if (IsFont()) pos--;
   
   if (IsInset())
     pos -= sizeof(char*) + 1; 
   
   if (array->bf[pos]>=' ') {
      int t;
      for (t = pos-1; t>0 && array->bf[t]>=' '; t--);
      if (MathIsFont(array->bf[t])) 
	fcode = array->bf[t];
      else 
	fprintf(stderr, "Math error: No font code!\n");
   } else
     fcode = -1;
   
   return True;
}

void LyxMathIter::Insert(byte c, LyxMathTextCodes t)
{
   if (c<' ') return;

   if (IsFont() && array->bf[pos]==t) {
      fcode = t;
      pos++;
   } else
   if (t!=fcode && pos>0 && MathIsFont(array->bf[pos-1])) {
      pos--;
      int k;
      for (k=pos-1; k>=0 && array->bf[k]>=' '; k--);
      fcode = (k >= 0 && MathIsFont(array->bf[k])) ? array->bf[k]: -1;
   }
   short f = (array->bf[pos]<' ') ? 0: fcode;
   int shift = (t==fcode) ? 1: ((f) ? 3: 2);

   if (t==LM_TC_TAB) {
      shift--;
      c = t;
   }

   while (array->last+shift >= array->maxsize)
     array->Resize(array->maxsize+ARRAY_STEP); 
   if (pos < array->last)
     array->Move(pos, shift);
   else {
      array->last += shift;
      array->bf[array->last] = '\0';
   }
   if (t != fcode) {
      if (f)  
	array->bf[pos+shift-1] = fcode;
      if (c>=' ') {
	 array->bf[pos++] = t;
	 fcode = t;
      }
   }      
   array->bf[pos++] = c;
}

// Prepare to insert a non-char object
void LyxMathIter::split(int shift)
{
   if (pos < array->last) {
      bool fg = false;
      if (array->bf[pos]>=' ') {
	 if (pos> 0 && MathIsFont(array->bf[pos-1]))
	   pos--;
	 else { 
	    fg = true; 
	    shift++;
	 }
      }      
      array->Move(pos, shift);
      if (fg) array->bf[pos+shift-1] = fcode;
   } else {
      if (array->last+shift>=array->maxsize)
	array->Resize(array->maxsize+ARRAY_STEP);
      array->last += shift;
   }
}

// I assume that both pos and pos2 are legal positions
void LyxMathIter::join(int pos2)
{   
    if (!OK() || pos2<=pos)
      return;    

    short f=fcode;	    
    if (pos>0 && array->bf[pos]>=' ' && MathIsFont(array->bf[pos-1])) 
      pos--;	
    	    
    if (MathIsFont(array->bf[pos2-1]))
      pos2--;
    
    if (array->bf[pos2]>=' ') {
	for (int p=pos2; p>0; p--) 
	  if (MathIsFont(array->bf[p])) {
	      f = array->bf[p];
	      break;
	  }
	array->bf[pos++] = f;
    }    

    array->Move(pos2, -(pos2-pos));
}

void LyxMathIter::Insert(LyxMathInset* p, int type)
{
   int shift = sizeof(p)+2;
   if (!MathIsInset(type))
     type = LM_TC_INSET;
   split(shift);
   array->bf[pos++] = type;
   memcpy(&array->bf[pos], &p, sizeof(p));
   pos += sizeof(p);
   array->bf[pos++] = type;
   array->bf[array->last] = '\0';
   fcode = -1;
}


Bool LyxMathIter::Delete()
{   
   if (!OK())
     return False;
   
   int shift = 0;
   byte c = GetChar();
   if (c>=' ') { 
      if (MathIsFont(array->bf[pos-1]) && array->bf[pos+1]<' ') {
	 int i;
	 shift = 2;
	 pos--;
	 for (i=pos-1; i>0 && !MathIsFont(array->bf[i]); i--);
	 if (i>0 && MathIsFont(array->bf[i]))
	   fcode = array->bf[i];
      } else
	shift = 1;      
   } else {
      if (MathIsInset(array->bf[pos]))
	shift = sizeof(char*) + 2;
     else 
      if (c==LM_TC_TAB) {
	 shift++;
//	 fprintf(stderr, "Es un tab.");
      }
     else {
	fprintf(stderr, "Math Warning: expected inset.");
	fflush(stderr);
     }
   } 
   if (shift!=0) {
      array->Move(pos+shift, -shift);
      if (pos>=array->last)
	 pos = (array->last>0) ? array->last: 0;
       return True;
   } else
     return False;
}

LyxArrayBase *LyxMathIter::Copy(int pos1, int pos2)
{
   if (!array) {
      fprintf(stderr, "Math error: Attempting to copy a void array.\n");
      return NULL;
   }
      
   int posx = pos;
   LyxArrayBase *t=array, *a;
    
   if (pos1>0 || pos2<=array->last) {       
       short fc=0;
       if (pos1>0 && array->bf[pos1]>' ') {
	   for (int p=pos1; p>=0; p--) 
	     if (MathIsFont(array->bf[p])) {
		 if (p!=pos1-1)
		   fc = array->bf[p];
		 else
		   pos1--;
		 break;
	     }
       }

       if (pos2>0 && array->bf[pos2]>=' ' && MathIsFont(array->bf[pos2-1])) 
	 pos2--;

       int dx = pos2 - pos1;
       a = new LyxArrayBase(dx+ARRAY_MIN_SIZE);
//       fprintf(stderr, "VA %d %d %d ", pos1, pos2, dx); fflush(stderr);
       memcpy(&a->bf[(fc) ? 1: 0], &array->bf[pos1], dx);
       if (fc) {
	   a->bf[0] = fc;
	   dx++;
       }
       a->last = dx;
       a->bf[dx] = '\0';
   }  else   
      a = new LyxArrayBase(*array);
   SetData(a);
   while (OK()) {
      if (IsInset()) {
	 LyxMathInset* inset = GetInset();
	 inset = inset->Clone();
	 memcpy(&array->bf[pos+1], &inset, sizeof(inset));
      }
      Next();
   }
   pos = posx;
   array = t;
   return a;
}


void LyxMathIter::Clean(int pos1, int pos2)
{
   if (!array) {
      fprintf(stderr, "Math error: Attempting to clean a void array.\n");
      return;
   }   
   Reset();  
   if (pos1>0)
     while (pos<pos1 && OK()) Next(); 

   while (OK() && pos<pos2) {
      if (IsInset()) {
	 LyxMathInset* inset = GetInset();
	 delete inset;
      }
      Next();
   }
   if (pos2<=array->last) {
       pos = pos1;
       join(pos2);
   }
}


void LyxMathIter::Merge(LyxArrayBase *a, int nc)
{
   if (!a) {
      fprintf(stderr, "Math error: Attempting to merge a void array.\n");
      return;
   }
   split(a->last);
   memcpy(&array->bf[pos], &a->bf[0], a->last);

   // All insets must be clonned
   LyxMathIter it(a);
   int pos1=pos, pos2 = pos + a->last, pos3=0;
   int nt=0;
   Reset();
   while (OK()) {
      if (pos>=pos1 && it.OK() && pos<pos2) {
	  if (IsInset()) {
	      LyxMathInset* inset = GetInset();
	      inset = inset->Clone();
	      memcpy(&array->bf[pos+1], &inset, sizeof(inset));
	  }
	  it.Next(); 
      }
      if (IsTab()) nt++;
       
      if (IsTab() && nt>nc)
	 Delete(); 
      else
	 Next();
   }
}

//-------------------  Insets stuff ----------------------


LyxMathInset::LyxMathInset(LyxMathInset* inset) 
{
   if (inset) {
      name = inset->GetName();
      objtype = inset->GetType();
      size = inset->GetStyle();
      width = inset->Width();
      ascent = inset->Ascent();
      descent = inset->Descent();
   } else {
      objtype = LM_OT_UNDEF;
      size = LM_ST_TEXT;
      width = ascent = descent = 0;
      name = NULL;
   }
}


MathFuncInset::MathFuncInset(char *nm, short ot, short st):
    LyxMathInset(nm, ot, st)
{
   ln = 0;
   lims = (GetType()==LM_OT_FUNCLIM);
   Metrics();
}

LyxMathInset *MathFuncInset::Clone()
{ 
   char *s = (GetType()==LM_OT_UNDEF) ? strnew(name): name;
   LyxMathInset *l = new MathFuncInset(s, GetType(), GetStyle());
   return l;
}

MathSpaceInset::MathSpaceInset(int sp, short ot, short st):
    LyxMathInset("", ot, st), space(sp)
{
   Metrics();
}

LyxMathInset *MathSpaceInset::Clone()
{ 
   LyxMathInset *l = new MathSpaceInset(space, GetType(), GetStyle());
   return l;
}

MathParInset::MathParInset(short st, char *nm, short ot):
   LyxMathInset(nm, ot, st)
{
   array = NULL;
   fixedsz = false;
   ascent = 8;
   width = 4;
   descent = 0;
}

MathParInset::MathParInset(MathParInset* p): LyxMathInset(p)
{
   fixedsz = false;
   while (p->Up());
   LyxMathIter it(p->GetData());
   array = it.Copy();
   
   // A standard paragraph shouldn't have any tabs.
   if (array) {
      it.SetData(array);
      while (it.OK()) {
	 if (it.GetChar()==LM_TC_TAB) it.Delete();
	 it.Next();
      }
   }
}

MathParInset::~MathParInset()
{
   if (array) {
      LyxMathIter it(array);
      it.Clean();
      delete array;
   }
}

LyxMathInset *MathParInset::Clone()
{   
   MathParInset* p = new MathParInset(this);
   return p;
}

MathSqrtInset::MathSqrtInset(short st): MathParInset(st, "sqrt", LM_OT_SQRT)
{
}


LyxMathInset *MathSqrtInset::Clone()
{   
   MathSqrtInset* p = new MathSqrtInset(GetStyle());
   LyxMathIter it(array);
   p->SetData(it.Copy());
   p->Metrics();
   return p;
}


MathDelimInset::MathDelimInset(int l, int r, short st): 
  MathParInset(st, "", LM_OT_DELIM), left(l), right(r)
{
}

LyxMathInset *MathDelimInset::Clone()
{   
   MathDelimInset* p = new MathDelimInset(left, right, GetStyle());
   LyxMathIter it(array);
   p->SetData(it.Copy());
   p->Metrics();
   return p;
}


MathDecorationInset::MathDecorationInset(int d, short st): 
  MathParInset(st, "", LM_OT_DECO), deco(d)
{
   upper = (deco!=LM_underline && deco!=LM_underbrace);
}

LyxMathInset *MathDecorationInset::Clone()
{   
   MathDecorationInset* p = new MathDecorationInset(deco, GetStyle());
   LyxMathIter it(array);
   p->SetData(it.Copy());
   p->Metrics();
   return p;
}

MathFracInset::MathFracInset(short st): MathParInset(st, "frac", LM_OT_FRAC)
{
   den = new MathParInset(st);
   dh = 0;
   Metrics();
   upper = true;
}

MathFracInset::~MathFracInset()
{
    delete den;
}

LyxMathInset *MathFracInset::Clone()
{   
   MathFracInset* p = new MathFracInset(GetStyle());
   LyxMathIter itn(array);
   LyxMathIter itd(den->GetData());
   p->SetData(itn.Copy(), itd.Copy());
   return p;
}

bool MathFracInset::Up()
{
   if (upper)
     return false;
   else 
     upper = true;
   return true;
}


bool MathFracInset::Down()
{
   if (upper) {
      upper = false;
      return true;
   } 
   return false;
}

void MathFracInset::SetStyle(short st)
{
   if (st!=size) {
      dh = 0;
      MathParInset::SetStyle(st);
      den->SetStyle(st);
      size = st;
      Metrics();
   }
}

void MathFracInset::SetData(LyxArrayBase *n, LyxArrayBase *d)
{
   den->SetData(d);
   MathParInset::SetData(n);
   Metrics();
}

void MathFracInset::SetData(LyxArrayBase *d)
{
   if (upper)
     MathParInset::SetData(d);
   else {
      den->SetData(d);
      Metrics();
   }
}

void MathFracInset::GetXY(int& x, int& y) const
{  
   if (upper)
     MathParInset::GetXY(x, y);
   else
     den->GetXY(x, y);
}
   
LyxArrayBase *MathFracInset::GetData(void)
{
   if (upper)
     return array;
   else
     return den->GetData();
}


void MathFracInset::SetFocus(int x, int y)
{  
   upper = !(den->Inside(x, y));
}


MathMatrixInset::MathMatrixInset(int m, int n, short st): 
   MathParInset(st, "array", LM_OT_MATRIX), nc(m)
{
   ws = new int[nc];
   crow = NULL;
   v_align = 0;
   h_align = new char[nc+1];
   for (int i =0; i<nc; i++) h_align[i] = 'c'; 
   h_align[nc] = '\0';
   mflag = false;
   while (n--) {
      AddRow();
   }
   crow = row;
}


MathMatrixInset::MathMatrixInset(MathMatrixInset *mt): 
   MathParInset(mt->GetStyle(), mt->GetName(), mt->GetType())
{
   int ir;
    
   nc = mt->nc;
   ws = new int[nc];
   h_align = new char[nc+1];
   strcpy(h_align, mt->GetAlign(&v_align));
   mflag = true;
   crow = NULL;
   ir = mt->GetRowIdx();
   mt->Top();
   LyxMathIter it;
   do {
      AddRow();
      it.SetData(mt->GetData());
      crow->array = it.Copy();
      crow->numbered = mt->IsNumbered();
      if (mt->GetLabel()) 
	 crow->label = strnew(mt->GetLabel());       
   } while (mt->Down());
   mflag = false;
   crow = row;
   Metrics();

    // Left mt at its original position   
   mt->Top();
   while (ir--) mt->Down();
}

MathMatrixInset::~MathMatrixInset()
{
   delete[] ws;
   rowst *q, *r = row;
   array = NULL;
   while (r) {
      q = r->next;
      LyxMathIter it;
      if (r->array) {
	 it.SetData(r->array);
	 it.Clean();
      }
      delete r;
      r = q;
   }
}

LyxMathInset *MathMatrixInset::Clone()
{   
   MathMatrixInset* mt = new MathMatrixInset(this);
   return mt;
}

void MathMatrixInset::SetAlign(char vv, char* hh)
{
   v_align = vv;
   strncpy(h_align, hh, nc);
   Metrics();
}

void MathMatrixInset::AddRow()
{
   rowst *r = new rowst(nc+1);
   r->prev = crow;
  
   if (crow) {
      r->next = crow->next;
      crow->next = r;
      if (r->next) 
	r->next->prev = r;
   } else {
      r->next = NULL;
      row = r;
   }   
   crow = r;
   
   if (!mflag) {
      crow->array = new LyxArrayBase;
      for (int i =0; i<nc-1; i++) crow->array->Insert(i, LM_TC_TAB);
      ct = 0;
   }
}
   
bool MathMatrixInset::DelRow()
{
   rowst *rn = crow->next;
   rowst *rp = crow->prev;
   
   if (!rn && !rp) return false;
   
   delete crow;
   if (rp) rp->next = rn; else row = rn;
   if (rn) rn->prev = rp;
   crow = (rn) ? rn: rp;
   array = crow->array;
   
   return true;
}
   
void MathMatrixInset::SetData(LyxArrayBase *a)
{
   if (!a) return;
   LyxMathIter it(a);
   int nn = nc-1;
   while (it.OK()) {
      if (it.GetChar()==LM_TC_TAB) nn--;
      if (nn<0) it.Delete();
      it.Next();
   }  
   it.Reset();
   
   // Automatically inserts tabs around bops
   while (nn>0) {
      if (it.FCode()==LM_TC_BOP) {	 
	 it.Insert(' ', LM_TC_TAB); nn--;
	 it.Next();
	 it.Next();
	 if (nn>0) {	    
	    it.Insert(' ', LM_TC_TAB); 
	    nn--;
	 } 
      } else 
	if (!it.OK()) {
	   it.Insert(' ', LM_TC_TAB); 
	   nn--;
	}
      it.Next();
   }
   array = crow->array = a;
}

bool MathMatrixInset::Up()
{
   if (crow->prev) {
      crow = crow->prev; 
      return true;
   } else
     return false;
} 

bool MathMatrixInset::Down()
{
   if (crow->next) {
      crow = crow->next; 
      return true;
   } else
     return false;
}

void MathMatrixInset::Top()
{   
   crow = row;
} 

void MathMatrixInset::Bottom()
{
   while (Down());
} 

int MathMatrixInset::GetRowIdx()
{
   int i=0;
   rowst *r = row;
   
   while (r!=crow) {
      r = r->next;
      i++;
   }
   return i;
}
   
void MathMatrixInset::Metrics()
{
   int i, ctx=ct, hl=0, h=0, y=0;
   rowst *r = crow;
   
   for (i=0; i<nc; i++) ws[i] = 0;
   crow = row;
   mflag = true;
   
   // Parcial metrics.  (Dirty trick, hmmm.. )
   while (crow) {
      ct = 0;
      array = crow->array;
      MathParInset::Metrics();
      crow->asc = Ascent();
      crow->desc = Descent();
      crow->y = (crow==row) ? crow->asc: 
                crow->asc + crow->prev->desc + MATH_ROWSEP + y;
      y = crow->y;
      h += crow->asc + crow->desc + MATH_ROWSEP;      
      crow->array = array;
      if (nc==1) {
	 crow->w[0] = width;
	 if (width > ws[0]) ws[0] = width;
      }
      crow = crow->next;
   }
   hl = Descent();
   crow = r;
   h -= MATH_ROWSEP;
   
   //  Compute vertical align
   switch (v_align) {
    case 't': ascent = row->y; break;
    case 'b': ascent = h - hl; break;
    default:  ascent = h/2; break;
   }
   descent = h - ascent + 2;
   
   // Adjusting local tabs
   r = row;
   width = MATH_COLSEP;
   while (r) {   
      int rg=MATH_COLSEP, ww, lf=0, *w = r->w;
      for (i=0; i<nc; i++) {
	 switch (h_align[i]) {
	  case 'l': lf = 0; break;
	  case 'c': lf = (ws[i] - w[i])/2; break;
	  case 'r': lf = ws[i] - w[i]; break;
	 }
	 ww = lf + w[i];
         w[i] = lf + rg;
	 rg = ws[i] - ww + MATH_COLSEP;
	 if (r==row) width += ws[i] + MATH_COLSEP;
      }
      r = r->next;
   }
   ct = ctx;
   mflag = false;
}


void MathMatrixInset::Draw(long unsigned int pm, int x, int baseline)
{
   int ctx = ct, x2 = x, y2 = baseline - ascent;
   rowst *r = crow;
   
   crow = row;
   while (crow) {
      ct = 0;
      array = crow->array;
      //fprintf(stderr, "YY[%d]", y2 + crow->y);
      MathParInset::Draw(pm, x2/* + crow->w[0]*/, y2 + crow->y);
      crow = crow->next;    
   }
   crow = r;
   ct = ctx;  xo = x2;  yo = baseline;
}

void MathMatrixInset::SetFocus(int /*x*/, int y)
{  
   rowst *r = row;
   int yy, ny = 0; // yy = yo;
   while (r) {
      ny++;
      yy = yo - ascent + r->y;  
      
      if (y >= yy - r->asc && y <= yy + r->desc) {
	 ct = 0;
	 crow = r;
	 break;
      }
      r = r->next;    
   }
}

void MathMatrixInset::GetXY(int& x, int& y) const
{  
   if (crow && ct>=0 && ct<nc) {
      x = xo + crow->w[0]; 
      y = yo + crow->y - ascent;
   }
}
   

LyxArrayBase *MathMatrixInset::GetData()
{
   //fprintf(stderr, "Crow %x %d!", crow, ct); fflush(stderr);
   if (crow) {
      ct = 0;
      return crow->array;
   } else {
      fprintf(stderr, "No crow!");
      return NULL;
   }
}

void MathMatrixInset::SetTab(int t)
{
   if (ct>=nc) {
      fprintf(stderr, "error1[%d]", ct);
      return;
   }   
   if (t > ws[ct]) ws[ct] = t;
   crow->w[ct++] = t;
}

int MathMatrixInset::GetTab(int i)
{
   if (mflag)
     return 0;
   
   if (ct+i<nc) {
      ct += i;
      if (ct<0) ct = 0;
      return crow->w[ct];
   } else {
      fprintf(stderr, "Excesive tabs[%d]", ct);
      return 0;
   }
}


MathAccentInset::MathAccentInset(byte cx, LyxMathTextCodes f, int cd, short st): 
  LyxMathInset("", LM_OT_ACCENT, st), c(cx), fn(f), code(cd)
{
   if (!MathIsAlphaFont(fn) && fn!=LM_TC_SYMB && fn!=LM_TC_BSYM) {  
      fn = LM_TC_NORMAL;
      c = 'a';
   }
   Metrics();
}

LyxMathInset *MathAccentInset::Clone()
{   
   MathAccentInset* p = new MathAccentInset(c, fn, code, GetStyle());
   return p;
}


MathBigopInset::MathBigopInset(char* name, int id, short st): 
  LyxMathInset(name, LM_OT_BIGOP, st), sym(id)
{
   lims = (sym!=LM_int && sym!=LM_oint);
   Metrics();
}

LyxMathInset *MathBigopInset::Clone()
{   
   MathBigopInset* p = new MathBigopInset(name, sym, GetStyle());
   return p;
}
 
MathDotsInset::MathDotsInset(char* name, int id, short st):
  LyxMathInset(name, LM_OT_DOTS, st), code(id)
{
   Metrics();
}

LyxMathInset *MathDotsInset::Clone()
{
   MathDotsInset* p = new MathDotsInset(name, code, GetStyle());
   return p;
}     

