/*
   FvwmButtons v2.0.41-plural-Z-alpha, copyright 1996, Jarl Totland

 * This module, and the entire GoodStuff program, and the concept for
 * interfacing this module to the Window Manager, are all original work
 * by Robert Nation
 *
 * Copyright 1993, Robert Nation. No guarantees or warantees or anything
 * are provided or implied in any way whatsoever. Use this program at your
 * own risk. Permission to use this program for any purpose is given,
 * as long as the copyright is kept intact.

*/

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
/*
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/Xproto.h>
*/

#include "FvwmButtons.h"
#include "button.h"

extern int w,h,x,y,xneg,yneg;
extern char *config_file;

/* ----------------------------------- macros ------------------------------ */

#define trimleft(s) {while(*s==' '||*s=='\t'||*s=='\n')(s)++;}
#define trimright(s) {                                                        \
  char *t=(s);while(*t!=0) t++;                                               \
  if(t>s) while(*(t-1)==' '||*(t-1)=='\t'||*(t-1)=='\n') t--;                 \
  *t=0;}

/**
*** seekright()
***
*** split off the first continous section of the string into a new allocated
*** string and move the old pointer forward. Accepts and strips quoting with
*** '," or `, and the current quote q can be escaped inside the string with \q.
**/
char *seekright(char **s)
{
  char *b,*t,*r,q;
  trimleft(*s);
  q=**s;
  if(q=='"' || q=='`' || q=='\'')
    {
      b=t=(*s)++;
      while(**s && (**s!=q || (*(*s-1)=='\\' && t--)))
	*t++=*((*s)++);
      if(!**s)
	fprintf(stderr,"%s: Unterminated %c quote found\n",MyName,q);
      else (*s)++;
    }
  else
    {
      b=t=*s;
      while(**s && !isspace(**s) && **s!=')' && **s!=',')
	(*s)++;
      t=*s;
    }
  if(t<=b)
    return NULL;
  r=mymalloc(t-b+1);
  strncpy(r,b,t-b);
  r[t-b]=0;
  return r;
}

/**
*** MatchSeveralLines()
***
*** Returns which, if any, of the strings in o[] matches start of s, t is moved
*** after the end of the match.
**/
int MatchSeveralLines(char *s,char **o,char **t)
{
  int i=0,j;
  if(s && o && o[0])
    while(o[i])
      {
	j=0;while(o[i][j] && tolower(s[j])==tolower(o[i][j]))j++;
	if(!o[i][j])
	  {
	    *t=&s[j];
	    return i;
	  }
	i++;
      }
  *t=s;
  return -1;
}

/**
*** ParseBack()
*** Parses the options possible to Back
**/
int ParseBack(char **ss)
{
  char *opts[]={"icon",NULL};
  char *t,*s=*ss;
  int r=0;

  while(*s && *s!=')')
    {
      trimleft(s);
      if(*s==',')
	s++;
      else switch(MatchSeveralLines(s,opts,&s))
	{
	case 0: /* Icon */
	  r=1;
	  fprintf(stderr,"%s: Back(Icon) not supported yet\n",MyName);
	  break;
	default:
	  t=seekright(&s);
	  fprintf(stderr,"%s: Illegal back option \"%s\"\n",MyName,t);
	  free(t);
	}
    }
  if(*s) s++;
  *ss=s;
  return r;
}

/**
*** ParseTitle()
*** Parses the options possible to Title
**/
void ParseTitle(char **ss,byte *flags,byte *mask)
{
  char *titleopts[]={"left","right","center","side",NULL};
  char *t,*s=*ss;

  while(*s && *s!=')')
    {
      trimleft(s);
      if(*s==',')
	s++;
      else switch(MatchSeveralLines(s,titleopts,&s))
	{
	case 0: /* Left */
	  *flags&=~b_TitleHoriz;
	  *mask|=b_TitleHoriz;
	  break;
	case 1: /* Right */
	  *flags&=~b_TitleHoriz;
	  *flags|=2;
	  *mask|=b_TitleHoriz;
	  break;
	case 2: /* Center */
	  *flags&=~b_TitleHoriz;
	  *flags|=1;
	  *mask|=b_TitleHoriz;
	  break;
	case 3: /* Side */
	  *flags|=b_Horizontal;
	  *mask|=b_Horizontal;
	  break;
	default:
	  t=seekright(&s);
	  fprintf(stderr,"%s: Illegal title option \"%s\"\n",MyName,t);
	  free(t);
	}
    }
  if(*s) s++;
  *ss=s;
}

