#include "stdafx.h"
#include "mainfrm.h"
#include "MUSHclient.h"
#include "doc.h"
#include "LuaInputBox.h"
#include "LuaInputEditDlg.h"
#include "SpellCheckDlg.h"
#include "LuaChooseBox.h"
#include "LuaChooseList.h"
#include "LuaChooseListMulti.h"
#include "dmetaph.h"
#include <direct.h>

#ifdef USE_LUA


// recursive node outputter
static void xmlOuputNode (lua_State *L, CXMLelement & node) 
  {
  POSITION pos;

  lua_newtable(L);    // table of this node

  MakeTableItem (L, "name", node.strName);    // node name, eg. <trigger>
  MakeTableItem (L, "content", node.strContent);  // content (inside element)
  if (node.bEmpty)
    MakeTableItemBool (L, "empty", node.bEmpty);  // empty, eg. <br/>
  MakeTableItem (L, "line", node.iLine);      // line it appeared on

  if (!node.AttributeMap.IsEmpty ())
    {
    CString strName;
    CAttribute * attribute;

    lua_pushliteral (L, "attributes");
    lua_newtable(L);    // table of attributes

    // attributes
    for (pos = node.AttributeMap.GetStartPosition(); pos; )
      {
      node.AttributeMap.GetNextAssoc (pos, strName, attribute);
      MakeTableItem (L, strName, attribute->strValue);    // attribute name -> value
      }      // end of checking each attribute

    lua_rawset(L, -3);              // bind attributes table to "attributes" entry
    }   // end of having attributes

  if (!node.ChildrenList.IsEmpty ())
    {
    lua_pushliteral (L, "nodes");
    lua_newtable(L);    // table of attributes

    int item = 1;

    // children
    for (pos = node.ChildrenList.GetHeadPosition (); pos; item++)
      {
      CXMLelement * pElement = node.ChildrenList.GetNext (pos);

      xmlOuputNode (L, *pElement);
      lua_rawseti (L, -2, item);   // number each node in sequence
      } // end of checking each tag

    luaL_setn (L, -1, item - 1);   // set table size now

    lua_rawset(L, -3);              // bind children table to "children" entry
    } // end of having children

  }

static int xmlread (lua_State *L) 
  {

// get xml text
size_t xmlLength;
const char * xml = luaL_checklstring (L, 1, &xmlLength);

CXMLparser parser;
CXMLelement * pMuClientElement = NULL;

  try
    {

    CMemFile f ((unsigned char *) xml, xmlLength);
    CArchive ar (&f, CArchive::load);

    parser.BuildStructure (ar.GetFile ());

    } // end of try block
  catch (CException* e) 
    {
    char sMessage [1000];
    e->GetErrorMessage (sMessage, sizeof sMessage);
    e->Delete ();
    lua_pushnil (L);     // nil indicates error
    lua_pushstring (L, sMessage); // nature of error
    lua_pushnumber (L, parser.m_xmlLine);  // which line it occurred on
    return 3;
    }

  int iResults = 2;

  // output root node
  xmlOuputNode (L, parser.m_xmlRoot);

  lua_pushstring (L, parser.m_strDocumentName); // root document name

  if (!parser.m_CustomEntityMap.IsEmpty ())
    {
    lua_newtable(L);    // table of custom entities
    iResults++;

    CString strName, strValue;

    // entities
    for (POSITION pos = parser.m_CustomEntityMap.GetStartPosition(); pos; )
      {
      parser.m_CustomEntityMap.GetNextAssoc (pos, strName, strValue);
      MakeTableItem (L, strName, strValue);    // entity name -> value
      }      // end of doing each entity

    }

  return iResults; // root node, plus document name, and maybe entities table
  } // end of  xmlread


// message-box display routine
// arg1 = message to display
// arg2 = title of box - if nil, defaults to "MUSHclient"
// arg3 = type of box: ok, abortretryignore, okcancel, retrycancel, yesno, yesnocancel
// arg4 = type of icon: ?, !, i, .
// arg5 = default button (1 - 3)
//
// returns: yes, no, ok, retry, ignore, cancel, abort, other 

