#include <stdio.h>
#include "Vector.h"
#include "GetOpt.h"

static int max(int a, int b)
{
  if (a>b)
    return a;
  return b;
}

GetOptOption *htnull()
{
  return NULL;
}

GetOpt::GetOpt()
{
  firstgoo=NULL;
  lastgoo=NULL;
}

GetOpt::~GetOpt()
{
}

/* returns false if errors detected */
bool GetOpt::parse(int argc, char *argv[], bool showErrors)
{
  bool okay=true;
  Vector<char*> v;

  /* step one: break up the input array into pieces--
     i.e., split --alpha=beta into two elements, '--alpha'
     and 'beta'. We put these in vector v. */

  for (int i=1;i<argc;i++)
    {
      char *c=argv[i];

      char *eq=strstr(c,"=");
      if (eq==NULL)
	{
	  v.add(c);
	}
      else
	{
	  char *val=&eq[1];
	  eq[0]=0;
	  v.add(c);

	  /* if the value is encased in quotes, e.g.:
	     key="value", remove the quotes */
	  if (val[0]=='=')
	    {
	      int last=strlen(val)-1;
	      if (val[last]=='=')
		val[last]=0;
	      v.add(&val[1]);
	    }
	  else
	    v.add(val);
	}
    }

  /* now loop over the elements and evaluate the arguments. */
  int i=0;

  while (i<v.getSize())
    {
      char *arg=v.get(i);

      if (!strncmp(arg,"--",2))
	{
	  char *optname=&arg[2];
	  GetOptOption *goo=_loptions.get(optname);
	  if (goo==NULL)
	    {
	      okay=false;
	      if (showErrors)
		printf("Unknown option --%s\n",optname);
	      i++;
	      continue;
	    }

	  if (goo->type==GOO_BOOL_TYPE)
	    {
	      if ((i+1)<v.getSize())
		{
		  char *val=v.get(i+1);
		  
		  if (!strcmp(val,"true"))
		    {
		      i+=2;
		      goo->bvalue=true;
		      continue;
		    }
		  if (!strcmp(val,"false"))
		    {
		      i+=2;
		      goo->bvalue=false;
		      continue;
		    }
		}

	      goo->bvalue=true;
	      i++;
	      continue;
	    }

	  if (goo->type==GOO_STRING_TYPE)
	    {
	      if ((i+1)<v.getSize())
		{
		  char *val=v.get(i+1);
		  i+=2;

		  goo->svalue=strdup(val);
		  continue;
		}

	      okay=false;
	      if (showErrors)
		{
		  printf("Option %s requires a string argument.\n",optname);
		}
	    }

	}

      /* is it just a single flag? */
      if (!strncmp(arg,"-",1) && strncmp(arg,"--",2))
	{
	  int len=strlen(arg);
	  for (int pos=1;pos<len;pos++)
	    {
	      GetOptOption *goo=_soptions.get(arg[pos]);
	      if (goo==NULL)
		{
		  okay=false;
		  if (showErrors)
		    printf("Unknown option -%c\n",arg[pos]);
		  i++;
		  continue;
		}

	      if (goo->type==GOO_BOOL_TYPE)
		{
		  goo->bvalue=true;
		  continue;
		}

	      if (goo->type==GOO_STRING_TYPE)
		{
		  if ((i+1)<v.getSize())
		    {
		      char *val=v.get(i+1);
		      if (val[0]=='-')
			{
			  okay=false;
			  if (showErrors)
			    {
			      printf("Ran out of arguments for option block %s\n",arg);
			    }
			}
		      i++;
		      
		      goo->svalue=strdup(val);
		      continue;
		    }

		  okay=false;
		  if (showErrors)
		    {
		      printf("Option -%c requires a string argument.\n",arg[pos]);
		    }
		}
	    }
	  i++;
	  continue;
	}

      _extraargs.add(arg);
      i++;
    }

  return okay;
}

void GetOpt::addToList(GetOptOption *goo)
{
  if (firstgoo==NULL)
    {
      firstgoo=goo;
      lastgoo=goo;
      return;
    }

  lastgoo->next=goo;
  lastgoo=goo;
}

void GetOpt::addSpacer(const char *s)
{
  GetOptOption *goo=new GetOptOption();
  goo->spacer=1;
  goo->help=strdup(s);
  addToList(goo);
}

