/***************************************************************************
 *   Copyright (C) 2004 by Tomas Mecir                                     *
 *   kmuddy@kmuddy.org                                                     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 ***************************************************************************/
#include "cmxpstate.h"

#include "centitymanager.h"
#include "cmxpcolors.h"
#include "cresulthandler.h"

#include "rgbops.h"
#include "stringops.h"

#include <config.h>

#include <algorithm>
#include <stdlib.h>

cMXPState::cMXPState (cResultHandler *resh, cElementManager *elm, cEntityManager *enm)
{
  results = resh;
  elements = elm;
  entities = enm;

  //currently implemented MXP version
  mxpVersion = "1.0";

  //starting MXP mode is LOCKED, to prevent problems with non-MXP MUDs).
  //This goes against the MXP protocol, therefore there's a setting that will keep the OPEN
  //mode, if desired - see public API.
  mode = lockedMode;
  defaultmode = lockedMode;
  initiallyLocked = true;
  tempMode = false;
  wasSecureMode = false;
  
  //some default values...
  cMXPColors *colors = cMXPColors::self();
  defaultfg = colors->color ("gray");
  defaultbg = colors->color ("black");
  defaultfont = "Courier";
  defaultsize = 12;
  defaultattribs = 0;
  //by default, all headers are written in the same font (Courier), they are bold and they
  //differ in sizes...
  for (int i = 0; i < 6; i++)
  {
    Hfont[i] = "Courier";
    Hfg[i] = defaultfg;
    Hbg[i] = defaultbg;
    Hattribs[i] = Bold;
  }
  Hsize[0] = 32;
  Hsize[1] = 24;
  Hsize[2] = 20;
  Hsize[3] = 16;
  Hsize[4] = 14;
  Hsize[5] = 12;
  ttFont = "Courier";
  setDefaultGaugeColor (colors->color ("white"));
  //PACKAGE and VERSION are defined in config.h
  clientName = PACKAGE;
  clientVersion = VERSION;
  //some default screen and font attributes...
  fX = 16;
  fY = 8;
  sX = 800;
  sY = 600;
  
  suplink = supgauge = supstatus = supframe = supimage = suprelocate = false;
  
  //params
  reset ();
}


cMXPState::~cMXPState ()
{
  //delete mxpResult structures in closing tags list
  list<closingTag *>::iterator it;
  for (it = closingTags.begin(); it != closingTags.end(); ++it)
  {
    if ((*it)->closingresult)
      delete (*it)->closingresult;
    list<mxpResult *> *rlist = (*it)->closingresults;
    if (rlist)
    {
      list<mxpResult *>::iterator it2;
      for (it2 = rlist->begin(); it2 != rlist->end(); ++it2)
        delete *it2;
      delete rlist;
    }
  }
  closingTags.clear ();
}

//some user-adjustable parameters

void cMXPState::setDefaultText (const string &font, int size, bool _bold, bool _italic,
    bool _underline, bool _strikeout, RGB fg, RGB bg)
{
  if (curfont == defaultfont) curfont = font;
  defaultfont = font;

  if (cursize == defaultsize) cursize = size;
  defaultsize = size;

  char curattrib = (bold?1:0) * Bold + (italic?1:0) * Italic +
      (underline?1:0) * Underline + (strikeout?1:0) * Strikeout;
  char newattribs = (_bold?1:0) * Bold + (_italic?1:0) * Italic +
      (_underline?1:0) * Underline + (_strikeout?1:0) * Strikeout;
  if (curattrib == defaultattribs)
  {
    bold = _bold;
    italic = _italic;
    underline = _underline;
    strikeout = _strikeout;
  }
  defaultattribs = newattribs;

  if (fgcolor == defaultfg) fgcolor = fg;
  defaultfg = fg;
  if (bgcolor == defaultbg) bgcolor = bg;
  defaultbg = bg;
}

void cMXPState::setHeaderParams (int which, const string &font, int size, bool _bold, bool _italic,
    bool _underline, bool _strikeout, RGB fg, RGB bg)
{
  //invalid H-num?
  if ((which < 1) || (which > 6))
    return;

  Hfont[which - 1] = font;

  Hsize[which - 1] = size;

  char newattribs = (_bold?1:0) * Bold + (_italic?1:0) * Italic +
      (_underline?1:0) * Underline + (_strikeout?1:0) * Strikeout;
  Hattribs[which - 1] = newattribs;

  Hfg[which - 1] = fg;
  Hbg[which - 1] = bg;
}

void cMXPState::setDefaultGaugeColor (RGB color)
{
  gaugeColor = color;
}

void cMXPState::setNonProportFont (string font)
{
  ttFont = font;
}

void cMXPState::setClient (string name, string version)
{
  clientName = name;
  clientVersion = version;
}