static int msgbox (lua_State *L) 
  {
  const char * boxmsg   = luaL_checkstring (L, 1);
  const char * boxtitle = luaL_optstring (L, 2, "MUSHclient");
  const char * boxtype  = luaL_optstring (L, 3, "ok");
  const char * boxicon  = luaL_optstring (L, 4, "!");
  const int boxdefault  = luaL_optnumber (L, 5, 1);

  if (strlen (boxmsg) > 1000)
     luaL_error (L, "msgbox message too long (max 1000 characters)");

  if (strlen (boxtitle) > 100)
     luaL_error (L, "msgbox title too long (max 100 characters)");

  int msgboxtype = MB_APPLMODAL;  

  // type of box
  if (strcmp (boxtype, "ok") == 0)
    msgboxtype = MB_OK;
  else if (strcmp (boxtype, "abortretryignore") == 0)
    msgboxtype = MB_ABORTRETRYIGNORE;
  else if (strcmp (boxtype, "okcancel") == 0)
    msgboxtype = MB_OKCANCEL;
  else if (strcmp (boxtype, "retrycancel") == 0)
    msgboxtype = MB_RETRYCANCEL;
  else if (strcmp (boxtype, "yesno") == 0)
    msgboxtype = MB_YESNO;
  else if (strcmp (boxtype, "yesnocancel") == 0)
    msgboxtype = MB_YESNOCANCEL;
  else
    luaL_error (L, "msgbox type unknown");

  // icon

  switch (tolower (boxicon [0]))
    {
    case '?' : msgboxtype |= MB_ICONQUESTION;     break;
    case '!' : msgboxtype |= MB_ICONEXCLAMATION;  break;
    case 'i' : msgboxtype |= MB_ICONINFORMATION;  break;
    case '.' : msgboxtype |= MB_ICONSTOP;         break;
    default  : luaL_error (L, "msgbox icon unknown");
   // default is ?
    } // end of switch on icon

  // default button

  switch (boxdefault)
    {
    case 1:    msgboxtype |= MB_DEFBUTTON1; break;
    case 2:    msgboxtype |= MB_DEFBUTTON2; break;
    case 3:    msgboxtype |= MB_DEFBUTTON3; break;
    default:   luaL_error (L, "msgbox default button must be 1, 2 or 3");
    }

  int result = ::MessageBox (Frame, boxmsg, boxtitle, msgboxtype);

  char * p;

  switch (result)
    {
    case IDYES:     p = "yes";    break;
    case IDNO:      p = "no";     break;
    case IDOK:      p = "ok";     break;
    case IDRETRY:   p = "retry";  break;
    case IDIGNORE:  p = "ignore"; break;
    case IDCANCEL:  p = "cancel"; break;
    case IDABORT:   p = "abort";  break;
    default:        p = "other";  break;
    } // end of switch

  lua_pushstring (L, p);

  return 1;
} // end of msgbox


// input-box display routine
// arg1 = message to display
// arg2 = title of box - if nil, defaults to "MUSHclient"
// arg3 = default text
//
// returns: what they typed, or nil if cancelled

template <typename T>
static int gen_inputbox (lua_State *L, T & msg) 
  {
  const char * inputmsg     = luaL_checkstring (L, 1);
  const char * inputtitle   = luaL_optstring (L, 2, "MUSHclient");
  const char * inputdefault = luaL_optstring (L, 3, "");
  const char * inputfont    = luaL_optstring (L, 4, "");
  const int    inputsize    = luaL_optnumber (L, 5, 9);

  // if we leave in & it will make the next letter underlined
  string sInputMsg = FindAndReplace (inputmsg, "&", "&&");

  if (sInputMsg.length () > 1000)
     luaL_error (L, "inputbox message too long (max 1000 characters)");

  if (strlen (inputtitle) > 100)
     luaL_error (L, "inputbox title too long (max 100 characters)");

  msg.m_strMessage  = sInputMsg.c_str ();
  msg.m_strTitle    = inputtitle;
  msg.m_strReply    = inputdefault;
  msg.m_strFont     = inputfont;
  msg.m_iFontSize   = inputsize;

  if (msg.DoModal () != IDOK)
    lua_pushnil (L);
  else
    lua_pushstring (L, msg.m_strReply);

  return 1;
} // end of gen_inputbox

static int inputbox (lua_State *L) 
  {
  CLuaInputBox msg;

  return gen_inputbox (L, msg);
  }  // end of inputbox

static int editbox (lua_State *L) 
  {
  CLuaInputEditDlg msg;

  return gen_inputbox (L, msg);
  }  // end of editbox

