#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <gnome-xml/parser.h>
#include "options.h"


/* #define DEBUG */

#define STR_TRUE(a) ((a)==1 ? ("True") : ("False"))

extern gboolean conf_changed;

/* Prototypes for internally used functions */
Options *parseOpts(xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur);
EntryList *parseEntries(xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur);
SSH_Entry *parseEntry(xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur);
GSList *seekData(char field,GSList *lst,gchar *data);

/* Load settings from XML file */
Options *loadSettings(char *filename) {
  Options *opts=NULL;
  char filename2[512];
  xmlDocPtr doc;
  xmlNodePtr cur;
  xmlNsPtr ns;
  #ifdef DEBUG
  int r; /* This is needed only when debugging */
  #endif
  if(filename==NULL) {
    sprintf(filename2,"%s/.gssh.xml",getenv("HOME"));
    filename=filename2;
  }
  doc=xmlParseFile(filename);
  if(doc==NULL) {
    fprintf(stderr,"Error ! Unable to parse file %s !\n",filename);
    return NULL;
  }
  cur=doc->root;
  if(cur==NULL) {
    fprintf(stderr,"Error ! Configuration file %s is empty !\n",filename);
    return NULL;
  }
  ns=xmlSearchNs(doc,cur,"gssh");
  cur=cur->childs;
  while(cur!=NULL) {
    #ifdef DEBUG
    printf("debug: %s = %s\n",cur->name,xmlNodeListGetString(doc,cur->childs,1));
    #endif
    if(!strcmp(cur->name,"main") && (cur->ns==ns)) {
      #ifdef DEBUG
      printf("debug: %s , reading global definitions\n",cur->name);
      #endif
      opts=parseOpts(doc,ns,cur);
      if(opts==NULL) {
        fprintf(stderr,"Error while parsing global definitions from %s\n",filename);
        return NULL;
      }
    }
    else if(!strcmp(cur->name,"entries") && (cur->ns==ns)) {
      #ifdef DEBUG
      printf("debug: %s , reading entry definitions\n",cur->name);
      #endif
      opts->entries=parseEntries(doc,ns,cur);
      if(opts->entries==NULL) {
        fprintf(stderr,"Error while parsing entry definitions from %s\n",filename);
        return NULL;
      }
    }
    cur=cur->next;
  }
  // Final debug run
  #ifdef DEBUG
  printf("debug, structude dump:\n");
  printf("save_host_name: %d\n",opts->save_host_name);
  printf("save_host_settings: %d\n",opts->save_host_settings);
  printf("use_host_settings: %d\n",opts->use_host_settings);
  printf("ssh command: \"%s\"\n",opts->ssh_cmd);
  printf("term command: \"%s\"\n",opts->term_cmd);
  printf("number of entries: %d\n",opts->entries->numEntries);
  for(r=0;r<opts->entries->numEntries;r++) {
    printf("  entry(%d) hostname: %s\n",r,opts->entries->entries[r].hostname);
    printf("  entry(%d) username: %s\n",r,opts->entries->entries[r].username);
    printf("  entry(%d) usePort: %d\n",r,opts->entries->entries[r].usePort);
    printf("  entry(%d) port: %d\n\n",r,opts->entries->entries[r].port);
  }
  #endif
  return opts;
}
/* Save settings to XML file */
int saveSettings(char *filename,Options *opts) {
  FILE *fp;
  char filename2[512];
  int e;
  if(filename==NULL) {
    sprintf(filename2,"%s/.gssh.xml",getenv("HOME"));
    filename=filename2;
  }
  fp=fopen(filename,"w");
  if(!fp) {
    fprintf(stderr,"Error ! Could not open file %s for writing !\n",filename);
    return 1;
  }
  fprintf(fp,"<?xml version=\"1.0\"?>\n");
  fprintf(fp,"<gssh>\n");
    fprintf(fp,"\t<main>\n");
      if(opts) {
      fprintf(fp,"\t\t<save_host_name>%s</save_host_name>\n",STR_TRUE(opts->save_host_name));
      fprintf(fp,"\t\t<save_host_settings>%s</save_host_settings>\n",STR_TRUE(opts->save_host_settings));
      fprintf(fp,"\t\t<use_host_settings>%s</use_host_settings>\n",STR_TRUE(opts->use_host_settings));
      fprintf(fp,"\t\t<ssh_cmd>%s</ssh_cmd>\n",opts->ssh_cmd);
      fprintf(fp,"\t\t<term_cmd>%s</term_cmd>\n",opts->term_cmd);
      } else {
      	/* These are the default values */
      fprintf(fp,"\t\t<save_host_name>True</save_host_name>\n");
      fprintf(fp,"\t\t<save_host_settings>True</save_host_settings>\n");
      fprintf(fp,"\t\t<use_host_settings>True</use_host_settings>\n");
      fprintf(fp,"\t\t<ssh_cmd>ssh</ssh_cmd>\n");
      fprintf(fp,"\t\t<term_cmd>gnome-terminal</term_cmd>\n");
      }
    fprintf(fp,"\t</main>\n\n");

    fprintf(fp,"\t<entries>\n");
      if(opts) {
      fprintf(fp,"\t\t<entry_count>%d</entry_count>\n",opts->entries->numEntries);
        for(e=0;e<opts->entries->numEntries;e++) {
          fprintf(fp,"\t\t\t<entry>\n");
          fprintf(fp,"\t\t\t\t<hostname>%s</hostname>\n",opts->entries->entries[e].hostname);
          if(opts->entries->entries[e].username)
            fprintf(fp,"\t\t\t\t<username>%s</username>\n",opts->entries->entries[e].username);
          else
            fprintf(fp,"\t\t\t\t<username></username>\n");
          fprintf(fp,"\t\t\t\t<useport>%s</useport>\n",STR_TRUE(opts->entries->entries[e].usePort));
          fprintf(fp,"\t\t\t\t<port>%d</port>\n",opts->entries->entries[e].port);
          fprintf(fp,"\t\t\t\t<use_extra>%s</use_extra>\n",STR_TRUE(opts->entries->entries[e].useExtra));
          fprintf(fp,"\t\t\t\t<local_forwarding>%s</local_forwarding>\n",STR_TRUE(opts->entries->entries[e].forwardLocal));
          fprintf(fp,"\t\t\t\t<local_forwarding_port_clientside>%d</local_forwarding_port_clientside>\n",opts->entries->entries[e].forwardLocalPortC);
          fprintf(fp,"\t\t\t\t<local_forwarding_destination>%s</local_forwarding_destination>\n",opts->entries->entries[e].forwardLocalDest);
          fprintf(fp,"\t\t\t\t<local_forwarding_port>%d</local_forwarding_port>\n",opts->entries->entries[e].forwardLocalPort);
          fprintf(fp,"\t\t\t\t<remote_forwarding>%s</remote_forwarding>\n",STR_TRUE(opts->entries->entries[e].forwardRemote));
          fprintf(fp,"\t\t\t\t<remote_forwarding_destination>%s</remote_forwarding_destination>\n",opts->entries->entries[e].forwardRemoteDest);
          fprintf(fp,"\t\t\t\t<remote_forwarding_port>%d</remote_forwarding_port>\n",opts->entries->entries[e].forwardRemotePort);
          fprintf(fp,"\t\t\t\t<remote_forwarding_port_clientside>%d</remote_forwarding_port_clientside>\n",opts->entries->entries[e].forwardRemotePortC);
          fprintf(fp,"\t\t\t\t<forward_x11>%s</forward_x11>\n",opts->entries->entries[e].forwardX11);
          fprintf(fp,"\t\t\t\t<forward_authentication>%s</forward_authentication>\n",opts->entries->entries[e].forwardAuth);
          fprintf(fp,"\t\t\t\t<allow_remote_connection>%s</allow_remote_connection>\n",STR_TRUE(opts->entries->entries[e].allowRemoteConnect));
          fprintf(fp,"\t\t\t\t<disable_kerberos_forwarding>%s</disable_kerberos_forwarding>\n",STR_TRUE(opts->entries->entries[e].disableKerberosForwarding));
          fprintf(fp,"\t\t\t\t<cipher_type>%s</cipher_type>\n",opts->entries->entries[e].cipherType);
          fprintf(fp,"\t\t\t\t<custom_escape_enabled>%s</custom_escape_enabled>\n",STR_TRUE(opts->entries->entries[e].useCustomEscape));
          fprintf(fp,"\t\t\t\t<custom_escape>%s</custom_escape>\n",opts->entries->entries[e].customEscape);
          fprintf(fp,"\t\t\t\t<identity_file>%s</identity_file>\n",opts->entries->entries[e].identityFile);
          fprintf(fp,"\t\t\t\t<use_nonprivileged_port>%s</use_nonprivileged_port>\n",STR_TRUE(opts->entries->entries[e].useNonPrivilegedPort));
          fprintf(fp,"\t\t\t\t<allocate_pseudotty>%s</allocate_pseudotty>\n",opts->entries->entries[e].allocatePseudoTTY);
          fprintf(fp,"\t\t\t\t<compression>%s</compression>\n",STR_TRUE(opts->entries->entries[e].compress));
          fprintf(fp,"\t\t\t\t<custom_options>%s</custom_options>\n",opts->entries->entries[e].customOptions);
          fprintf(fp,"\t\t\t\t<fork_to_background>%s</fork_to_background>\n",STR_TRUE(opts->entries->entries[e].forkToBg));
          fprintf(fp,"\t\t\t\t<devnull>%s</devnull>\n",STR_TRUE(opts->entries->entries[e].devNull));
          fprintf(fp,"\t\t\t\t<execcmd>%s</execcmd>\n",STR_TRUE(opts->entries->entries[e].execCmd));
          fprintf(fp,"\t\t\t\t<remotecmd>%s</remotecmd>\n",opts->entries->entries[e].remoteCmd);
          fprintf(fp,"\t\t\t\t</entry>\n");
        }
      }
    fprintf(fp,"\t</entries>\n</gssh>\n");
  fclose(fp);
  conf_changed=0;
  return 0;
}