void cMXPState::supportsLink (bool supports)
{
  suplink = supports;
}

void cMXPState::supportsGauge (bool supports)
{
  supgauge = supports;
}

void cMXPState::supportsStatus (bool supports)
{
  supstatus = supports;
}

void cMXPState::supportsSound (bool supports)
{
  supsound = supports;
}

void cMXPState::supportsFrame (bool supports)
{
  supframe = supports;
}

void cMXPState::supportsImage (bool supports)
{
  supimage = supports;
}

void cMXPState::supportsRelocate (bool supports)
{
  suprelocate = supports;
}

void cMXPState::switchToOpen ()
{
  mode = openMode;
  defaultmode = openMode;
  initiallyLocked = false;
  //not we conform to MXP spec... use with care - only affects non-MXP MUDs, where it allows
  //open tags - MUDs supporting MXP are NOT affected
}

void cMXPState::reset ()
{
  bold = defaultattribs & Bold;
  italic = defaultattribs & Italic;
  underline = defaultattribs & Underline;
  strikeout = defaultattribs & Strikeout;
  fgcolor = defaultfg;
  bgcolor = defaultbg;
  curfont = defaultfont;
  cursize = defaultsize;
  inVar = false;
  varValue = "";
  inParagraph = false;
  ignoreNextNewLine = false;
  inLink = false;
  isALink = false;
  linkText = "";
  gotmap = false;
  curWindow = "";
  prevWindow = "";
  
}

//modes, mode switching

mxpMode cMXPState::getMXPMode ()
{
  return mode;
}

void cMXPState::setMXPMode (mxpMode m)
{
  mode = m;
  tempMode = false;
  wasSecureMode = false;
  
  //if we start in LOCKED mode and mode change occurs, we set default mode
  //to OPEN, so that we are compatible with the spec...
  if (initiallyLocked)
  {
    initiallyLocked = false;
    defaultmode = openMode;
  }
}

void cMXPState::gotLineTag (int number)
{
  //got a line tag - close outstanding entities, if any (unless we're in LOCKED mode)
  if (mode != lockedMode)
  {
    string t = entities->expandEntities ("", true);
    if (!t.empty())
      gotText (t, false);
  }

  //leaving secure mode
  if (wasSecureMode && (number != 1))
    closeAllTags ();
  wasSecureMode = false;

  if (number < 0) return;
  if (number > 99) return;
  if (number >= 10)
    results->addToList (results->createLineTag (number));
  else
  {
    switch (number) {
      case 0:
        setMXPMode (openMode);
        break;
      case 1:
        setMXPMode (secureMode);
        break;
      case 2:
        setMXPMode (lockedMode);
        break;
      case 3:
        closeAllTags ();
        //default mode remains the same...
        setMXPMode (openMode);
        reset ();
        break;
      case 4:
        setMXPMode (secureMode);
        tempMode = true;
        break;
      case 5:
        setMXPMode (openMode);
        defaultmode = openMode;
        break;
      case 6:
        setMXPMode (secureMode);
        defaultmode = secureMode;
        break;
      case 7:
        setMXPMode (lockedMode);
        defaultmode = lockedMode;
        break;
      default:
        results->addToList (results->createWarning ("Received unrecognized line tag."));
        break;
    };
  }
}

void cMXPState::closeAllTags ()
{
  if (closingTags.empty())
    return;

  //process open tags one by one...
  while (!closingTags.empty())
  {
    //closingTags is a FIFO queue, tho technically it's a list
    closingTag *tag = closingTags.back ();
    closingTags.pop_back ();

    results->addToList (results->createWarning ("Had to auto-close tag " + tag->name + "."));
    
    closeTag (tag);
  }
}

void cMXPState::commonTagHandler ()
{
  //got a new tag - close outstanding entities, if any (unless we're in LOCKED mode)
  if (mode != lockedMode)
  {
    string t = entities->expandEntities ("", true);
    if (!(t.empty()))
      gotText (t, false);
  }

  //outstanding tags are closed, if we're going out of secure mode, unless a change back to secure
  //mode occurs
  if (wasSecureMode)
  {
    closeAllTags ();
    wasSecureMode = false;
  }

  //error is reported, if we're inside VAR...
  if (inVar)
    results->addToList (results->createError ("Got a tag inside a variable!"));
}

void cMXPState::commonAfterTagHandler ()
{
  //secure mode for one tag?
  if (tempMode)
  {
    tempMode = false;
    //set mode back to default mode
    mode = defaultmode;
  }
}

//regular text