// combo-box choose routine
// arg1 = message to display
// arg2 = title of box - if nil, defaults to "MUSHclient"
// arg3 = table of key/value pairs (value is displayed)
// arg4 = default *key*
//
// returns: chosen key, or nil if: cancelled, or none selected

template <typename T>
static int gen_choose (lua_State *L, T & msg) 
  {
  const char * choosemsg     = luaL_checkstring (L, 1);
  const char * choosetitle   = luaL_optstring (L, 2, "MUSHclient");
  // arg3 is table
  // arg4 is default

  bool bDefaultIsNumber = lua_type (L, 4) == LUA_TNUMBER;
  bool bHaveDefault = lua_gettop (L) >= 4 && !lua_isnil (L, 4);

  string defaultstring;
  lua_Number defaultnumber = 0;

  if (bHaveDefault)
    {
    if (!lua_isstring (L, 4))
      luaL_error (L, "default key must be string or number");
    if (bDefaultIsNumber)
      defaultnumber = lua_tonumber (L, 4);
    else
      defaultstring = lua_tostring (L, 4);
    }

  // if we leave in & it will make the next letter underlined
  string sChooseMsg = FindAndReplace (choosemsg, "&", "&&");

  if (sChooseMsg.length () > 1000)
     luaL_error (L, "message too long (max 1000 characters)");

  if (strlen (choosetitle) > 100)
     luaL_error (L, "title too long (max 100 characters)");

  if (!lua_istable (L, 3))
     luaL_error (L, "must have table of choices as 3rd argument");

  msg.m_strMessage  = sChooseMsg.c_str ();
  msg.m_strTitle    = choosetitle;

  lua_pushnil (L);  // first key for traversal

  while (lua_next (L, 3) != 0)
    {
    if (!lua_isstring (L, -2))
      luaL_error (L, "table must have string or number keys");

    if (!lua_isstring (L, -1))
      luaL_error (L, "table must have string or number values");

    // value can simply be converted to a string
    string sValue = lua_tostring (L, -1);

    // get key
    CKeyValuePair kv (sValue);
    
    if (lua_type (L, -2) == LUA_TSTRING)
      {
      kv.sKey_ = lua_tostring (L, -2);
      // remember default position
      if (bHaveDefault && 
          !bDefaultIsNumber && 
          kv.sKey_ == defaultstring)
          msg.m_iDefault = msg.m_data.size ();
      }
    else
      {     // not string, should be number :)
      // cannot do lua_tostring because that confuses lua_next
      kv.bNumber_ = true;
      kv.iKey_ = lua_tonumber (L, -2);
      // remember default position
      if (bHaveDefault && 
          bDefaultIsNumber && 
          kv.iKey_ == defaultnumber)
          msg.m_iDefault = msg.m_data.size ();
      }   // end of key being a number


    msg.m_data.push_back (kv);

    lua_pop (L, 1); // remove value, keep key for next iteration

    } // end of looping through table


  if (msg.DoModal () != IDOK)
    lua_pushnil (L);
  else
    {  // not cancelled
    if (msg.m_iResult == CB_ERR)
      lua_pushnil (L);   // no choice made
    else
      {    // get key of choice
      if (msg.m_data [msg.m_iResult].bNumber_)
        lua_pushnumber (L, msg.m_data [msg.m_iResult].iKey_);
      else
        lua_pushstring (L, msg.m_data [msg.m_iResult].sKey_.c_str ()); 
      }
    }
  
  return 1;   // 1 result

} // end of gen_choose

static int choose (lua_State *L) 
  {
  CLuaChooseBox msg;
  return gen_choose (L, msg);   // templated function above
  }  // end of choose

static int listbox (lua_State *L) 
  {
  CLuaChooseList msg;
  return gen_choose (L, msg);   // templated function above
  }  // end of listbox


// combo-box choose routine
// arg1 = message to display
// arg2 = title of box - if nil, defaults to "MUSHclient"
// arg3 = table of key/value pairs (value is displayed)
// arg4 = default *key*
//
// returns: chosen key, or nil if: cancelled, or none selected