/**
*** ParseSwallow()
*** Parses the options possible to Swallow
**/
void ParseSwallow(char **ss,byte *flags,byte *mask)
{
  char *swallowopts[]={"nohints","hints","nokill","kill","noclose","close",
			 "respawn","norespawn","useold",
			 "noold","usetitle","notitle",NULL};
  char *t,*s=*ss;

  while(*s && *s!=')')
    {
      trimleft(s);
      if(*s==',')
	s++;
      else switch(MatchSeveralLines(s,swallowopts,&s))
	{
	case 0: /* NoHints */
	  *flags|=b_NoHints;
	  *mask|=b_NoHints;
	  break;
	case 1: /* Hints */
	  *flags&=~b_NoHints;
	  *mask|=b_NoHints;
	  break;
	case 2: /* NoKill */
	  *flags&=~b_Kill;
	  *mask|=b_Kill;
	  break;
	case 3: /* Kill */
	  *flags|=b_Kill;
	  *mask|=b_Kill;
	  break;
	case 4: /* NoClose */
	  *flags|=b_NoClose;
	  *mask|=b_NoClose;
	  break;
	case 5: /* Close */
	  *flags&=~b_NoClose;
	  *mask|=b_NoClose;
	  break;
	case 6: /* Respawn */
	  *flags|=b_Respawn;
	  *mask|=b_Respawn;
	  break;
	case 7: /* NoRespawn */
	  *flags&=~b_Respawn;
	  *mask|=b_Respawn;
	  break;
	case 8: /* UseOld */
	  *flags|=b_UseOld;
	  *mask|=b_UseOld;
	  break;
	case 9: /* NoOld */
	  *flags&=~b_UseOld;
	  *mask|=b_UseOld;
	  break;
	case 10: /* UseTitle */
	  *flags|=b_UseTitle;
	  *mask|=b_UseTitle;
	  break;
	case 11: /* NoTitle */
	  *flags&=~b_UseTitle;
	  *mask|=b_UseTitle;
	  break;
	default:
	  t=seekright(&s);
	  fprintf(stderr,"%s: Illegal swallow option \"%s\"\n",MyName,t);
	  free(t);
	}
    }
  if(*s) s++;
  *ss=s;
}

/**
*** ParseContainer()
*** Parses the options possible to Container
**/
void ParseContainer(char **ss,button_info *b)
{
  char *conts[]={"columns","rows","font","frame","back","fore",
		   "padding","title","swallow","nosize","size",NULL};
  char *t,*o,*s=*ss;
  int i,j;

  while(*s && *s!=')')
    {
      trimleft(s);
      if(*s==',')
	s++;
      else switch(MatchSeveralLines(s,conts,&s))
	{
	case 0: /* Columns */
	  b->c->num_columns=max(1,strtol(s,&t,10));
	  s=t;
	  break;
	case 1: /* Rows */
	  b->c->num_rows=max(1,strtol(s,&t,10));
	  s=t;
	  break;
	case 2: /* Font */
	  b->c->font_string=seekright(&s);
	  b->c->flags|=b_Font;
	  break;
	case 3: /* Frame */
	  b->c->framew=strtol(s,&t,10);
	  b->c->flags|=b_Frame;
	  s=t;
	  break;
	case 4: /* Back */
	  trimleft(s);
	  if(*s=='(' && s++)
	    if(ParseBack(&s))
	      b->c->flags|=b_IconBack;
	  b->c->back=seekright(&s);
	  if(b->c->back)
	    b->c->flags|=b_Back;
	  else
	    b->c->flags&=~b_IconBack;
	  break;
	case 5: /* Fore */
	  b->c->fore=seekright(&s);
	  b->c->flags|=b_Fore;
	  break;
	case 6: /* Padding */
	  i=strtol(s,&t,10);
	  if(t>s)
	    {
	      b->c->xpad=b->c->ypad=i;
	      s=t;
	      i=strtol(s,&t,10);
	      if(t>s)
		{
		  b->c->ypad=i;
		  s=t;
		}
	      b->c->flags|=b_Padding;
	    }
	  else
	    fprintf(stderr,"%s: Illegal padding argument\n",MyName);
	  break;
	case 7: /* Title - flags */
	  trimleft(s);
	  if(*s=='(' && s++)
	    {
	      b->c->justify=0;
	      b->c->justify_mask=0;
	      ParseTitle(&s,&b->c->justify,&b->c->justify_mask);
	      if(b->c->justify_mask)
		b->c->flags|=b_Justify;
	    }
	  else
	    {
	      fprintf(stderr,"%s: Illegal title in container options\n",
		      MyName);
	      free(seekright(&s));
	    }
	  break;
	case 8: /* Swallow - flags */
	  trimleft(s);
	  if(*s=='(' && s++)
	    {
	      b->c->swallow=0;
	      b->c->swallow_mask=0;
	      ParseSwallow(&s,&b->c->swallow,&b->c->swallow_mask);
	      if(b->c->swallow_mask)
		b->c->flags|=b_Swallow;
	    }
	  else
	    {
	      fprintf(stderr,"%s: Illegal swallow in container options\n",
		      MyName);
	      free(seekright(&s));
	    }	    
	  break;
	case 9: /* NoSize */
	  b->c->flags|=b_Size;
	  b->c->minx=b->c->miny=0;
	  break;
	  
	case 10: /* Size */
	  i=strtol(s,&t,10);
	  j=strtol(t,&o,10);
	  if(t>s && o>t)
	    {
	      b->c->minx=i;
	      b->c->miny=j;
	      b->c->flags|=b_Size;
	      s=o;
	    }
	  else
	    fprintf(stderr,"%s: Illegal size arguments\n",MyName);
	  break;

	default:
	  t=seekright(&s);
	  fprintf(stderr,"%s: Illegal container option \"%s\"\n",MyName,t);
	  free(t);
	}
    }
  if(*s) s++;
  *ss=s;
}