void cMXPState::gotText (const string &text, bool expandentities)
{
  if (text.length() == 0)
    return;
  //temp-secure mode -> ERROR!
  if (tempMode)
  {
    tempMode = false;
    mode = defaultmode;
    results->addToList (results->createError ("Temp-secure line tag not followed by a tag!"));
  }

  //outstanding tags are closed, if we're going out of secure mode, unless a change back to secure
  //mode occurs
  if (wasSecureMode)
  {
    closeAllTags ();
    wasSecureMode = false;
  }

  //expand entities, if needed
  string t;
  if (expandentities && (mode != lockedMode))
    t = entities->expandEntities (text, false);
  else
    t = text;

  //special handling if we're in a variable or a link
  if (inVar)
    varValue.append (t);
  if (inLink)
    linkText.append (t);

  //text can be sent is it's not a part of a link or of a variable
  if (!(inVar || inLink))
    //add text to the list of things to send
    results->addToList (results->createText (t));
}

void cMXPState::gotNewLine ()
{
  //got a newline char - close outstanding entities, if any (unless we're in LOCKED mode)
  if (mode != lockedMode)
  {
    string t = entities->expandEntities ("", true);
    if (!t.empty())
      gotText (t, false);
  }

  //was temp-secure mode?
  if (tempMode)
  {
    tempMode = false;
    mode = defaultmode;
    results->addToList (results->createError ("Temp-secure line tag followed by a newline!"));
  }

  //leaving secure mode?
  wasSecureMode = false;
  if ((mode == secureMode) && (defaultmode != secureMode))
    wasSecureMode = true;

  //ending line in OPEN mode - close all tags!
  if (mode == openMode)
    closeAllTags ();

  //is we're in SECURE mode, some tags may need to be closed...

  //line ended inside a link
  if (inLink)
  {
    inLink = false;
    isALink = false;
    linkText = "";
    results->addToList (results->createError ("Received an unterminated link!"));
  }

  if (inVar)
  {
    inVar = false;
    results->addToList (results->createError ("Received an unterminated VAR tag!"));
    varValue = "";
  }

  //should next newline be ignored?
  if (ignoreNextNewLine)
  {
    ignoreNextNewLine = false;
    return;
  }

  //if we're in a paragraph, don't report the new-line either
  if (inParagraph)
    return;

  //set mode back to default mode
  mode = defaultmode;
  
  //neither NOBR nor P - report newline
  results->addToList (results->createText ("\r\n"));
}

//flags

//we treat flag as another tag - this is needed to allow correct flag closing even if the //appropriate closing tag wasn't sent by the MUD (auto-closing of flag)
void cMXPState::gotFlag (bool begin, string flag)
{
  bool setFlag = false;  //is this a set-variable flag?
  string f = lcase (flag);
  if ((f[0] == 's') && (f[1] == 'e') && (f[2] == 't') && (f[3] == ' '))
    setFlag = true;

  //disable inVar and remember old value, if this is a set-flag
  //this is needed to prevent error report in commonTagHandler()
  bool oldInVar = inVar;
  if (setFlag) inVar = false;

  commonTagHandler();
  
  //restore inVar value
  inVar = oldInVar;
  
  //no -> inform about the flag
  if (begin)
  {
    mxpResult *res = results->createFlag (true, flag);
    mxpResult *res2 = createClosingResult (res);
    results->addToList (res);
    addClosingTag ("flag", res2);
    
    //"set xxx" type of flag?
    if (setFlag)
    {
      if (inVar)  //in variable already
      {
        results->addToList (results->createError
            ("Got a set-flag, but I'm already in a variable definition!"));
        return;
      }
      //we are now in a variable
      inVar = true;
      varName = f.substr (f.rfind (' ') + 1);  //last word
      varValue = "";
    }
  }
  else
  {
    //closing set-flag...
    if (inVar && setFlag)
    {
      results->addToList (results->createVariable (varName, varValue));
      //send variable value, but no varname as in </var>
      results->addToList (results->createText (varValue));
      entities->addEntity (varName, varValue);
      inVar = false;
      varName = "";
      varValue = "";
    }
    gotClosingTag ("flag");
  }
  
  //no commonAfterTagHandler() here - this ain't no real tag :D
}


//tags:

//variables

void cMXPState::gotVariable (const string &name, const string &value, bool erase)
{
  commonTagHandler();

  //send the variable value
  results->addToList (results->createVariable (name, value, erase));

  commonAfterTagHandler();
}

void cMXPState::gotVAR (const string &name)
{
  commonTagHandler();

  if (inVar)
  {
    results->addToList (results->createError ("Nested VAR tags are not allowed!"));
    commonAfterTagHandler();
    return;
  }

  //we are now in a variable
  inVar = true;
  varName = name;
  varValue = "";

  //create a closing result; the variable name shall be updated when the tag will be closed
  addClosingTag ("var");

  commonAfterTagHandler();
}


//text formatting (OPEN tags)

