/* file rdPrtDesc.c
 *
 * read printer description file and fill the
 * printer knowledge data base
 *
 *  Copyrights: Jean-Jacques Sarton  j.sarton@t-online.de
 */
/*
 *
 * 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 of the License, 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "model.c"

#ifndef BLD_UTIL
#define BLD_UTIL 1
#endif

/* a few define for the type of description entry */
/* and the description for the diferent entries   */
#define D_BEGIN 1
#define A_STR   2
#define A_NB    3
#define A_PROT  4
#define A_FLAG  5
#define A_FNAME 6
#define D_END   7

#define OFFSET(a) ((int)(&((configData_t*)0)->a))

typedef struct attr_s
{
   char *attrName;
   int   attrType;
   int   attrOffset;
} attr_t;

static attr_t attr[] =
{
   { ".PRINTER",              D_BEGIN,  -1                       },
   { ".name:",                A_STR,    OFFSET(name)             },
   { ".colorsNb:",            A_NB,     OFFSET(colors)           },
   { ".mainProt:",            A_PROT,   OFFSET(prot)             },
   { ".stateFlg:",            A_FLAG,   OFFSET(state)            },
   { ".exchangeFlg:",         A_FLAG,   OFFSET(exchange)         },
   { ".exchangeSeparateFlg:", A_FLAG,   OFFSET(exchangeSeparate) },
   { ".cleanSeparateFlg:",    A_FLAG,   OFFSET(cleanSeparate)    },
   { ".resetProt:",           A_PROT,   OFFSET(reset)            },     
   { ".alignProt:",           A_PROT,   OFFSET(align)            },        
   { ".idFlg:",               A_FLAG,   OFFSET(id)               },
   { ".passesNb:",            A_NB,     OFFSET(passes)           },
   { ".choicesNb:",           A_NB,     OFFSET(choices)          },
   { ".colorPassesNb:",       A_NB,     OFFSET(color_passes)     },
   { ".colorChoicesNb:",      A_NB,     OFFSET(color_choices)    },
   { ".checkNozzleNeedReset:",A_FLAG,   OFFSET(checkNeedReset)   },
   { ".alignFunctionName:",   A_FNAME,  OFFSET(alignFunction)    },
   { ".END",                  D_END,    -1                       },  
   { NULL,                                                       }
};

/* help structures for accessing the members of the original structur */
/* via the structur base address and an offset for the menbers        */

typedef struct s_s
{
   char *s;
} s_t;

typedef struct i_s
{
   int i;
} i_t;

#define ATTR_NO (sizeof(attr)/sizeof(attr_t) - 3)

configData_t *configData    = NULL;
int           configEntries = 0;

/*******************************************************************/
/* Function skipSpace                                              */
/*                                                                 */
/* return the a pointer to the firdst non space character          */
/*                                                                 */
/*******************************************************************/

static char *skipSpace(char *s)
{
   while ( *s && (*s==' '||*s=='\t'||*s=='\r'|| *s=='\n') )
      s++;
   return s;
}

/*******************************************************************/
/* Function endFName                                               */
/*                                                                 */
/* skip over non space characters and set the first space char     */
/* to '\0'                                                         */
/*                                                                 */
/*******************************************************************/

static void endFName(char *s)
{
   while(*s && !(*s==' '||*s=='\t'||*s=='\r'|| *s=='\n'))
      s++;
   *s = '\0';
}

/*******************************************************************/
/* Function endStr                                                 */
/*                                                                 */
/* eliminate trailing space characters from the end of the line    */
/*                                                                 */
/*******************************************************************/

static void endStr(char *s)
{
   while(s[1])
      s++;
   while (*s==' '||*s=='\t'||*s=='\r'|| *s=='\n')
      *s-- = '\0';
}

/*******************************************************************/
/* Function getAttr                                                */
/*                                                                 */
/* retriewe the attribute description for the given key            */
/*                                                                 */
/*******************************************************************/