/**
*** match_string()
***
*** parse a buttonconfig string.
*** *FvwmButtons(option[ options]) title iconname command
**/
/*#define DEBUG_PARSER*/
void match_string(button_info **uberb,char *s)
{
  button_info *b,*ub=*uberb;
  int i,j;
  char *t,*o;
  b=alloc_button(ub,(ub->c->num_buttons)++);
  trimleft(s);

  if(*s=='(' && s++)
    {
      char *opts[]={"back","fore","font","title","icon","frame","padding",
		      "swallow","action","container","end","nosize","size",
		      NULL};
      trimleft(s);
      while(*s && *s!=')')
	{
	  if(*s>='0' && *s<='9')
	    {
	      sscanf(s,"%dx%d",&i,&j);
	      if(i>0) b->BWidth=i;
	      if(j>0) b->BHeight=j;
	      while(*s!=' ' && *s!='\t' && *s!=')' && *s!=',') s++;
	      trimleft(s);
	      continue;
	    }
	  if(*s==',' && s++)
	    trimleft(s);
	  switch(MatchSeveralLines(s,opts,&s))
	    {
	    case 0: /* Back */
	      trimleft(s);
	      if(*s=='(' && s++)
		if(ParseBack(&s))
		  b->flags|=b_IconBack;
	      if(b->flags&b_Back) free(b->back);
	      b->back=seekright(&s);
	      if(b->back)
		b->flags|=b_Back;
	      else
		b->flags&=~b_IconBack;
	      break;

	    case 1: /* Fore */
	      if(b->flags&b_Fore) free(b->fore);
	      b->fore=seekright(&s);
	      b->flags|=b_Fore;
	      break;

	    case 2: /* Font */
	      if(b->flags&b_Font) free(b->font_string);
	      b->font_string=seekright(&s);
	      b->flags|=b_Font;
	      break;

	      /* --------------------------- Title ------------------------- */

	    case 3: /* Title */
	      trimleft(s);
	      if(*s=='(' && s++)
		{
		  b->justify=0;
		  b->justify_mask=0;
		  ParseTitle(&s,&b->justify,&b->justify_mask);
		  if(b->justify_mask)
		    b->flags|=b_Justify;
		}
	      t=seekright(&s);
	      if(t && (t[0]!='-' || t[1]!=0))
		{
		  b->title=t;
#ifdef DEBUG_PARSER
		  fprintf(stderr,"PARSE: Title \"%s\"\n",b->title);
#endif
		  b->flags|=b_Title;
		}
	      else
		{
		  fprintf(stderr,"%s: Missing title argument\n",MyName);
		  if(t)free(t);
		}
	      break;

	      /* ---------------------------- icon ------------------------- */

	    case 4: /* Icon */
	      t=seekright(&s);
	      if(t && (t[0]!='-' && t[1]!=0))
		{
		  b->icon_file=t;
		  b->IconWin=None;
		  b->flags|=b_Icon;
		}
	      else
		{
		  fprintf(stderr,"%s: Missing icon argument\n",MyName);
		  if(t)free(t);
		}
	      break;

	      /* --------------------------- frame ------------------------- */

	    case 5: /* Frame */
	      i=strtol(s,&t,10);
	      if(t>s)
		{
		  b->flags|=b_Frame;
		  b->framew=i;
		  s=t;
		}
	      else
		fprintf(stderr,"%s: Illegal frame argument\n",MyName);
	      break;

	      /* -------------------------- padding ------------------------ */

	    case 6: /* Padding */
	      i=strtol(s,&t,10);
	      if(t>s)
		{
		  b->xpad=b->ypad=i;
		  b->flags |= b_Padding;
		  s=t;
		  i=strtol(s,&t,10);
		  if(t>s)
		    {
		      b->ypad=i;
		      s=t;
		    }
		}
	      else
		fprintf(stderr,"%s: Illegal padding argument\n",MyName);
	      break;

	      /* -------------------------- swallow ------------------------ */

	    case 7: /* Swallow */
	      trimleft(s);
	      b->swallow=0;
	      b->swallow_mask=0;
	      if(*s=='(' && s++)
		ParseSwallow(&s,&b->swallow,&b->swallow_mask);
	      t=seekright(&s);
	      o=seekright(&s);
	      if(t)
		{
		  b->hangon=t;
		  b->flags|=b_Hangon;
		  b->flags|=b_Swallow;
		  b->swallow|=1;
		  if(!(b->swallow&b_NoHints))
		    b->hints=(XSizeHints*)mymalloc(sizeof(XSizeHints));
		  if(o)
		    {
		      if(!(buttonSwallow(b)&b_UseOld))
			SendText(fd,o,0);
		      b->spawn=o;  /* Might be needed if respawning sometime */
		    }
      		}
	      else
		{
		  fprintf(stderr,"%s: Missing swallow argument\n",MyName);
		  if(t)free(t);
		  if(o)free(o);
		}
	      break;

	      /* --------------------------- action ------------------------ */
	     
	    case 8: /* Action */
	      trimleft(s);
	      i=0;
	      if(*s=='(')
		{
		  s++;
		  if(strncasecmp(s,"mouse",5)!=0)
		    {
		      fprintf(stderr,"%s: Couldn't parse action\n",MyName);
		    }
		  s+=5;
		  i=strtol(s,&t,10);
		  s=t;
		  while(*s && *s!=')') 
		    s++;
		  if(*s==')')s++;
		}
	      t=seekright(&s);
	      if(t)
		AddButtonAction(b,i,strdup(t));
	      else
		fprintf(stderr,"%s: Missing action argument\n",MyName);
	      break;

	      /* -------------------------- container ---------------------- */

	    case 9: /* Container */
	      b->flags&=b_Frame|b_Back|b_Fore|b_Padding|b_Action;
	      MakeContainer(b);
	      *uberb=b;
	      trimleft(s);
	      if(*s=='(' && s++)
		ParseContainer(&s,b);
	      break;

	    case 10: /* End */
	      *uberb=ub->parent;
	      ub->c->buttons[--(ub->c->num_buttons)]=NULL;
	      if(!ub->parent)
		{
		  fprintf(stderr,"%s: Unmatched END in config file\n",MyName);
		  exit(1);
		}
	      break;

	    case 11: /* NoSize */
	      b->flags|=b_Size;
	      b->minx=b->miny=0;
	      break;

	    case 12: /* Size */
	      i=strtol(s,&t,10);
	      j=strtol(t,&o,10);
	      if(t>s && o>t)
		{
		  b->minx=i;
		  b->miny=j;
		  b->flags|=b_Size;
		  s=o;
		}
	      else
		fprintf(stderr,"%s: Illegal size arguments\n",MyName);
	      break;

	    default:
	      t=seekright(&s);
	      fprintf(stderr,"%s: Illegal button option \"%s\"\n",MyName,t);
	      free(t);
	      break;
	    }
	  trimleft(s);
	}
      s++;
      trimleft(s);
    }

  /* get title and iconname */
  if(!(b->flags&b_Title))
    {
      b->title=seekright(&s);
      if(b->title && ((b->title)[0]!='-'||(b->title)[1]!=0))
	b->flags |= b_Title;
      else
	if(b->title)free(b->title);
    }
  else
    free(seekright(&s));

  if(!(b->flags&b_Icon))
    {
      b->icon_file=seekright(&s);
      if(b->icon_file && ((b->icon_file)[0]!='-'||(b->icon_file)[1]!=0))
	{
	  b->flags|=b_Icon;
	  b->IconWin=None;
	}
      else
	if(b->icon_file)free(b->icon_file);
    }
  else
    free(seekright(&s));

  trimleft(s);

  /* Swallow hangon command */
  if(strncasecmp(s,"swallow",7)==0)
    {
      if(b->flags&b_Swallow)
	{
	  fprintf(stderr,"%s: Illegal with both old and new swallow!\n",
		  MyName);
	  exit(1);
	}
      s+=7;
      b->hangon=seekright(&s);
      b->flags|=(b_Swallow|b_Hangon);
      b->swallow|=1;
      trimleft(s);
      if(!(b->swallow&b_NoHints))
	b->hints=(XSizeHints*)mymalloc(sizeof(XSizeHints));
      if(*s)
	{
	  if(!(buttonSwallow(b)&b_UseOld))
	    SendText(fd,s,0);
	  b->spawn=strdup(s);
	}
    }
  else if(*s)
    AddButtonAction(b,0,strdup(s));
  return;
}