void GetOpt::addOptionBool(char sname, const char *lname, bool def, char *help)
{
  GetOptOption *goo=new GetOptOption();
  goo->sname=sname;
  goo->lname=strdup(lname);
  goo->bvalue=def;
  goo->type=GOO_BOOL_TYPE;
  goo->help=help;

  _loptions.put(goo->lname,goo);
  _soptions.put(sname,goo);

  addToList(goo);
}

void GetOpt::addOptionInt(char sname, const char *lname, int def, char *help)
{
  char b[128];
  snprintf(b,128,"%i",def);

  addOptionString(sname,lname,b,help);
}

void GetOpt::addOptionString(char sname, const char *lname, char *def, char *help)
{
  GetOptOption *goo=new GetOptOption();
  goo->sname=sname;
  goo->lname=strdup(lname);
  goo->svalue=strdup(def);
  goo->type=GOO_STRING_TYPE;
  goo->help=help;

  _loptions.put(goo->lname,goo);
  _soptions.put(sname,goo);

  addToList(goo);
}

bool GetOpt::getOptionBool(const char *lname)
{
  GetOptOption *goo=_loptions.get(lname);
  if (goo==NULL)
    return false;
  return goo->bvalue;
}

int GetOpt::getOptionInt(const char *lname)
{
  char *b=getOptionString(lname);
  if (b==NULL)
    return 0;

  return atoi(b);
}

char *GetOpt::getOptionString(const char *lname)
{
  GetOptOption *goo=_loptions.get(lname);
  if (goo==NULL)
    return NULL;
  return goo->svalue;
}

Vector<char*> *GetOpt::getExtraArgs()
{
  return &_extraargs;
}

void GetOpt::setOptionString(const char *lname, char *val)
{
  GetOptOption *goo=_loptions.get(lname);
  if (goo==NULL)
    return;

  if (goo->svalue!=NULL)
    free(goo->svalue);

  goo->svalue=strdup(val);
}

void GetOpt::setOptionBool(const char *lname, bool val)
{
  GetOptOption *goo=_loptions.get(lname);
  if (goo==NULL)
    return;

  goo->bvalue=val;
}

void GetOpt::setOptionInt(const char *lname, int val)
{
  char b[128];

  GetOptOption *goo=_loptions.get(lname);
  if (goo==NULL)
    return;

  if (goo->svalue!=NULL)
    free(goo->svalue);

  snprintf(b,128,"%i",val);
  goo->svalue=strdup(b);
}

void GetOpt::doUsage()
{
  GetOptOption *goo;

  int leftmargin=2;
  int longwidth=12;
  int valuewidth=10;

  for (goo=firstgoo; goo!=NULL; goo=goo->next)
    {
      if (goo->spacer)
	continue;

      longwidth=max(longwidth, strlen(goo->lname));

      if (goo->type==GOO_STRING_TYPE)
	valuewidth=max(valuewidth, strlen(goo->svalue));
    }

  for (goo=firstgoo; goo!=NULL; goo=goo->next)
    {
      if (goo->spacer)
	{
	  if (goo->help==NULL || strlen(goo->help)==0)
	    printf("\n");
	  else
	    printf("\n%*s%s\n\n", leftmargin, "", goo->help);
	  continue;
	}

      printf("%*s", leftmargin, "");

      if (goo->sname==0)
	printf("     ");
      else
	printf("-%c | ", goo->sname);

      printf("--%*s ", -longwidth, goo->lname);

      const char *v;
      if (goo->type==GOO_BOOL_TYPE)
	v=goo->bvalue ? "true" : "false";
      else
	v=goo->svalue;

      printf(" [ %s ]", v);

      printf("%*s", (int) (valuewidth-strlen(v)), "");

      printf(" %s   ", goo->help);
      printf("\n");
    }
}

void GetOpt::showOptions()
{
  HashTableIterator<const char*,GetOptOption*> *it=_loptions.iterator();

  while (it->next())
    {
      const char *key=it->getKey();
      GetOptOption *goo=_loptions.get(key);

      printf("%s = ",key);

      if (goo->type==GOO_BOOL_TYPE)
	{
	  if (goo->bvalue)
	    printf("true\n");
	  else
	    printf("false\n");
	}

      if (goo->type==GOO_STRING_TYPE)
	printf("'%s'\n",goo->svalue);

    }
  delete it;

  for (int i=0;i<_extraargs.getSize();i++)
    {
      printf("arg%i = %s\n",i,_extraargs.get(i));
    }
}