void cMXPState::gotBOLD ()
{
  commonTagHandler();

  mxpResult *res = results->createFormatting (USE_BOLD, Bold, cMXPColors::noColor(),
      cMXPColors::noColor(), "", 0);
  mxpResult *res2 = createClosingResult (res);
  applyResult (res);
  results->addToList (res);
  addClosingTag ("b", res2);

  commonAfterTagHandler();
}

void cMXPState::gotITALIC ()
{
  commonTagHandler();

  mxpResult *res = results->createFormatting (USE_ITALICS, Italic, cMXPColors::noColor(),
      cMXPColors::noColor(), "", 0);
  mxpResult *res2 = createClosingResult (res);
  applyResult (res);
  results->addToList (res);
  addClosingTag ("i", res2);

  commonAfterTagHandler();
}

void cMXPState::gotUNDERLINE ()
{
  commonTagHandler();

  mxpResult *res = results->createFormatting (USE_UNDERLINE, Underline, cMXPColors::noColor(), cMXPColors::noColor(), "", 0);
  mxpResult *res2 = createClosingResult (res);
  applyResult (res);
  results->addToList (res);
  addClosingTag ("u", res2);

  commonAfterTagHandler();
}

void cMXPState::gotSTRIKEOUT ()
{
  commonTagHandler();

  mxpResult *res = results->createFormatting (USE_STRIKEOUT, Strikeout, cMXPColors::noColor(),
      cMXPColors::noColor(), "", 0);
  mxpResult *res2 = createClosingResult (res);
  applyResult (res);
  results->addToList (res);
  addClosingTag ("s", res2);

  commonAfterTagHandler();
}

void cMXPState::gotCOLOR (RGB fg, RGB bg)
{
  commonTagHandler();

  mxpResult *res = results->createFormatting (USE_FG | USE_BG, 0, fg, bg, "", 0);
  mxpResult *res2 = createClosingResult (res);
  applyResult (res);
  results->addToList (res);
  addClosingTag ("c", res2);

  commonAfterTagHandler();
}

void cMXPState::gotHIGH ()
{
  commonTagHandler();

  RGB color = fgcolor;
  //High color is computed by adding 128 to each attribute...
  //This is a very primitive way of doing it, and it's probably insufficient. We'll see.
  color.r = (color.r < 128) ? (color.r + 128) : 255;
  color.g = (color.g < 128) ? (color.g + 128) : 255;
  color.b = (color.b < 128) ? (color.b + 128) : 255;

  mxpResult *res = results->createFormatting (USE_FG, 0, color, cMXPColors::noColor(), "", 0);
  mxpResult *res2 = createClosingResult (res);
  applyResult (res);
  results->addToList (res);
  addClosingTag ("h", res2);

  commonAfterTagHandler();
}

void cMXPState::gotFONT (const string &face, int size, RGB fg, RGB bg)
{
  commonTagHandler();

  mxpResult *res = results->createFormatting (USE_FG | USE_BG | USE_FONT | USE_SIZE, 0, fg, bg,
      face, size);
  mxpResult *res2 = createClosingResult (res);
  applyResult (res);
  results->addToList (res);
  addClosingTag ("font", res2);

  commonAfterTagHandler();
}

//line spacing

void cMXPState::gotNOBR ()
{
  commonTagHandler();

  //next new-line is to be ignored
  ignoreNextNewLine = true;

  //no reporting to client

  commonAfterTagHandler();
}

void cMXPState::gotP ()
{
  commonTagHandler();

  //we're now in a paragraph
  inParagraph = true;

  addClosingTag ("p");
  
  //no reporting to the client

  commonAfterTagHandler();
}

void cMXPState::gotBR ()
{
  commonTagHandler();

  //inform the client that we got a newline (but no mode changes shall occur)
  results->addToList (results->createText ("\r\n"));

  commonAfterTagHandler();
}

void cMXPState::gotSBR ()
{
  commonTagHandler();

  //soft-break is represented as 0x1F
  results->addToList (results->createText ("\x1f"));

  commonAfterTagHandler();
}

//links

void cMXPState::gotA (const string &href, const string &hint, const string &expire)
{
  commonTagHandler();

  inLink = true;
  isALink = true;
  linkText = "";
  mxpResult *res = results->createLink (expire, href, "", hint);

  addClosingTag ("a", res);

  commonAfterTagHandler();
}

void cMXPState::gotSEND (const string &command, const string &hint, bool prompt, const string &expire)
{
  commonTagHandler();

  inLink = true;
  isALink = false;
  linkText = "";
  gotmap = false;
  lastcmd = command;
  mxpResult *res = results->createSendLink (expire, command, "", hint, prompt,
      (command.find ("|") == string::npos) ? false : true);

  addClosingTag ("send", res);

  commonAfterTagHandler();
}