static attr_t *getAttr(char **s)
{
   attr_t *a = attr;
   int     len;

   while(a->attrName)
   {
      if ( strncmp(a->attrName, *s, (len = strlen(a->attrName))) == 0 )
      {
         *s += len;
         if ( **s )
         *s = skipSpace(*s);
         return a;
      }
      a++;
   }
   return NULL;
}

/*******************************************************************/
/* Function readDataFile                                           */
/*                                                                 */
/* read the description file and build an array of printer desc.   */
/*                                                                 */
/*******************************************************************/

static int readDataFile(FILE *fp)
{
   char          buf[2048];
   int           attrNb = 0;
   char         *s;
   s_t          *ss;
   i_t          *is;
   attr_t       *a;
   configData_t *c = NULL; /* make gcc happy */
   int           line = 0;

   while (fgets(buf,sizeof(buf), fp))
   {
      line++;
      s = skipSpace(buf);
      if ( *s != '.' )
      {
         /* this may be an empty or comment line */
         continue;
      }
      else
      {
         a = getAttr(&s);
         if ( a )
         {
            if ( *s == '\0' && a->attrOffset != -1 )
            {
               fprintf(stderr,"error at line %d\n",line++);
               return 0;
            }
            else
            {
               if ( a->attrType == D_END && attrNb != ATTR_NO )
               {
                  fprintf(stderr,
                          "incomplete printer description at line %d\n",line);
                  return 0;
               }
               else if ( a->attrType == D_BEGIN && attrNb != 0 )
               {
                  fprintf(stderr,
                          "Printer description not closed at line %d\n",line);
                  return 0;
                  
               }
               else if ( a->attrType == D_BEGIN )
               {
                  /* alloc storage for this */
                  if ( configData == NULL )
                  {
                     configData = (configData_t*)calloc(sizeof(configData_t), 1);
                  }
                  else
                  {
                     configData = (configData_t*)realloc(configData, sizeof(configData_t)*(configEntries+1));
                     if ( configData )
                        memset(&configData[configEntries], 0, sizeof(configData_t));
                  }
                  if ( configData == NULL )
                  {
                     fprintf(stderr,"no enough memory\n");
                     return 0;
                  }
                  c = &configData[configEntries];
               }
               else if ( a->attrType == D_END && attrNb == ATTR_NO )
               {
                  configEntries++;
                  attrNb = 0;
               }
               else
               {
                  is = (void*)((char*)c + a->attrOffset);
                  ss = (void*)((char*)c + a->attrOffset);
                  is->i = 0;
                  ss->s = NULL;
                  switch(a->attrType)
                  {
                     case A_FLAG:
                        if ( *s == '0' || strncasecmp("false",s,5) == 0 )
                          is->i = 0;
                        else if ( *s == '1' || strncasecmp("true",s,4) == 0 )
                          is->i = 1;
                        else
                        {
                           fprintf(stderr,"Wrong value at line %d\n",line);
                           return 0;
                        }
                     break;
                     case A_NB:
                        if ( (*s == '-' && s[1] >= '0' && s[1] <= '9') ||
                             (*s >= '0' && *s <= '9') )
                        {
                           is->i = atoi(s);
                        }
                        else
                        {
                           fprintf(stderr,"Wrong value at line %d\n",line);
                           return 0;
                        }
                     break;
                     case A_PROT:
                        while(*s)
                        {
                           if ( strncmp(s, "D4", 2) == 0 )
                           {
                              is->i |= PROT_D4;
                              s += 2;
                           }
                           else if ( strncmp(s, "EXD4", 4) == 0 )
                           {
                              is->i |= PROT_NEW;
                              s += 4;
                           }
                           else if ( strncmp(s, "OLD", 3) == 0 )
                           {
                              is->i |= PROT_OLD;
                              s += 3;
                           }
                           s = skipSpace(s);
                        }
                     break;
                     case A_FNAME:
                        endFName(s);
                        if ( *s != '-' )
                           ss->s = strdup(s);
                     break;
                     case A_STR:
                        endStr(s);
                        ss->s = strdup(s);
                     break;
                  }
                  attrNb++;
               }
            }
         }
      }
   }

   return 1;
}