static int multilistbox (lua_State *L) 
  {
  CLuaChooseListMulti msg;

  const char * choosemsg     = luaL_checkstring (L, 1);
  const char * choosetitle   = luaL_optstring (L, 2, "MUSHclient");
  // arg3 is table
  // arg4 is table of defaults

  bool bHaveDefaults = lua_gettop (L) >= 4 && !lua_isnil (L, 4);

  if (bHaveDefaults)
    {
    if (!lua_istable (L, 4))
      luaL_error (L, "defaults must be a table, or nil");
    }

  // if we leave in & it will make the next letter underlined
  string sChooseMsg = FindAndReplace (choosemsg, "&", "&&");

  if (sChooseMsg.length () > 1000)
     luaL_error (L, "message too long (max 1000 characters)");

  if (strlen (choosetitle) > 100)
     luaL_error (L, "title too long (max 100 characters)");

  if (!lua_istable (L, 3))
     luaL_error (L, "must have table of choices as 3rd argument");

  msg.m_strMessage  = sChooseMsg.c_str ();
  msg.m_strTitle    = choosetitle;

  lua_pushnil (L);  // first key for traversal

  while (lua_next (L, 3) != 0)
    {
    if (!lua_isstring (L, -2))
      luaL_error (L, "table must have string or number keys");

    if (!lua_isstring (L, -1))
      luaL_error (L, "table must have string or number values");

    // value can simply be converted to a string
    string sValue = lua_tostring (L, -1);

    // get key
    CKeyValuePair kv (sValue);
    
    if (lua_type (L, -2) == LUA_TSTRING)
      {
      kv.sKey_ = lua_tostring (L, -2);
      }   // end string key
    else
      {     // not string, should be number :)
      kv.bNumber_ = true;
      kv.iKey_ = lua_tonumber (L, -2);
      }   // end of key being a number

    // remember default positions
    if (bHaveDefaults)
      {
      lua_pushvalue (L, -2);   // copy key to top of stack
      lua_gettable (L, 4);  // see if this key is in defaults table
      if (!(lua_isnil (L, -1) ||
            lua_isboolean (L, -1) &&  !lua_toboolean (L, -1))) 
        msg.m_iDefaults.insert (msg.m_data.size ());
      lua_pop (L, 1); // remove result of table-lookup
      }  // end of having a defaults table

    msg.m_data.push_back (kv);

    lua_pop (L, 1); // remove value, keep key for next iteration

    } // end of looping through table

  lua_settop (L, 0);    // get rid of stuff lying around

  if (msg.DoModal () != IDOK)
    lua_pushnil (L);
  else
    {  // not cancelled
    lua_newtable(L);    // table of results

    set<int>::const_iterator iter;

    for (iter = msg.m_iResults.begin ();
         iter != msg.m_iResults.end ();
         iter++)
           {
            if (msg.m_data [*iter].bNumber_)
              lua_pushnumber (L, msg.m_data [*iter].iKey_);
            else
              lua_pushstring (L, msg.m_data [*iter].sKey_.c_str ()); 
            lua_pushboolean (L, 1); // 
            lua_settable (L, -3); // add key = true to table
           }   // end of processing each result
    }
  
  return 1;   // 1 result

} // end of multilistbox


extern const char * sFunctions [1];

// returns table of internal functions
static int functionlist (lua_State *L) 
  {
  lua_newtable(L);    // table of function names

  for (int i = 0; sFunctions [i] [0]; i++)
    {
    lua_pushstring (L, sFunctions [i]);
    lua_rawseti(L, -2, i + 1);  // make 1-relative
    }

  luaL_setn (L, -1, i);   // set table size now
  return 1;   // 1 table
  } // end of functionlist


// file picker routine
// arg1 = title of dialog
// arg2 = default file name
// arg3 = default extension
// arg4 = filter table 
// arg5 = true to save, false or nil to load
//
// returns: chosen file name, or nil if: cancelled, or none selected