void cMXPState::gotEXPIRE (const string &name)
{
  commonTagHandler();

  results->addToList (results->createExpire (name));

  commonAfterTagHandler();
}

//version control

void cMXPState::gotVERSION ()
{
  commonTagHandler();

  //this is to be sent...
  results->addToList (results->createSendThis ("\x1b[1z<VERSION MXP=" + mxpVersion + " CLIENT=" +
      clientName + " VERSION=" + clientVersion + ">\r\n"));

  commonAfterTagHandler();
}

void cMXPState::gotSUPPORT (list<string> params)
{
  commonTagHandler();

  if (!params.empty())  //some parameters - this is not supported at the moment
    results->addToList (results->createWarning (
        "Received <support> with parameters, but this isn't supported yet..."));
    
  string res;
  res = "\x1b[1z<SUPPORTS +!element +!attlist +!entity +var +b +i +u +s +c +h +font";
  res += " +nobr +p +br +sbr +version +support +h1 +h2 +h3 +h4 +h5 +h6 +hr +small +tt";
  if (suplink)
    res += " +a +send +expire";
  if (supgauge)
    res += " +gauge";
  if (supstatus)
    res += " +status";
  if (supsound)
    res += " +sound +music";
  if (supframe)
    res += " +frame +dest";
  if (supimage)
    res += " +image";
  if (suprelocate)
    res += " +relocate +user +password";
  res += ">\r\n";
  results->addToList (results->createSendThis (res));
  
  commonAfterTagHandler();
}

//optional tags go next

//other HTML tags

void cMXPState::gotHtag (int which)
{
  if ((which < 1) || (which > 6)) //BUG!!!
  {
    commonAfterTagHandler();
    return;
  }

  commonTagHandler();

  int idx = which - 1;
  mxpResult *res = results->createFormatting (USE_ALL, Hattribs[idx], Hfg[idx], Hbg[idx],
      Hfont[idx], Hsize[idx]);
  mxpResult *res2 = createClosingResult (res);
  applyResult (res);
  results->addToList (res);
  char ct[3];
  ct[0] = 'h';
  ct[1] = '1' + idx;
  ct[2] = '\0';
  addClosingTag (ct, res2);

  commonAfterTagHandler();
}

void cMXPState::gotHR ()
{
  commonTagHandler();

  results->addToList (results->createHorizLine ());

  commonAfterTagHandler();
}

void cMXPState::gotSMALL ()
{
  commonTagHandler();

  //SMALL means 3/4 of standard size :)
  mxpResult *res = results->createFormatting (USE_SIZE, 0, cMXPColors::noColor(),
    cMXPColors::noColor(), "", defaultsize * 3/4);
  mxpResult *res2 = createClosingResult (res);
  applyResult (res);
  results->addToList (res);
  addClosingTag ("small", res2);

  commonAfterTagHandler();
}

void cMXPState::gotTT ()
{
  commonTagHandler();

  mxpResult *res = results->createFormatting (USE_FONT, 0, cMXPColors::noColor(), cMXPColors::noColor(), ttFont, 0);
  mxpResult *res2 = createClosingResult (res);
  applyResult (res);
  results->addToList (res);
  addClosingTag ("tt", res2);

  commonAfterTagHandler();
}

//MSP compatibility

void cMXPState::gotSOUND (const string &fname, int vol, int count, int priority,
    const string &type, const string &url)
{
  commonTagHandler();

  results->addToList (results->createSound (true, fname, vol, count, priority, false, type, url));

  commonAfterTagHandler();
}

void cMXPState::gotMUSIC (const string &fname, int vol, int count, bool contifrereq,
    const string &type, const string &url)
{
  commonTagHandler();

  results->addToList (results->createSound (false, fname, vol, count, 0, contifrereq, type, url));

  commonAfterTagHandler();
}

//gauges / status bars

void cMXPState::gotGAUGE (const string &entity, const string &maxentity, const string &caption,
    RGB color)
{
  commonTagHandler();

  results->addToList (results->createGauge (entity, maxentity, caption, color));

  commonAfterTagHandler();
}

void cMXPState::gotSTAT (const string &entity, const string &maxentity, const string &caption)
{
  commonTagHandler();

  results->addToList (results->createStat (entity, maxentity, caption));

  commonAfterTagHandler();
}

//frames and cursor control