/**
*** ParseConfigLine
**/
void ParseConfigLine(button_info **ubb,char *s)
{
  button_info *ub=*ubb;
  char *opts[]={"geometry","font","padding","columns","rows","back","fore",
		  "frame","file",NULL};
  int i,j,k;

  switch(MatchSeveralLines(s,opts,&s))
    {
    case 0:/* Geometry */
      {
	char geom[64];
	int flags,g_x,g_y,width,height;
	i=sscanf(s,"%63s",geom);
	if(i==1)
	  {
	    flags=XParseGeometry(geom,&g_x,&g_y,&width,&height);
	    if(flags&WidthValue) w=width;
	    if(flags&HeightValue) h=height;
	    if(flags&XValue) x=g_x;
	    if(flags&YValue) y=g_y;
	    if(flags&XNegative) xneg=1;
	    if(flags&YNegative) yneg=1;
	  }
	break;
      }
    case 1:/* Font */
      CopyString(&ub->c->font_string,s);
      break;
    case 2:/* Padding */
      i=sscanf(s,"%d %d",&j,&k);
      if(i>0) ub->c->xpad=ub->c->ypad=j;
      if(i>1) ub->c->ypad=k;
      break;
    case 3:/* Columns */
      i=sscanf(s,"%d",&j);
      if(i>0) ub->c->num_columns=j;
      break;
    case 4:/* Rows */
      i=sscanf(s,"%d",&j);
      if(i>0) ub->c->num_rows=j;
      break;
    case 5:/* Back */
      CopyString(&(ub->c->back),s);
      break;
    case 6:/* Fore */
      CopyString(&(ub->c->fore),s);
      break;
    case 7:/* Frame */
      i=sscanf(s,"%d",&j);
      if(i>0) ub->c->framew=j;
      break;
    case 8:/* File */
      trimleft(s);
      config_file=seekright(&s);
      break;
    default:
      trimleft(s);
      match_string(ubb,s);
      break;
    }
}