/*******************************************************************/
/* Function mStrCmp()                                              */
/*                                                                 */
/* Compare string regardless of case, consider number as numvber   */
/* and not a single character.                                     */
/*                                                                 */
/*******************************************************************/

int _mStrCmp(char *s, char *t)
{
   int ret = 0;
   while ( !ret && *s )
   {
      if ( isdigit(*s) && isdigit(*t) )
      {
         ret = atoi(s) - atoi(t);
         while ( isdigit(*s) ) s++;
         while ( isdigit(*t) ) t++;
      }
      else
      {
         ret = tolower(*s) - tolower(*t);
         s++;
         t++;
      }
   }
   return ret;
}

/*******************************************************************/
/* Function cmpName()                                              */
/*                                                                 */
/* help function for sorting of printer descriptions               */
/*                                                                 */
/*******************************************************************/

static int cmpName(const void *P1, const void *P2)
{
   configData_t *p1 = (configData_t *)P1;
   configData_t *p2 = (configData_t *)P2;

   if ( p1->name[0] == '?' )
   {
      return  1;
   }
   if ( p2->name[0] == '?' )
   {
      return -1;
   }
   return _mStrCmp(p1->name, p2->name);
}

/*******************************************************************/
/* Function mergeDescs()                                           */
/*                                                                 */
/* merge the buildin and the external datas                        */
/*                                                                 */
/*******************************************************************/

static int mergeDescs()
{
   int i, j;

   for ( i = 0; i < defaultConfigDataSize; i++ )
   {
      /* if the printer is allready externaly described */
      /* don't add this                                 */
      for ( j = 0; j < configEntries; j++ )
      {
         if ( strcmp(configData[j].name, defaultConfigData[i].name) == 0 )
            break;
      }
      if ( j == configEntries )
      {
         /* printer not found, add an array element */
         if ( configData == NULL )
         {
            configData = (configData_t*)malloc(sizeof(configData_t));
         }
         else
         {
            configData = (configData_t*)realloc(configData,
                                 sizeof(configData_t)*(configEntries+1));
         }
         if ( configData == NULL )
         {
             fprintf(stderr,"no enough memory\n");
             configEntries = 0;
             return 0;
         }
         else
         {
            memcpy(&configData[configEntries],
                   &defaultConfigData[i],
                   sizeof(configData_t));
           configEntries++;
         }
      }
   }
   
   if ( configEntries )
   {
      qsort(configData, configEntries, sizeof(configData_t), cmpName);
   }
   return 1;
}

/*******************************************************************/
/* Function readPrinterData                                        */
/*                                                                 */
/* read the description file, main entry                           */
/*                                                                 */
/*******************************************************************/

int readPrinterData()
{
   int i;
   FILE *fp = NULL;
   char *path[] =
   {
#if ! BLD_UTIL
      "/usr/lib/mtink/printer.desc",
      "/usr/local/lib/mtink/printer.desc",
      "/opt/mtink/printer.desc",
#else
      "./printer.desc"
#endif
   };
  
   for ( i = 0; i < sizeof(path)/sizeof(char*); i++ )
   {
      if ( path[i] && (fp = fopen(path[i],"r")) )
      {
         /* read the file */
         readDataFile(fp);
         fclose(fp);
         mergeDescs();
         return 1;
      }
   }
   mergeDescs();
   return 0;
}

#if BLD_UTIL

/*******************************************************************/
/* Function printData                                              */
/*                                                                 */
/* print the data as described into the file model.c               */
/*                                                                 */
/*******************************************************************/