void cMXPState::gotFRAME (const string &name, const string &action, const string &title,
    bool internal, const string &align, int left, int top, int width, int height,
    bool scrolling, bool floating)
{
  commonTagHandler();

  if (name.empty())
  {
    results->addToList (results->createError ("Got FRAME tag without frame name!"));
    commonAfterTagHandler();
    return;
  }

  string nm = lcase (name);
  string act = lcase (action);
  string alg = lcase (align);

  string tt = title;
  //name is the default title
  if (tt.empty())
    tt = name;
   
  //align
  alignType at = Top;
  if (!align.empty())
  {
    bool alignok = false;
    if (align == "left") { at = Left; alignok = true; }
    if (align == "right") { at = Right; alignok = true; }
    if (align == "top") { at = Top; alignok = true; }
    if (align == "bottom") { at = Bottom; alignok = true; }
    if (!alignok)
      results->addToList (results->createError ("Received FRAME tag with unknown ALIGN option!"));
  }

  //does the list of frames contain frame with name nm?
  bool nmExists = (frames.count (nm) != 0);
  
  if (act == "open")
  {
    if (nmExists)
    {
      results->addToList (results->createError ("Received request to create an existing frame!"));
      commonAfterTagHandler();
      return;
    }
    //cannot create _top or _previous
    if ((nm == "_top") || (nm == "_previous"))
    {
      results->addToList (results->createError ("Received request to create a frame with name " +
          nm + ", which is invalid!"));
      commonAfterTagHandler();
      return;
    }
    if (internal)
    {
      //false for internal windows... value not used as of now, but it may be used later...
      frames[nm] = false;
      results->addToList (results->createInternalWindow (nm, tt, at, scrolling));
    }
    else
    {
      //true for normal windows... value not used as of now, but it may be used later...
      frames[nm] = true;
      results->addToList (results->createWindow (nm, tt, left, top, width, height,
          scrolling, floating));
    }
  }
  if (act == "close")
  {
    if (nmExists)
    {
      frames.erase (nm);
      results->addToList (results->createCloseWindow (nm));
    }
    else
      results->addToList (results->createError
          ("Received request to close a non-existing frame!"));
  }
  if (act == "redirect")
  {
    //if the frame exists, or if the name is either _top or _previous, we redirect to that window
    if ((nm == "_top") || (nm == "_previous") || nmExists)
      redirectTo (nm);

    else
    {
      //create that window
      if (internal)
      {
        //false for internal windows... value not used as of now, but it may be used later...
        frames[nm] = false;
        results->addToList (results->createInternalWindow (nm, tt, at, scrolling));
      }
      else
      {
        //true for normal windows... value not used as of now, but it may be used later...
        frames[nm] = true;
        results->addToList (results->createWindow (nm, tt, left, top, width, height,
            scrolling, floating));
      }
      //then redirect to it
      redirectTo (nm);
    }
  }

  commonAfterTagHandler();
}

void cMXPState::redirectTo (const string &name)
{
  string nm = lcase (name);
  
  string emptystring;
  mxpResult *res = 0;
  if (nm == "_top")
    res = results->createSetWindow (emptystring);
  else
  if (nm == "_previous")
    res = results->createSetWindow (prevWindow);
  else
    if (frames.count (nm))
      res = results->createSetWindow (nm);
    else
      res = results->createError ("Received request to redirect to non-existing window " + nm);
  //apply result - will update info about previous window and so...
  applyResult (res);
  results->addToList (res);
}

void cMXPState::gotDEST (const string &name, int x, int y, bool eol, bool eof)
{
  commonTagHandler();

  string nm = lcase (name);
  bool nmExists = (frames.count (nm) != 0);
  
  if (!nmExists)
  {
    results->addToList (results->createError ("Received a request to redirect to non-existing window " + nm));
    return;
  }
  
  mxpResult *res = results->createSetWindow (name);
  mxpResult *res2 = createClosingResult (res);
  applyResult (res);
  results->addToList (res);

  int _x = x;
  int _y = y;
  if ((y >= 0) && (x < 0)) _x = 0;
  if ((_x >= 0) && (_y >= 0))
    results->addToList (results->createMoveCursor (_x, _y));

  list<mxpResult *> *ls = 0;
  //erase AFTER displaying text
  if (eol || eof)
  {
    ls = new list<mxpResult *>;
    ls->push_back (res2);
    res2 = results->createEraseText (eof);
  }

  //closing tag...
  addClosingTag ("dest", res2, ls);

  commonAfterTagHandler();
}

//crosslinking servers

void cMXPState::gotRELOCATE (const string &hostname, int port)
{
  commonTagHandler();

  results->addToList (results->createRelocate (hostname, port));

  commonAfterTagHandler();
}

void cMXPState::gotUSER ()
{
  commonTagHandler();

  results->addToList (results->createSendLogin (true));

  commonAfterTagHandler();
}

void cMXPState::gotPASSWORD ()
{
  commonTagHandler();

  results->addToList (results->createSendLogin (false));

  commonAfterTagHandler();
}

//images