/* Import host settings from kssh */
int importkssh(char *filename,Options *opts,gboolean append) {
  FILE *kssh;
  gchar line[256];
  char listMode=0;
  gchar **args;
  SSH_Entry *entry=NULL;
  GSList *entries=NULL;
  GSList *curEntry=NULL;
  int numEntries=0,offset=0;
  #ifdef DEBUG
  char debug_oldListMode=0;
  #endif
  kssh = fopen(filename,"r");
  if(!kssh) {
    fprintf(stderr,"Error ! Unable to open file %s for reading !\n",filename);
    return 1;
  }
  while(fgets(line,256,kssh)) {
    #ifdef DEBUG
    if(debug_oldListMode!=listMode)
      printf("debug: list mode changed to %d\n",listMode);
    //printf("debug: %s",line);
    debug_oldListMode=listMode;
    #endif
    /* Different list modes */
    if(strstr(line,"User List")) {listMode=1; continue;} /* Host and usernames are added here */
    if(strstr(line,"Host List")) {listMode=2; continue;} /* This mode is never used */
    if(strstr(line,"Port List")) {listMode=3; continue;}
    if(strstr(line,"PortNum List")) {listMode=4; continue;}
    /*** User list ***/
    if(listMode==1) {
      args=g_strsplit(g_strstrip(line),"=",2);
      #ifdef DEBUG
      printf("Debug: User List mode: %s is %s\n",args[0],args[1]);
      #endif
      // Append user and hostname
      entry=malloc(sizeof(SSH_Entry));
      initExtra(entry);
      entry->hostname=args[0];
      entry->username=args[1];
      entries=g_slist_append(entries,entry);
      numEntries++;
    } /*** Port list ***/
    else if(listMode==3) {
      args=g_strsplit(g_strstrip(line),"=",2);
      #ifdef DEBUG
      printf("Debug: usePort List mode: %s is %s\n",args[0],args[1]);
      #endif
      curEntry=seekData(0,entries,args[0]);
      if(curEntry==NULL) {
        #ifdef DEBUG
        printf("Debug: usePort List mode: did not find host %s\n",args[0]);
        #endif
        continue;
      }
      ((SSH_Entry*)curEntry->data)->usePort=!strcmp(args[1],"true");
    } else if(listMode==4) {     /*** Port list ***/
      args=g_strsplit(g_strstrip(line),"=",2);
      #ifdef DEBUG
      printf("Debug: Port List mode: %s is %s\n",args[0],args[1]);
      #endif
      curEntry=seekData(0,entries,args[0]);
      if(curEntry==NULL) {
        #ifdef DEBUG
        printf("Debug: usePort List mode: did not find host %s\n",args[0]);
        #endif
        continue;
      }
      ((SSH_Entry*)curEntry->data)->port=atoi(args[1]);
    }
  }
  fclose(kssh);
  // Add data to the Options structure
  if(append==1) {
    offset=opts->entries->numEntries;
    opts->entries->numEntries=offset+numEntries;
    opts->entries->entries = realloc(opts->entries->entries,(sizeof(SSH_Entry)*opts->entries->numEntries));
    if(opts->entries->entries==NULL) {
      fprintf(stderr,"Realloc error !\n");
      exit(1);
    }
  } else {
    offset=0;
    opts->entries->numEntries=numEntries;
    if(opts->entries->entries) free(opts->entries->entries);
    opts->entries->entries = malloc(sizeof(SSH_Entry)*numEntries);
    if(opts->entries->entries==NULL) {
      fprintf(stderr,"Malloc error !\n");
      exit(1);
    }
  }
  while(entries!=NULL) {
    opts->entries->entries[offset]=*(SSH_Entry*)entries->data;
    entries=entries->next;
    offset++;
  }
  return 0;
}

