/* Length.C
 *
 * The official LaTeX length parameters are implemented here. They control
 * variables such as the paragraph indentation and the space to skip between
 * lines.
 *
 * Copyright 1992 Jonathan Monsarrat. Permission given to freely distribute,
 * edit and use as long as this copyright statement remains intact.
 *
 */

#include "Global.h"
#include "Font.h"
#include <string.h>
#include <search.h>
#include <stdlib.h>
#include <stdio.h>

LengthParam::LengthParam(LengthParam *lp, Length *parent)
{
   _value = lp->_value;
   _tokentext = lp->_tokentext;
   _parent = parent;
}

LengthParam::LengthParam(float value, char *tokentext, Length *parent)
{
   _value = value;
   _tokentext = new char [strlen(tokentext)+1];
   strcpy(_tokentext,tokentext);
   _parent = parent;
}

LengthParam::~LengthParam()
{
   delete _tokentext;
}

int LengthParam::compare(const void *length1, const void *length2)
{
   LengthParam **len1;
   LengthParam **len2;

   len1 = (LengthParam **) length1;
   len2 = (LengthParam **) length2;

   return strcmp((*len1)->_tokentext, (*len2)->_tokentext);
}

void LengthParam::set(float value)
{
   if(_value != value) {
      if(value > _value && match("\\baselineskip"))
	 Global::files->readjust_vspace = value;
      _value = value;
      postscript_set();
   }
}

float LengthParam::get()
{
   return _value;
}

void LengthParam::revert(Length *from)
{
   LengthParam **lp;
   lp = from->fetch(_tokentext);
   if(_value != (*lp)->get())
      postscript_set();
}

void LengthParam::postscript_set()
{
   LengthParam **lp;
   Global::files->outfile << endl;

   if(match("\\baselineskip"))
      Global::files->outfile << _value << " BASELINESKIP" << endl;
   else if(match("\\parindent"))
      Global::files->outfile << "/parindent " <<  _value << " def" << endl;
   else if(match("\\parskip"))
      Global::files->outfile << "/parskip " <<  _value << " def" << endl;
   else if(match("\\textheight")) {
       lp = _parent->fetch("\\topmargin");
      Global::files->outfile << "/bottommargin "
	 << 684.0 - _value - (*lp)->get() << " def" << endl;
   }
   else if(match("\\textwidth") || match("\\linewidth")) {
      lp = _parent->fetch("\\oddsidemargin");
      Global::files->outfile << "/rightmargin "
	 << 540.0 - _value - (*lp)->get() << " def" << endl;
   }
   else if(match("\\topmargin")) {
      Global::files->outfile << "/topmargin " << _value+108 << " def" << endl;
      lp = _parent->fetch("\\textheight");
      Global::files->outfile << "/bottommargin "
	 << 684.0 - _value - (*lp)->get() << " def" << endl;
   }
   else if(match("\\oddsidemargin") || match("\\evensidemargin")) {
      Global::files->outfile << "/leftmargin " << _value+72 << " def" << endl;
      lp = _parent->fetch("\\textwidth");
      Global::files->outfile << "/rightmargin "
	 << 540.0 - _value - (*lp)->get() << " def" << endl;
   }
}

int LengthParam::match(char *tokentext)
{
   return !strcmp(_tokentext,tokentext);
}

Length::Length()
{
   /* The LaTeX defaults for the LaTeX Length Parameters */
   numvalues = 0;
   makeparam( 12.0, "\\baselineskip");    // space between lines
   makeparam(  1.0, "\\baselinestretch"); //  ditto, in units of lines
   makeparam(126.0, "\\linewidth");       // same as textwidth
   makeparam( 18.0, "\\parindent");       // paragraph indentation
   makeparam(  0.0, "\\parskip");         // space between paragraphs
   makeparam(540.0, "\\textheight");      // height of the page
   makeparam(360.0, "\\textwidth");       // width of the page
   makeparam( 54.0, "\\topmargin");       // top margin
   makeparam( 54.0, "\\oddsidemargin");   // left margin, basically
   makeparam( 54.0, "\\evensidemargin");  // left margin, basically
   makeparam( 21.4, "\\bigskipamount");   // big vertical skip
   makeparam( 16.9, "\\medskipamount");   // medium vertical skip
   makeparam( 14.5, "\\smallskipamount"); // small vertical skip
   makeparam( 28.34,"cm");                // centimeters (28 pts)
   makeparam( 10.0, "em");                // width of letter M in current font
   makeparam(  8.0, "ex");                // width of letter X in current font
   makeparam( 72.0, "in");                // inches (72 pts)
   makeparam( 12.0, "pc");                // Picas (1pc = 12pt)
   makeparam(  1.0, "pt");                // Points
   makeparam(  2.83,"mm");                // millimeters
   for(int x=0; x < numvalues; x++)  // Initialize the variables
      postscript_set(x);
}

/* The class Length contains an array of pointers. The automatic definition
 * created by the C++ compiler just copies over these pointers to the new
 * Length. But we want the new Length being created to actually have copies
 * of all the LengthParams in *values[], not pointers to the same ones!
 * So an explicit definition is written here.
 */
Length::Length(Length *base)
{
   numvalues = base->numvalues;
   for(int index=0; index < numvalues; index++)
      values[index] = new LengthParam(base->values[index], this);
}

Length::~Length()
{
   for(int x=0; x < numvalues; x++)
      delete values[x];
}