void cMXPState::gotIMAGE (const string &fname, const string &url, const string &type, int height,
    int width, int hspace, int vspace, const string &align, bool ismap)
{
  commonTagHandler();

  //align
  string alg = lcase (align);
  alignType at = Top;
  if (!align.empty())
  {
    bool alignok = false;
    if (align == "left") { at = Left; alignok = true; }
    if (align == "right") { at = Right; alignok = true; }
    if (align == "top") { at = Top; alignok = true; }
    if (align == "bottom") { at = Bottom; alignok = true; }
    if (align == "middle") { at = Middle; alignok = true; }
    if (!alignok)
      results->addToList (results->createError ("Received IMAGE tag with unknown ALIGN option!"));
  }

  if (gotmap)
    results->addToList (results->createError ("Received multiple image maps in one SEND tag!"));

  if (ismap)
  {
    if (inLink && (!isALink))
    {
      results->addToList (results->createImageMap (lastcmd));
      lastcmd = "";
      gotmap = true;
    }
    else
      results->addToList (results->createError ("Received an image map with no SEND tag!"));
  }
  results->addToList (results->createImage (fname, url, type, height, width, hspace, vspace, at));

  commonAfterTagHandler();
}


//closing tags

void cMXPState::gotClosingTag (const string &name)
{
  string nm = lcase (name);
  //hack, to prevent an error from being reported when </var> or end-of-flag comes
  //we cannot simply test for </var> and friends and disable it then, because
  //we could have the var tag inside some element
  bool oldInVar = inVar;
  inVar = false;
  
  commonTagHandler();
  
  //restore the inVar variable...
  inVar = oldInVar;

  bool okay = false;
  while (!okay)
  {
    if (closingTags.empty())
      break;  //last one closed...
    //closingTags is a FIFO queue, tho technically it's a list
    closingTag *tag = closingTags.back ();
    closingTags.pop_back ();

    if (tag->name == nm)
      okay = true;  //good
    else
      results->addToList (results->createWarning ("Had to auto-close tag " + tag->name +
          ", because closing tag </" + name + "> was received."));
    
    closeTag (tag);
  }

  if (!okay)
    results->addToList (results->createError ("Received unpaired closing tag </" + name + ">."));

  commonAfterTagHandler();
}

void cMXPState::closeTag (closingTag *tag)
{
  //some tags need special handling...
  if (tag->name == "p")
  {
    inParagraph = false;
    ignoreNextNewLine = false;
    //also send a newline after end of paragraph... MXP docs say nothing about this :(
    results->addToList (results->createText ("\r\n"));
  }
  if (tag->name == "var")
  {
    tag->closingresult = 0;
    tag->closingresults = 0;
    results->addToList (results->createVariable (varName, varValue));
    results->addToList (results->createText (varName + ": " + varValue));
    entities->addEntity (varName, varValue);
    inVar = false;
    varName = "";
    varValue = "";
  }
  if (tag->name == "a")
  {
    if (inLink && isALink)
    {
      // !!! SOME LOW-LEVEL MANIPULATIONS HERE !!!
      
      linkStruct *ls = (linkStruct *) tag->closingresult->data;
      //assign text, using URL if no text given
      string lt = linkText.empty() ? (ls->url ? ls->url : "") : linkText;
      ls->text = new char[lt.length() + 1];
      ls->text[0] = '\0';
      if (lt.length())
        strcpy (ls->text, lt.c_str());
    }
    else
      //this should never happen
      results->addToList (results->createError ("Received </A> tag, but I'm not in a link!"));
    linkText = "";
    inLink = false;
    isALink = false;
  }
  if (tag->name == "send")
  {
    if (gotmap)
    {
      //don't send this closing result
      results->deleteResult (tag->closingresult);
      tag->closingresult = 0;
      
      if (!linkText.empty())
        results->addToList (results->createError
            ("Received image map and a command in one SEND tag!"));
    }
    else if (inLink && (!isALink))
    {
      // !!! SOME LOW-LEVEL MANIPULATIONS HERE !!!
      
      sendStruct *ss = (sendStruct *) tag->closingresult->data;
      //assign text, also assign to command if none given
      
      //assign linkText to ss->text
      delete[] ss->text;
      ss->text = new char[linkText.length() + 1];
      strcpy (ss->text, linkText.c_str());
      
      if (ss->hint)
      {
        //expand &text; in hint
        string hint = ss->hint;

        bool found = true, havematch = false;
        while (found)
        {
          int p = hint.find ("&text;");
          if (p < hint.length())   //found it
          {
            //replace it...
            hint.replace (p, 6, linkText);
            havematch = true;
          }
          else
            found = false;  //no more matches
        }
        if (havematch)  //apply changes if needed
        {
          //assign hint to ss->hint
          delete[] ss->hint;
          ss->hint = new char[hint.length() + 1];
          strcpy (ss->hint, hint.c_str());
        }
      }
      if (ss->command)
      {
        string cmd = ss->command;
        //also expand &text; in href
        
        bool found = true, havematch = false;
        while (found)
        {
          int p = cmd.find ("&text;");
          if (p < cmd.length())   //found it
          {
            //replace it...
            cmd.replace (p, 6, linkText);
            havematch = true;
          }
          else
            found = false;  //no more matches
        }
        if (havematch)  //apply changes if needed
        {
          //assign cmd to ss->command
          delete[] ss->command;
          ss->command = new char[cmd.length() + 1];
          strcpy (ss->command, cmd.c_str());
        }
      }
      else if (!linkText.empty())
      {
        //assign linkText to ss->command
        ss->command = new char[linkText.length() + 1];
        strcpy (ss->command, linkText.c_str());
      }
    }
    else
      //this should never happen
      results->addToList (results->createError ("Received </SEND> tag, but I'm not in a link!"));

    linkText = "";
    inLink = false;
    isALink = false;
    gotmap = false;
  }

  //handle applying/sending of closing results, is any
  if (tag->closingresult)
  {
    //apply result, reverting changes made by opening tag
    applyResult (tag->closingresult);
    //and send the changes to the client app
    results->addToList (tag->closingresult);
  }
  if (tag->closingresults)
  {
    //the same for remaining closing tags...
    list<mxpResult *>::iterator it;
    for (it = tag->closingresults->begin(); it != tag->closingresults->end(); ++it)
    {
      applyResult (*it);
      results->addToList (*it);
    }
  }
  //finally, the closing tag gets deleted
  //note that this won't delete the results themselves - they will be deleted after
  //they are processed by the client app
  delete tag->closingresults;
  tag->closingresults = 0;
  delete tag;
}