/* Initialize the extra settings in SSH_Entry structure */
void initExtra(SSH_Entry *initThis) {
  initThis->useExtra=0; /* By default, we do not use the extra features */
  initThis->forwardLocal=0;
  initThis->forwardLocalDest="";
  initThis->forwardLocalPort=0;
  initThis->forwardLocalPortC=0;
  initThis->forwardRemote=0;
  initThis->forwardRemoteDest="";
  initThis->forwardRemotePort=0;
  initThis->forwardRemotePortC=0;
  initThis->forwardX11="Default";
  initThis->forwardAuth="Default";
  initThis->allowRemoteConnect=FALSE;
  initThis->disableKerberosForwarding=FALSE;

  initThis->cipherType="Default";
  initThis->useCustomEscape=FALSE;
  initThis->customEscape="~";
  initThis->identityFile="";
  initThis->useNonPrivilegedPort=FALSE;
  initThis->allocatePseudoTTY="Default";
  initThis->compress=FALSE;
  initThis->customOptions="";

  initThis->forkToBg=FALSE;
  initThis->devNull=FALSE;
  initThis->execCmd=FALSE;
  initThis->remoteCmd="";
}

/* Internal, used by importkssh() */
GSList *seekData(char field,GSList *lst,gchar *data) {
switch(field) {
  case 0: /* hostname */
    while(lst!=NULL) {
//      printf("-> %s\n",((SSH_Entry*)lst->data)->hostname);
      if(strcmp((((SSH_Entry*)lst->data)->hostname),data)==0) break;
      lst=lst->next;
    }
    break;
 case 1: /* username */
    while(lst!=NULL) {
      if(strcmp((((SSH_Entry*)lst->data)->username),data)==0) break;
      lst=lst->next;
    }
    break;
}
return lst;
}

