/*
 * parse.cc
 *
 * Copyright (C) 1995-2000 Kenichi Kourai
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with qvwm; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xlocale.h>
#include "main.h"
#include "util.h"
#include "taskbar.h"
#include "icon.h"
#include "key.h"
#include "parse.h"
#include "hash.h"
#include "qvwmrc.h"
#include "qvwm.h"
#include "indicator.h"
#include "desktop.h"
#include "accessory.h"
#include "timer.h"

static Hash<FuncNumber>*	funcHashTable;
static Hash<VariableTable>*	varHashTable;

extern FILE*	yyin;
extern int 	yyparse();
extern int 	line;
extern char     filename[256];

void SetFont();
#ifdef __EMX__
static char* TranslateForOS2(char* name);
#endif

/*
 * ParseQvwmrc --
 *   Parse '.qvwmrc', 'system.qvwmrc', etc.
 */
void ParseQvwmrc(char* rcFileName)
{
  char *home, *rcPath;
  int i;

  // This hash table is used if not parsing
  Qvwm::appHashTable = new Hash<AppAttr>(HashTableSize);

  if (noParse) {
    DoAllSetting();  // minimum setting
    return;
  }

  /*
   * Initialize hash table.
   */
  funcHashTable = new Hash<FuncNumber>(HashTableSize);
  for (i = 0; i < FuncTableSize; i++)
    funcHashTable->SetHashItem(funcTable[i].name, &funcTable[i].num);

  varHashTable = new Hash<VariableTable>(HashTableSize);
  for (i = 0; i < VarTableSize; i++)
    varHashTable->SetHashItem(varTable[i].name, &varTable[i]);

/*
  // set YYDEBUG in yaccsrc.yy to 1 if debugging
  extern int yydebug;
  yydebug = 1;
*/

  if (rcFileName != NULL) {
#ifdef __EMX__
    if ((strchr(rcFileName, '/') == NULL) &&
	(strchr(rcFileName, '\\') == NULL))
      yyin = fopen(rcFileName, "r");
    else
      yyin = fopen(__XOS2RedirRoot((char*)rcFileName), "r");
    if (yyin != NULL) {
#else    
    if ((yyin = fopen(rcFileName, "r")) != NULL) {
#endif    
      line = 1;
      strncpy(filename, rcFileName, 255);
      filename[255] = '\0';

      yyparse();
      fclose(yyin);
    }
    else {
      QvwmError("Can't open rcfile '%s'", rcFileName);
      SetFont();
    }

    delete funcHashTable;
    delete varHashTable;

    return;
  }

  home = getenv("HOME");
  rcPath = new char[strlen(home)+9];
  sprintf(rcPath, "%s/.qvwmrc", home);

  if ((yyin = fopen(rcPath, "r")) != NULL) {
    line = 1;
    strncpy(filename, rcPath, 255);
    filename[255] = '\0';

    yyparse();
    fclose(yyin);
  }
  else {
    delete [] rcPath;
    rcPath = new char[strlen(QVWMDIR) + 15];
    sprintf(rcPath, "%s/system.qvwmrc", QVWMDIR);

#ifdef __EMX__
    if ((yyin = fopen(__XOS2RedirRoot(rcPath), "r")) != NULL) {
#else        
    if ((yyin = fopen(rcPath, "r")) != NULL) {
#endif    
      line = 1;
      strncpy(filename, rcPath, 255);
      filename[255] = '\0';

      yyparse();
      fclose(yyin);
    }
    else {
      QvwmError("Can't open file '%s'", rcPath);
      SetFont();
    }
  }

  delete [] rcPath;
  delete funcHashTable;
  delete varHashTable;
}

/*
 * AssignVariable --
 *   Assign a value to string variable.
 */
void AssignVariable(const char* var, char* str)
{
  VariableTable* vItem;

  if ((vItem = varHashTable->GetHashItem(var))) {
    ASSERT(vItem);
    switch (vItem->attr) {
    case F_STRING:
      {
	char** sVal = (char **)vItem->var;
	*sVal = str;
      }
      break;

    case F_NATURAL:
      {
	int val;

	if ((val = atoi(str)) >= 0) {
	  int* nVal = (int *)vItem->var;
	  *nVal = val;
	}
	else
	  QvwmError("%d: '%s' is not natural number", line, str);
      }
      break;
	
    case F_POSITIVE:
      {
	int val;

	if ((val = atoi(str)) > 0) {
	  int* nVal = (int *)vItem->var;
	  *nVal = val;
	}
	else
	  QvwmError("%d: '%s' is not positive number", line, str);
      }
      break;
	
    case F_FLOATING:
      {
	double val;

	if ((val = strtod(str, NULL)) > 0) {
	  double* nVal = (double *)vItem->var;
	  *nVal = val;
	}
	else
	  QvwmError("%d: '%s' is not floating point number", line, str);
      }
      break;
	
    case F_COLOR:
      {
	XColor color;
	XColor* pVal = (XColor *)vItem->var;

	if (strcmp(str, "qvgray") == 0)
	  *pVal = gray;
	else if (strcmp(str, "qvdarkgray") == 0)
	  *pVal = darkGray;
	else if (strcmp(str, "qvlightgray") == 0)
	  *pVal = lightGray;
	else if (strcmp(str, "qvgrey") == 0)  // not qvgray!
	  *pVal = grey;
	else if (strcmp(str, "qvblue") == 0)
	  *pVal = blue;
	else if (strcmp(str, "qvlightblue") == 0)
	  *pVal = lightBlue;
	else if (strcmp(str, "qvgreen") == 0)
	  *pVal = green;
	else if (strcmp(str, "qvyellow") == 0)
	  *pVal = yellow;
	else if (XParseColor(display, colormap, str, &color) != 0) {
	  XAllocColor(display, colormap, &color);
	  *pVal = color;
	}
	else
	  QvwmError("%d: '%s' is invalid color", line, str);
      }
      break;

    case F_BOOL:
      {
	int* nVal = (int *)vItem->var;

	if (strcmp(str, "True") == 0)
	  *nVal = 1;
	else if (strcmp(str, "False") == 0)
	  *nVal = 0;
	else
	  QvwmError("%d: '%s' is not boolean", line, str);
      }
      break;

    case F_TASKBAR:
      {
	Taskbar::TaskbarPos* tVal = (Taskbar::TaskbarPos *)vItem->var;

	if (strcmp(str, "Bottom") == 0)
	  *tVal = Taskbar::BOTTOM;
	else if (strcmp(str, "Top") == 0)
	  *tVal = Taskbar::TOP;
	else if (strcmp(str, "Left") == 0)
	  *tVal = Taskbar::LEFT;
	else if (strcmp(str, "Right") == 0)
	  *tVal = Taskbar::RIGHT;
	else
	  QvwmError("%d: '%s' is not a taskbar position", line, str);
      }
      break;

    case F_GRADSTYLE:
      {
	Qvwm::GradStyle* tVal = (Qvwm::GradStyle *)vItem->var;

	if (strcmp(str, "Normal") == 0)
	  *tVal = Qvwm::Normal;
	else if (strcmp(str, "TopToBottom") == 0)
	  *tVal = Qvwm::TopToBottom;
	else if (strcmp(str, "LeftToRight") == 0)
	  *tVal = Qvwm::LeftToRight;
	else if (strcmp(str, "CenterToTopBottom") == 0)
	  *tVal = Qvwm::CenterToTopBottom;
	else if (strcmp(str, "CenterToLeftRight") == 0)
	  *tVal = Qvwm::CenterToLeftRight;
	else if (strcmp(str, "CenterToAll") == 0)
	  *tVal = Qvwm::CenterToAll;
	else
	  QvwmError("%d: '%s' is not a grad window style", line, str);
      }
      break;
	
    case F_GEOMETRY:
      {
	InternGeom ig, *iVal = (InternGeom *)vItem->var;
	int bitmask;

	bitmask = XParseGeometry(str, &ig.rc.x, &ig.rc.y,
				 (unsigned int *)&ig.rc.width,
				 (unsigned int *)&ig.rc.height);
	if ((bitmask & (WidthValue|HeightValue)) != (WidthValue|HeightValue)) {
	  QvwmError("%d: geometry requires width and height", line);
	  break;
	}

	ASSERT(iVal);

	iVal->rc.width = ig.rc.width;
	iVal->rc.height = ig.rc.height;

	if (bitmask & XNegative) {     // EastGravity
	  iVal->gravity.x = EAST;
	  iVal->rc.x = DisplayWidth(display, screen) + ig.rc.x - ig.rc.width;
	}
	else if (bitmask & XValue) {   // WestGravity
	  iVal->gravity.x = WEST;
	  iVal->rc.x = ig.rc.x;
	}
	else {                         // CenterGravity
	  iVal->gravity.x = CENTER;
	  iVal->rc.x = ig.rc.x;
	}
	if (bitmask & YNegative) {     // SouthGravity
	  iVal->gravity.y = SOUTH;
	  iVal->rc.y = DisplayHeight(display, screen) + ig.rc.y - ig.rc.height;
	}
	else if (bitmask & YValue) {   // NorthGravity
	  iVal->gravity.y = NORTH;
	  iVal->rc.y = ig.rc.y;
	}
	else {                         // CenterGravity
	  iVal->gravity.y = CENTER;
	  iVal->rc.y = ig.rc.y;
	}
      }
      break;

    case F_OFFSET:
      {
	Point* pVal = (Point *)vItem->var;
	unsigned int dummy;
	
	ASSERT(pVal);

	XParseGeometry(str, &pVal->x, &pVal->y, &dummy, &dummy);
      }
      break;
      
    case F_SIZE:
      {
	Dim* dVal = (Dim *)vItem->var;
	int dummy;

	ASSERT(dVal);

	XParseGeometry(str, &dummy, &dummy,
		       (unsigned int *)&dVal->width,
		       (unsigned int *)&dVal->height);
      }
      break;

    case F_MODMASK:
      {
	unsigned int* mask = (unsigned int *)vItem->var;
	*mask = MakeModifier(str, 0);
      }	
      break;
    }
  }
  else
    QvwmError("%d: '%s' is unknown variable", line, var);
}

/*
 * MakeExecItem --
 *
 */
MenuElem* MakeExecItem(char* name, char* file, char* exec)
{
  MenuElem* mItem = new MenuElem();

  mItem->name = name;

#ifdef __EMX__
  mItem->file = TranslateForOS2(file);
#else
  mItem->file = file;
#endif

  mItem->func = Q_EXEC;

#ifdef __EMX__
  mItem->exec = TranslateForOS2(exec);
#else
  mItem->exec = exec;
#endif  

  mItem->child = NULL;

  return mItem;
}

/*
 * MakeDesktopItem --
 *
 */
MenuElem* MakeDesktopItem(char* name, char* file, char* exec, char* x, char* y)
{
  MenuElem* mItem;
  
  mItem = MakeExecItem(name, file, exec);

  mItem->x = mItem->y = -1;
  if (x != NULL) {
    if (*x == '!')
      mItem->x = atoi(x+1) + Icon::SFACTOR;
    else
      mItem->x = atoi(x);
  }
  if (y != NULL) {
    if (*y == '!')
      mItem->y = atoi(y+1) + Icon::SFACTOR;
    else
      mItem->y = atoi(y);
  }

  return mItem;
}  

/*
 * MakeDesktopFuncItem --
 *
 */
MenuElem* MakeDesktopFuncItem(char* name, char* file, char* func,
			      char* x, char* y)
{
  MenuElem* mItem;
  
  mItem = MakeFuncItem(name, file, func);

  mItem->x = mItem->y = -1;
  if (x != NULL) {
    if (*x == '!')
      mItem->x = atoi(x+1) + Icon::SFACTOR;
    else
      mItem->x = atoi(x);
  }
  if (y != NULL) {
    if (*y == '!')
      mItem->y = atoi(y+1) + Icon::SFACTOR;
    else
      mItem->y = atoi(y);
  }

  return mItem;
}  

/*
 * MakeFuncItem --
 */
MenuElem* MakeFuncItem(char* name, char* file, char* func)
{
  MenuElem* mItem = new MenuElem;
  FuncNumber* fNum;

  mItem->name = name;

#ifdef __EMX__
  mItem->file = TranslateForOS2(file);
#else
  mItem->file = file;
#endif  

  if ((fNum = funcHashTable->GetHashItem(func)) != NULL)
    mItem->func = *fNum;
  else
    mItem->func = Q_NONE;
  mItem->exec = "";
  mItem->child = NULL;

  return mItem;
}

/*
 * MakeDirItem --
 *
 */
MenuElem* MakeDirItem(char* name, char* file, MenuElem* child)
{
  MenuElem* mItem = new MenuElem();

  mItem->name = name;

#ifdef __EMX__
  mItem->file = TranslateForOS2(file);
#else
  mItem->file = file;
#endif  

  mItem->func = Q_DIR;
  mItem->exec = "";
  mItem->child = child;

  return mItem;
}

/*
 * MakeDlgFuncItem --
 */
MenuElem* MakeDlgFuncItem(char* name, char* str, char* func)
{
  MenuElem* mItem = new MenuElem();
  FuncNumber* fNum;

  mItem->name = name;
  mItem->file = str;
  if (func == NULL || (fNum = funcHashTable->GetHashItem(func)) == NULL)
    mItem->func = Q_NONE;
  else
    mItem->func = *fNum;
  mItem->exec = "";
  mItem->child = NULL;

  return mItem;
}

/*
 * MakeDlgItem --
 */
MenuElem* MakeDlgItem(char* name, char* str, char* exec)
{
  MenuElem* mItem = new MenuElem();

  mItem->name = name;
  mItem->file = str;
  mItem->func = Q_EXEC;
  mItem->exec = exec;
  mItem->child = NULL;

  return mItem;
}

/*
 * ChainMenuItem --
 *   Chain two MenuItems.
 */
MenuElem* ChainMenuItem(MenuElem* mItem, MenuElem* nextItem)
{
  ASSERT(mItem);

  mItem->next = nextItem;

  return mItem;
}

/*
 * CompleteMenu --
 *   Complete menu.
 */
void CompleteMenu(char* menuName, MenuElem* mItem)
{
  if (strcmp(menuName, "Shortcuts") == 0) {
    ShortCutItem = mItem;
    return;
  }
  else if (strcmp(menuName, "ExitDialog") == 0) {
    ExitDlgItem = mItem;
    return;
  }

  if (mItem == NULL) {
    mItem = new MenuElem();

    mItem->name = "(None) ";
    mItem->func = Q_NONE;
    mItem->file = "";
    mItem->exec = "";
    mItem->next = NULL;
    mItem->child = NULL;
  }
  
  if (strcmp(menuName, "StartMenu") == 0)
    StartMenuItem = mItem;
  else if (strcmp(menuName, "CtrlMenu") == 0)
    CtrlMenuItem = mItem;
  else if (strcmp(menuName, "DesktopMenu") == 0)
    DesktopMenuItem = mItem;
  else if (strcmp(menuName, "IconMenu") == 0)
    IconMenuItem = mItem;
  else if (strcmp(menuName, "TaskbarMenu") == 0)
    TaskbarMenuItem = mItem;
}

/*
 * DoAllSetting --
 *   Complete Setting.
 */
void DoAllSetting()
{
  SetFont();
}

/*
 * MakeStream --
 *   Make stream item for attribute.
 */
AttrStream* MakeStream(char* attr, char* value, AttrStream* stream)
{
  AttrStream* newStream = new AttrStream;
  int i;

  newStream->next = stream;

  for (i = 0; i < AttrNum; i++)
    if (strcmp(attr, attrSet[i].name) == 0) {
      newStream->attr = attrSet[i].flag;
      newStream->act = attrSet[i].act;
      newStream->value = value;
      break;
    }

  if (i == AttrNum) {
    QvwmError("'%s' is invalid attribute.", attr);
    delete newStream;
    return stream;
  }

  return newStream;
}

/*
 * CreateAppHash --
 *   Create a hash for application attributes.
 */
void CreateAppHash(char* appName, AttrStream* stream)
{
  AttrStream* tmpStream;
  AppAttr* attrs;

  while (stream) {
    if (!(attrs = Qvwm::appHashTable->GetHashItem(appName))) {
      attrs = new AppAttr;
      attrs->flags = TITLE | BORDER | BORDER_EDGE | CTRL_MENU | BUTTON1
	| BUTTON2 | BUTTON3;
      Qvwm::appHashTable->SetHashItem(appName, attrs);
    }
    if (stream->act)
      attrs->flags |= stream->attr;
    else
      attrs->flags &= ~stream->attr;

    if (stream->attr == SMALL_IMG)
      attrs->small_file = stream->value;
    else if (stream->attr == LARGE_IMG)
      attrs->large_file = stream->value;

    tmpStream = stream;
    stream = stream->next;

    delete tmpStream;
  }
}

/*
 * MakeModifier --
 *   Make OR'd modifier flag.
 */
unsigned int MakeModifier(char* mod, unsigned int modifier)
{
  int i;

  for (i = 0; i < KeyModNum; i++)
    if (strcmp(mod, keyMod[i].str) == 0) {
      modifier |= keyMod[i].mask;
      break;
    }

  if (i == KeyModNum)
    QvwmError("'%s' is invalid key modifier.", mod);

  return modifier;
}

/*
 * CreateSCKey --
 *   Make ShortCutKey from key + mod and command.
 */
void CreateSCKey(char* key, unsigned int mod, char* exec)
{
  KeySym sym;

  if ((sym = XStringToKeysym(key)) == NoSymbol) {
    if (key[0] == '#')
      scKey->AddSCKey((KeyCode)atoi(&key[1]), mod, exec);
    else
      QvwmError("'%s' is invalid key.", key);
  }
  else
    scKey->AddSCKey(sym, mod, exec);
}

/*
 * CreateSCKeyFunc --
 *   Make ShortCutKey from key + mod and func.
 */
void CreateSCKeyFunc(char* key, unsigned int mod, char* func)
{
  KeySym sym;
  FuncNumber* fNum;

  if ((fNum = funcHashTable->GetHashItem(func)) == NULL) {
    QvwmError("'%s' is invalid function.", func);
    return;
  }

  if ((sym = XStringToKeysym(key)) == NoSymbol) {
    if (key[0] == '#')
      scKey->AddSCKey((KeyCode)atoi(&key[1]), mod, *fNum);
    else
      QvwmError("'%s' is invalid key.", key);
  }
  else
    scKey->AddSCKey(sym, mod, *fNum);
}

/*
 * CreateIndicator --
 *   Create an indicator.
 */
void CreateIndicator(char* comp, char* exec)
{
  new Indicator(exec, comp);
}

void CreateAccessory(char* filename, char* pos, char* mode)
{
  Accessory* acc;

  acc = new Accessory(filename, pos, mode);

  desktop.GetAccList().InsertTail(acc);
}

#define DEFAULT_FONT "-*-*-medium-r-normal-*-14-*-*-*-*-*-*-*"

/*
 * SetFont --
 *   Set fonts.
 */
void SetFont()
{
  if (setlocale(LC_ALL, LocaleName) == NULL)
    QvwmError("Can't set the locale");

  char **miss, *def;
  int nMiss;
  int fsNum = (UseBoldFont) ? FsNum : FsNum - 1;

  // create default font
  fsDefault = XCreateFontSet(display, DefaultFont, &miss, &nMiss, &def);
  if (fsDefault == NULL) {
    QvwmError("Can't find font '%s'", DefaultFont);
    fsDefault = XCreateFontSet(display, DEFAULT_FONT, &miss, &nMiss, &def);
    if (fsDefault == NULL)
      exit(1);
  }
  if (miss)
    XFreeStringList(miss);

  /*
   * Create specified FontSets, but keep times of XCreateFontSet() to a 
   * mininum because the function takes much time.
   */
  for (int i = 0; i < fsNum; i++) {
    Bool match = False;

    if (*fsSet[i].fontname == NULL ||
	strcmp(*fsSet[i].fontname, DefaultFont) == 0) {
      *fsSet[i].fs = fsDefault;
      continue;
    }

    for (int j = 0; j < i; j++) {
      if (*fsSet[j].fontname &&
	  strcmp(*fsSet[i].fontname, *fsSet[j].fontname) == 0) {
	*fsSet[i].fs = *fsSet[j].fs;
	match = True;
	break;
      }
    }
    if (!match) {
      *fsSet[i].fs = XCreateFontSet(display, *fsSet[i].fontname, &miss, &nMiss,
				    &def);
      if (miss)
	XFreeStringList(miss);

      // XFontSet is pointer.
      if (*fsSet[i].fs == NULL) {
	QvwmError("Can't find font '%s'", *fsSet[i].fontname);
	*fsSet[i].fs = fsDefault;
      }
    }
  }
}

/*
 * IsFunction
 *   Return True if text is a function. The identifier beginning with 'QVWM_'
 *   is an internal function.
 */
Bool IsFunction(const char* text)
{
  char prefix[6];

  strncpy(prefix, text, 5);
  prefix[5] = '\0';

  if (strcmp(prefix, "QVWM_") == 0)
    return True;
  else
    return False;
}

/*
 * IsAttribute --
 *   Return True if text is an attribute.
 */
Bool IsAttribute(const char* text)
{
  for (int i = 0; i < AttrNum; i++)
    if (strcmp(text, attrSet[i].name) == 0)
      return True;

  return False;
}

#ifdef __EMX__
static char* TranslateForOS2(char* name)
{
  char* tName;

  if (name != NULL)
    if (strchr(name, '/') != NULL || strchr(name, '\\') != NULL) {
      char nom[300];

      strcpy(nom, __XOS2RedirRoot(name));
      tName = new char[strlen(nom) + 1];
      strcpy(tName, nom);

      return tName;
    }
  
  return name;
}
#endif  