//mxpResult handling

mxpResult *cMXPState::createClosingResult (mxpResult *what)
{
  mxpResult *res = 0;
  switch (what->type) {
    case 3: {
      flagStruct *fs = (flagStruct *) what->data;
      res = results->createFlag (false, fs->name);
      break;
    }
    case 5: {
      formatStruct *fs = (formatStruct *) what->data;
      //usemask is the most relevant thing here - things not enabled there won't be applied,
      //so we can place anything there
      int usemask = fs->usemask;
      char curattrib = (bold?1:0) * Bold + (italic?1:0) * Italic +
          (underline?1:0) * Underline + (strikeout?1:0) * Strikeout;
      string font;
      if (usemask & USE_FONT)
        font = curfont;
      res = results->createFormatting (usemask, curattrib, fgcolor, bgcolor, font, cursize);
      break;
    }
    case 15: {
      res = results->createSetWindow (curWindow);
      break;
    }
  };
  return res;
}

void cMXPState::applyResult (mxpResult *what)
{
  switch (what->type) {
    case 5: {
      formatStruct *fs = (formatStruct *) what->data;
      int usemask = fs->usemask;
      if (usemask & USE_BOLD)
        bold = fs->attributes & Bold;
      if (usemask & USE_ITALICS)
        italic = fs->attributes & Italic;
      if (usemask & USE_UNDERLINE)
        underline = fs->attributes & Underline;
      if (usemask & USE_STRIKEOUT)
        strikeout = fs->attributes & Strikeout;
      if (usemask & USE_FG)
        fgcolor = fs->fg;
      if (usemask & USE_BG)
        bgcolor = fs->bg;
      if (usemask & USE_FONT)
        curfont = fs->font;
      if (usemask & USE_SIZE)
        cursize = fs->size;
      break;
    }
    case 15: {
      prevWindow = curWindow;
      if (what->data)
        curWindow = (char *) what->data;
      else
        curWindow = "";
      break;
    };

  };
}

void cMXPState::addClosingTag (const string &name, mxpResult *res, list<mxpResult *> *res2)
{
  closingTag *ctag = new closingTag;
  ctag->name = name;
  ctag->closingresult = res;
  ctag->closingresults = res2;
  closingTags.push_back (ctag);
}

void cMXPState::setScreenProps (int sx, int sy, int wx, int wy, int fx, int fy)
{
  sX = sx;
  sY = sy;
  wX = wx;
  wY = wy;
  fX = fx;
  fY = fy;
}

int cMXPState::computeCoord (const string &coord, bool isX, bool inWindow)
{
  int retval = atoi (coord.c_str());
  int len = coord.length();
  char ch = coord[len - 1];
  if (ch == 'c') retval *= (isX ? fX : fY);
  if (ch == '%') retval = retval * (inWindow ? (isX ? wX : wY) : (isX ? sX : sY)) / 100;
  return retval;
}



syntax highlighted by Code2HTML, v. 0.9.1