/* Internal, used by loadSettings() */
Options *parseOpts(xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur) {
  Options *ret=NULL;
  #ifdef DEBUG
  printf("debug: parseOpts\n");
  #endif
  ret = (Options*)malloc(sizeof(Options));
  if(ret==NULL) {
    fprintf(stderr,"Error ! Out of memory !\n");
    return NULL;
  }
  memset(ret,0,sizeof(Options));
  cur=cur->childs;
  while(cur!=NULL) {
    #ifdef DEBUG
    printf("debug: parseOpts: %s = %s\n",cur->name,xmlNodeListGetString(doc,cur->childs,1));
    #endif
    if(!strcmp(cur->name,"save_host_name") && (cur->ns==ns))
      ret->save_host_name=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"save_host_settings") && (cur->ns==ns))
      ret->save_host_settings=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"use_host_settings") && (cur->ns==ns))
      ret->use_host_settings=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"ssh_cmd") && (cur->ns==ns))
      ret->ssh_cmd=xmlNodeListGetString(doc,cur->childs,1);
    else if(!strcmp(cur->name,"term_cmd") && (cur->ns==ns))
      ret->term_cmd=xmlNodeListGetString(doc,cur->childs,1);
    cur=cur->next;
  }
  return ret;
}

/* Internal, used by loadSettings() */
EntryList *parseEntries(xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur) {
  EntryList *ret=NULL;
  int pos=0;
  #ifdef DEBUG
  printf("debug: parseEntries\n");
  #endif
  ret = (EntryList*)malloc(sizeof(EntryList));
  if(ret==NULL) {
    fprintf(stderr,"Error ! Out of memory !\n");
    return NULL;
  }
  memset(ret,0,sizeof(EntryList));
  cur=cur->childs;
  while(cur!=NULL) {
    #ifdef DEBUG
    printf("debug: parseEntries: %s = %s\n",cur->name,xmlNodeListGetString(doc,cur->childs,1));
    #endif
    if(!strcmp(cur->name,"entry_count") && (cur->ns==ns)) {
      ret->numEntries=atoi(xmlNodeListGetString(doc,cur->childs,1));
      ret->entries = (SSH_Entry*)malloc(sizeof(SSH_Entry)*ret->numEntries);
      if(ret->entries==NULL) {
        fprintf(stderr,"Error ! Out of memory !\n");
        return NULL;
      }
    }
    else if(!strcmp(cur->name,"entry") && (cur->ns==ns)) {
      ret->entries[pos]=*parseEntry(doc,ns,cur);
      pos++;
    }
    cur=cur->next;
  }
  return ret;
}