void printData(configData_t *conf, int nb)
{
   int           i;
   attr_t       *a;
   s_t          *ss;
   i_t          *is;
   configData_t *c;
    
   for (i=0; i < nb; i++)
   {
      c = &conf[i];
      a = attr;
      while(a->attrName)
      {
         ss = (void*)((char*)c+a->attrOffset);
         is = (void*)((char*)c+a->attrOffset);
         switch(a->attrType)
         {
            case D_BEGIN: 
               printf("%s\n",a->attrName);
               break;
            case D_END:   
               printf("%s\n\n",a->attrName);
               break;
            case A_STR:   
               printf("   %-21s %s\n",a->attrName, ss->s);
               break;
            case A_NB:    
               printf("   %-21s %d\n",a->attrName, is->i);
            break;
            case A_FNAME:
               printf("   %-21s %s\n",a->attrName,  ss->s ? ss->s : "-");
            break;
            case A_FLAG:
               printf("   %-21s %s\n",a->attrName, is->i ? "True" : "False");
            break;
            case A_PROT:
               printf("   %-21s",a->attrName);
               if ( is->i & PROT_OLD )
                  printf(" OLD");
               if ( is->i & PROT_EXIT )
                  printf(" EXD4");
               if ( is->i & PROT_D4 )
                  printf(" D4");
               printf("\n");
         }
         a++;
      }
   }
}

static void genCFile(configData_t *conf, int nb)
{
   int           i;
   attr_t       *a;
   s_t          *ss;
   i_t          *is;
   configData_t *c;
   
   printf("/* file model.c\n"
          " *\n"
          " *\n"
          " List of know printers and flags\n"
          " * device and printer choice\n"
          " *\n"
          " */\n"
          "\n"
          "#include \"model.h\"\n"
          "\n"
          "configData_t defaultConfigData[] = {\n" );

   for (i=0; i < nb; i++)
   {
      c = &conf[i];
      a = attr;
      while(a->attrName)
      {
         ss = (void*)((char*)c+a->attrOffset);
         is = (void*)((char*)c+a->attrOffset);
         switch(a->attrType)
         {
            case D_BEGIN: 
               printf("   { ");
               break;
            case D_END:   
               printf("},\n");
               break;
            case A_STR:   
               printf("\"%s\",%*s ", ss->s,20-strlen(ss->s),"");
               break;
            case A_NB:    
               printf("%2d, ", is->i);
            break;
            case A_FNAME:
               if ( ss->s )
                  printf("\"%s\" ", ss->s);
               else
                  printf("NULL ");
            break;
            case A_FLAG:
               if ( is->i ) printf("True,  ");
               else         printf("False, ");
            break;
            case A_PROT:
               switch(is->i)
               {
                   case 1: printf("PROT_OLD,         "); break;
                   case 2: printf("PROT_EXIT,        "); break;
                   case 3: printf("PROT_NEW,         "); break;
                   case 4: printf("PROT_D4,          "); break;
                   case 5: printf("PROT_OLD|PROT_D4, "); break;
               }
            break;
         }
         a++;
      }
   }
   printf("};\n"
          "\n"
          "configData_t defaultConfigData[];\n"
          "int          defaultConfigDataSize = sizeof(defaultConfigData) / sizeof(configData_t);\n"
          "\n"
         );

}


/*******************************************************************/
/* Function main                                                   */
/*                                                                 */
/* This is for producing a printer description file for ttink/mtink*/
/* or generationg the c description file from an allready provided */
/* printer description file                                        */
/*                                                                 */
/*******************************************************************/

int main(int argc, char **argv)
{
   int           i;
   attr_t       *a;
   char         *t;
   configData_t *c;
   int           mode = 0;
   char         *prgName;

   prgName = strrchr(argv[0], '/');
   if ( prgName )
      prgName++;
   else
      prgName = argv[0];

   argv++;
   argc--;
   
   if ( argc == 0 || argc > 1 || argv[0][0] != '-' ||
        !(argv[0][1] == 'c' || argv[0][1] == 'p'))
   {
       fprintf(stderr, "Syntax: %s -p|-c\n\n",prgName);
       fprintf(stderr, "        with the -p option you will print a description file\n");
       fprintf(stderr, "        The -c option produce a c file from the desc. file\n");
       fprintf(stderr, "        printer.desc which must be in the working directory\n");
       fprintf(stderr, " All output are on the console\n");
       exit(1);
   }
   
   if ( argv[0][1] == 'p' )
   {
      printData(defaultConfigData,defaultConfigDataSize);
   }
   else
   {
      readPrinterData();
      fprintf(stderr,"Read %d descriptions\n",configEntries);
      if ( configData )
         genCFile(configData,configEntries);
   }
   return 0;
}
#endif