Param *Length::copy()
{
   return new Length(this);
}

// Fetches the LengthParam in array values with given name
LengthParam **Length::fetch(char *tokenstr)
{
   LengthParam key(0.0, tokenstr, this);
   LengthParam *keyptr = &key;
   LengthParam **keyptrptr = &keyptr;
   LengthParam **lp = (LengthParam **)
      bsearch((char *)keyptrptr, (char *) values,
	      numvalues, sizeof(LengthParam *), LengthParam::compare);
   return lp;
}

void Length::makeparam(float value, char *tokentext)
{
   values[numvalues++] = new LengthParam(value, tokentext, this);
   qsort((char*)values, numvalues,
	 sizeof(LengthParam *), LengthParam::compare);
}

void Length::set_lp(LengthParam *lp, float value)
{
   LengthParam **skip;
   lp->set(value);
   if(lp->match("\\baselinestretch")) {
      skip = fetch("\\baselineskip");
      float fontsize = Stack::get(Environment::PFont, Font::Size, "");
      (*skip)->set(value * fontsize * 1.2);  // 1.2 is magic spacing number
   } else if(lp->match("\\oddsidemargin")) {
      skip = fetch("\\evensidemargin");
      (*skip)->set(value);
   } else if(lp->match("\\evensidemargin")) {
      skip = fetch("\\oddsidemargin");
      (*skip)->set(value);
   }
}

// Set the value of the LengthParam in the values array with given name
// to the given value. Returns success boolean.
int Length::set(int, float value, char *tokentext)
{
   LengthParam **lp;
   if((lp=fetch(tokentext)) == NULL)
      return FALSE;

   set_lp(*lp,value);
   return TRUE;
}

float Length::get(int subtype, char *tokentext)
{
   if(subtype == Parse_Length)
      return length_argument();

   LengthParam **lp;   // Get parameter value
   if((lp=fetch(tokentext)) == NULL) {
      char message[MAXSTRING];
      sprintf(message, "No length parameter %s defined", tokentext);
      Global::files->fatal_error(message);
   }
   return (*lp)->get();
}

void Length::postscript_set(int index)
{
   values[index]->postscript_set();
}

void Length::revert(Param *from)
{
   for(int index=0; index < numvalues; index++)
      values[index]->revert((Length *)from);
}

/* Parses an argument between braces to be used in a length function
 * such as \addtolength and \setlength. Returns the length in points.
 */
float Length::length_argument()
{
   char *tokenname;
   LengthParam **lp;
   float val = 1.0;
   Global::files->set_parsing_length(TRUE);
   for(Token command; !command.match("}"); command = Token()) {
      if(command.match(""))
	 continue;
      tokenname = command.get_text();
      if(tokenname[0] >= '0' && tokenname[0] <= '9'
	 || tokenname[0]=='-' || tokenname[0]=='.') {
	 float t;           // A number
	 sscanf(tokenname,"%f",&t);
	 val *= t;
      } else {              // A variable
	 lp = fetch(tokenname);
	 if(!lp) {
	    char message[MAXSTRING];
	    sprintf(message, "Undefined length parameter %s", tokenname);
	    Global::files->fatal_error(message);
	 }
	 val *= (*lp)->get();
      }
   }
   
   Global::files->set_parsing_length(FALSE);
   return val;
}

void Length::addtolength(int, int, float, char *)
{
   Token openbrace;
   if(!openbrace.match("{"))
      Global::files->fatal_error(
             "Expecting '{' after \\addtolength statement");
   
   Token lengthparam;
   if(lengthparam.match("}"))
      Global::files->fatal_error(
            "Expecting lengthparam before closing '}' in \\addtolength");
      
   LengthParam **lp = fetch(lengthparam.get_text());

   Token closebrace;
   if(!closebrace.match("}"))
      Global::files->fatal_error(
         "More than one word before closing '}' in \\addtolength");

   openbrace = Token();
   if(!openbrace.match("{"))
      Global::files->fatal_error(
           "Expecting second '{' after \\addtolength statement");
   
   set_lp(*lp,(*lp)->get()+Length::length_argument());
}

void Length::newlength(int, int, float, char *)
{
   Token openbrace;
   if(!openbrace.match("{"))
      Global::files->fatal_error("Expecting '{' after \\newlength statement");
   
   Token lengthparam;
   if(lengthparam.match("}"))
      Global::files->fatal_error(
            "Expecting lengthparam before closing '}' in \\newlength");
      
   makeparam(0.0,lengthparam.get_text());

   Token closebrace;
   if(!closebrace.match("}"))
      Global::files->fatal_error(
         "More than one word before closing '}' in \\newlength");
}

void Length::setlength(int, int, float, char *)
{
   Token openbrace;
   if(!openbrace.match("{"))
      Global::files->fatal_error("Expecting '{' after \\setlength statement");
   
   Token lengthparam;
   if(lengthparam.match("}"))
      Global::files->fatal_error(
            "Expecting lengthparam before closing '}' in \\setlength");

   LengthParam **lp = fetch(lengthparam.get_text());

   Token closebrace;
   if(!closebrace.match("}"))
      Global::files->fatal_error(
         "More than one word before closing '}' in \\setlength");

   openbrace = Token();
   if(!openbrace.match("{"))
      Global::files->fatal_error(
             "Expecting second '{' after \\setlength statement");
   
   set_lp(*lp,Length::length_argument());
}