static int filepicker (lua_State *L) 
  {
  BOOL bLoad;  // true to load, false to save
  DWORD flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;

  const char * sTitle = luaL_optstring (L, 1, "");
  const char * sDefaultName = luaL_optstring (L, 2, "");
  const char * sDefaultExtension = luaL_optstring (L, 3, "");
  string sFilter;             // arg 4 = table
  int bSave = optboolean (L, 5, 0);

  if (bSave)
    {
    bLoad = false;
    }
  else
    { 
    bLoad = TRUE;
    flags |= OFN_FILEMUSTEXIST;
    }

  if (sTitle [0] == 0)
    sTitle = NULL; 

  if (sDefaultName [0] == 0)
    sDefaultName = NULL; 

  if (sDefaultExtension [0] == 0)
    sDefaultExtension = NULL; 


  if (lua_gettop (L) >= 4 && !lua_isnil (L, 4))
    {

    if (!lua_istable (L, 4))
      luaL_error (L, "argument 4 must be a table of filters, or nil");

    // construct filter

    lua_pushnil (L);  // first key for traversal

    while (lua_next (L, 4) != 0)
      {


      if (lua_type (L, -1) != LUA_TSTRING ||
          lua_type (L, -2) != LUA_TSTRING )
        luaL_error (L, "table of filters must be suffix/description pair");

      string sDescription = lua_tostring (L, -1);
      string sSuffix = lua_tostring (L, -2);

      sFilter += sDescription;
      sFilter += " (*.";
      sFilter += sSuffix;
      sFilter += ")|*.";
      sFilter += sSuffix;
      sFilter += "|";

      lua_pop (L, 1); // remove value, keep key for next iteration

      } // end of looping through table

    }      // end of not nil for argument 4

  sFilter += "|";

  CFileDialog dlg ( 
    bLoad,
    sDefaultExtension,
    sDefaultName,
    flags,
    sFilter.c_str (),
    NULL);    // parent window

  if (sTitle)
	  dlg.m_ofn.lpstrTitle = sTitle;

	if (dlg.DoModal() != IDOK)
		lua_pushnil (L);
  else
    lua_pushstring (L, dlg.GetPathName());

  return 1;

  } // end of filepicker

static int spellcheckdialog (lua_State *L) 
  {
  CSpellCheckDlg dlg;

  dlg.m_strMisspeltWord = luaL_checkstring (L, 1);

  if (lua_gettop (L) >= 2 && !lua_isnil (L, 2))
    {

    if (!lua_istable (L, 2))
      luaL_error (L, "argument 2 must be a table of suggestions, or nil");

    for (int i = 1; ; i++)
      {
      lua_rawgeti (L, 2, i);   // get i'th item
      if (lua_isnil (L, -1))
        break;    // first nil key, leave loop
      dlg.m_suggestions.push_back (luaL_checkstring (L, -1));
      lua_pop (L, 1); // remove value
      } // end of looping through table

    }      // end of not nil for argument 2

  if (dlg.DoModal () != IDOK)
    {
    lua_pushnil (L);
    return 1;
    }

  lua_pushstring (L, dlg.m_sAction.c_str ());
  lua_pushstring (L, dlg.m_strMisspeltWord);
  return 2;    
  } // end of spellcheckdialog


static int metaphone (lua_State *L) 
  {

  MString m (luaL_checkstring (L, 1), luaL_optnumber (L, 2, 4));

  CString str1, str2;

  m.DoubleMetaphone (str1, str2);

  lua_pushstring (L, str1);  // first result
  if (str2.IsEmpty ())
    lua_pushnil (L);
  else
    lua_pushstring (L, str2);  // second result

  return 2;
  }  // end of metaphone

static int info (lua_State *L) 
  {

  lua_newtable(L);    // table to hold this stuff

  char * p = _getcwd (NULL, 1024);
  if (p)
    {
    CString strPath (p);
    free (p);
    if (strPath.Right (1) != "\\")
      strPath += "\\"; // append trailing backslash
    MakeTableItem (L, "current_directory", strPath);
    }

  MakeTableItem (L, "app_directory", ExtractDirectory (App.m_strMUSHclientFileName));
  MakeTableItem (L, "world_files_directory", App.m_strDefaultWorldFileDirectory);
  MakeTableItem (L, "log_files_directory", App.m_strDefaultLogFileDirectory);
  MakeTableItem (L, "plugins_directory", App.m_strPluginsDirectory);

  return 1;
  }  // end of metaphone


int edit_distance (lua_State *L) 
  {
  lua_pushinteger (L, EditDistance (luaL_checkstring (L, 1), luaL_checkstring (L, 2)));
  return 1;
}   // end of edit_distance


// table of operations
static const struct luaL_reg xmllib [] = 
  {

  {"msgbox",        msgbox},
  {"inputbox",      inputbox},
  {"editbox",       editbox},
  {"choose",        choose},
  {"listbox",       listbox},
  {"multilistbox",  multilistbox},
  {"xmlread",       xmlread},
  {"functionlist",  functionlist},
  {"filepicker",    filepicker},
  {"metaphone",     metaphone},
  {"info",          info},
  {"spellcheckdialog", spellcheckdialog},
  {"edit_distance", edit_distance},

  {NULL, NULL}
  };

const struct luaL_reg *ptr_xmllib = xmllib;

#endif  // LUA