/**
*** ParseConfigFile()
*** Parses optional separate configuration file for FvwmButtons
**/
void ParseConfigFile(button_info *ub)
{
  char s[1024],*t;
  FILE *f=fopen(config_file,"r");
  int l;
  if(!f)
    {
      fprintf(stderr,"%s: Couldn't open config file %s\n",MyName,config_file);
      return;
    }

  while(fgets(s,1023,f))
    {
      /* Got to do some preprocessing here... Line continuation: */
      while((l=strlen(s))<sizeof(s) && s[l-1]=='\n' && s[l-2]=='\\')
	fgets(s+l-2,sizeof(s)-l,f);
      /* And comments: */
      t=s;
      while(*t)
	{
	  if(*t=='#' && (t==s || *(t-1)!='\\'))
	    {
	      *t=0;
	      break;
	    }
	  t++;
	}
      t=s;trimleft(t);
      if(*t)ParseConfigLine(&ub,t);
    }

  fclose(f);
}

/**
*** ParseOptions()
**/
void ParseOptions(button_info *ub)
{
  char *s;
  char *items[]={"iconpath","pixmappath",NULL,NULL};

  items[2]=mymalloc(strlen(MyName)+2);
  sprintf(items[2],"*%s",MyName);

  GetConfigLine(fd,&s);
  while(s && s[0])
    {
      switch(MatchSeveralLines(s,items,&s))
	{
	case -1:
	  break;
	case 0:
	  CopyString(&iconPath,s);
	  break;
	case 1:
	  CopyString(&pixmapPath,s);
	  break;
	case 2:
	  if(s && s[0] && !config_file)
	    ParseConfigLine(&ub,s);
	}
      GetConfigLine(fd,&s);
    }

  if(config_file)
    ParseConfigFile(ub);

  return;
}