SSH_Entry *parseEntry(xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur) {
  SSH_Entry *ret;
  #ifdef DEBUG
  printf("debug: parseEntry\n");
  #endif
  ret = (SSH_Entry*)malloc(sizeof(SSH_Entry));
  if(ret==NULL) {
    fprintf(stderr,"Error ! Out of memory !\n");
    return NULL;
  }
  memset(ret,0,sizeof(SSH_Entry));
  initExtra(ret);
  cur=cur->childs;
  while(cur!=NULL) {
    #ifdef DEBUG
    printf("debug: parseEntry: %s = %s\n",cur->name,xmlNodeListGetString(doc,cur->childs,1));
    #endif
    if(!strcmp(cur->name,"hostname") && (cur->ns==ns))
      ret->hostname=xmlNodeListGetString(doc,cur->childs,1);
    else if(!strcmp(cur->name,"username") && (cur->ns==ns))
      ret->username=xmlNodeListGetString(doc,cur->childs,1);
    else if(!strcmp(cur->name,"useport") && (cur->ns==ns))
      ret->usePort=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"port") && (cur->ns==ns))
      ret->port=atoi(xmlNodeListGetString(doc,cur->childs,1));
    else if(!strcmp(cur->name,"use_extra") && (cur->ns==ns))
      ret->useExtra=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"local_forwarding") && (cur->ns==ns))
      ret->forwardLocal=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"local_forwarding_port_clientside") && (cur->ns==ns))
      ret->forwardLocalPortC=atoi(xmlNodeListGetString(doc,cur->childs,1));
    else if(!strcmp(cur->name,"local_forwarding_destination") && (cur->ns==ns))
      ret->forwardLocalDest=xmlNodeListGetString(doc,cur->childs,1);
    else if(!strcmp(cur->name,"local_forwarding_port") && (cur->ns==ns))
      ret->forwardLocalPort=atoi(xmlNodeListGetString(doc,cur->childs,1));
    else if(!strcmp(cur->name,"remote_forwarding") && (cur->ns==ns))
      ret->forwardRemote=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"remote_forwarding_port_clientside") && (cur->ns==ns))
      ret->forwardRemotePortC=atoi(xmlNodeListGetString(doc,cur->childs,1));
    else if(!strcmp(cur->name,"remote_forwarding_destination") && (cur->ns==ns))
      ret->forwardRemoteDest=xmlNodeListGetString(doc,cur->childs,1);
    else if(!strcmp(cur->name,"remote_forwarding_port") && (cur->ns==ns))
      ret->forwardRemotePort=atoi(xmlNodeListGetString(doc,cur->childs,1));
    else if(!strcmp(cur->name,"forward_x11") && (cur->ns==ns))
      ret->forwardX11=xmlNodeListGetString(doc,cur->childs,1);
    else if(!strcmp(cur->name,"forward_authentication") && (cur->ns==ns))
      ret->forwardAuth=xmlNodeListGetString(doc,cur->childs,1);
    else if(!strcmp(cur->name,"allow_remote_connection") && (cur->ns==ns))
      ret->allowRemoteConnect=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"disable_kerberos_forwarding") && (cur->ns==ns))
      ret->disableKerberosForwarding=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"cipher_type") && (cur->ns==ns))
      ret->cipherType=xmlNodeListGetString(doc,cur->childs,1);
    else if(!strcmp(cur->name,"custom_escape_enabled") && (cur->ns==ns))
      ret->useCustomEscape=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"custom_escape") && (cur->ns==ns))
      ret->customEscape=xmlNodeListGetString(doc,cur->childs,1);
    else if(!strcmp(cur->name,"identity_file") && (cur->ns==ns))
      ret->identityFile=xmlNodeListGetString(doc,cur->childs,1);
    else if(!strcmp(cur->name,"use_nonprivileged_port") && (cur->ns==ns))
      ret->useNonPrivilegedPort=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"allocate_pseudotty") && (cur->ns==ns))
      ret->allocatePseudoTTY=xmlNodeListGetString(doc,cur->childs,1);
    else if(!strcmp(cur->name,"compression") && (cur->ns==ns))
      ret->compress=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"custom_options") && (cur->ns==ns))
      ret->customOptions=xmlNodeListGetString(doc,cur->childs,1);
    else if(!strcmp(cur->name,"fork_to_background") && (cur->ns==ns))
      ret->forkToBg=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"devnull") && (cur->ns==ns))
      ret->devNull=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"execcmd") && (cur->ns==ns))
      ret->execCmd=!strcmp(xmlNodeListGetString(doc,cur->childs,1),"True");
    else if(!strcmp(cur->name,"remotecmd") && (cur->ns==ns))
      ret->remoteCmd=xmlNodeListGetString(doc,cur->childs,1);
    cur=cur->next;
  }
  return ret;
}

