/* $Id: cmssheet.c,v 1.6 2006/02/19 23:59:13 hvengel Exp $ */
/*  Little cms - profiler construction set */
/*  Copyright (C) 1998-2001 Marti Maria */
/* */
/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
/* */
/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
/* OF THIS SOFTWARE. */
/* */
/* This file 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. */
/* */
/* As a special exception to the GNU General Public License, if you */
/* distribute this file as part of a program that contains a */
/* configuration script generated by Autoconf, you may include it under */
/* the same distribution terms that you use for the rest of that program. */
/* */
/* Version 1.08a */


#include "lcmsprf.h"

/* #define _DEBUG  1  */



/* IT8.7 / CGATS.17-200x handling */
LCMSHANDLE      cdecl cmsxIT8Alloc(void);
void            cdecl cmsxIT8Free(LCMSHANDLE IT8);

/* Persistence */
LCMSHANDLE      cdecl cmsxIT8LoadFromFile(const char* cFileName);
LCMSHANDLE      cdecl cmsxIT8LoadFromMem(void *Ptr, size_t len);
BOOL            cdecl cmsxIT8SaveToFile(LCMSHANDLE IT8,  const char* cFileName);

/* Properties */
const char* cdecl cmsxIT8GetSheetType(LCMSHANDLE hIT8);
BOOL        cdecl cmsxIT8SetSheetType(LCMSHANDLE hIT8, const char* Type);

BOOL        cdecl cmsxIT8SetProperty(LCMSHANDLE hIT8, const char* cProp, const char *Str);
BOOL        cdecl cmsxIT8SetPropertyDbl(LCMSHANDLE hIT8, const char* cProp, double Val);

const char* cdecl cmsxIT8GetProperty(LCMSHANDLE hIT8, const char* cProp);
double      cdecl cmsxIT8GetPropertyDbl(LCMSHANDLE hIT8, const char* cProp);
int         cdecl cmsxIT8EnumProperties(LCMSHANDLE IT8, char ***PropertyNames);


/* Datasets */

BOOL        cdecl cmsxIT8GetDataSetByPos(LCMSHANDLE IT8, int col, int row, char* Val, int ValBufferLen);

BOOL        cdecl cmsxIT8GetDataSet(LCMSHANDLE IT8, const char* cPatch,
                                                const char* cSample,
                                                char* Val, int ValBufferLen);


BOOL        cdecl cmsxIT8GetDataSetDbl(LCMSHANDLE IT8, const char* cPatch, const char* cSample, double* d);

BOOL        cdecl cmsxIT8SetDataSet(LCMSHANDLE IT8, const char* cPatch,
                                                const char* cSample,
                                                char *Val);

BOOL        cdecl cmsxIT8SetDataFormat(LCMSHANDLE IT8, int n, const char *Sample);
int         cdecl cmsxIT8EnumDataFormat(LCMSHANDLE IT8, char ***SampleNames);

const char *cdecl cmsxIT8GenericPatchName(int nPatch, char* buffer);
int         cdecl cmsxIT8GenericPatchNum(const char *Name);


/* ------------------------------------------------------------- Implementation */


#define MAXID   128     /* Max lenght of identifier */
#define MAXSTR  255     /* Max lenght of string */

#ifndef NON_WINDOWS
#include <io.h>
#endif

/* Symbols */

typedef enum { SNONE,
               SINUM,      /* Integer */
               SDNUM,      /* Real */
               SIDENT,     /* Identifier */
               SSTRING,    /* string */
               SCOMMENT,   /* comment */
               SEOLN,      /* End of line */
               SEOF,       /* End of stream */
               SSYNERROR,  /* Syntax error found on stream */

               /* Keywords */

               SBEGIN_DATA,
               SBEGIN_DATA_FORMAT,
               SEND_DATA,
               SEND_DATA_FORMAT,
               SKEYWORD,
               SSTRING_SY

           } SYMBOL;


/* Linked list of variable names */

typedef struct _KeyVal {

    struct _KeyVal*  Next;
    char*            Keyword;       /* Name of variable */
    char*            Value;         /* Points to value */

   } KEYVALUE, FAR* LPKEYVALUE;

/* Linked list of values (Memory sink) */

typedef struct _OwnedMem {

    struct _OwnedMem* Next;
    void *            Ptr;          /* Point to value */

   } OWNEDMEM, FAR* LPOWNEDMEM;


/* This struct hold all information about an openened */
/* IT8 handler. Only one dataset is allowed. */

typedef struct {

    int            nSamples, nPatches;    /* Rows, Cols */
    int            SampleID;              /* Pos of ID */
    LPKEYVALUE     HeaderList;            /* The properties */
    char*          FileBuffer;            /* The ASCII stream */
    char**         DataFormat;            /* The binary stream descriptor */
    char**         Data;                  /* The binary stream */
    LPOWNEDMEM     MemorySink;            /* The storage bakend */

    /* Parser state machine */

    SYMBOL         sy;              /* Current symbol */
    int            ch;              /* Current character */
    char*          Source;          /* Points to loc. being parsed */
    int            inum;            /* integer value */
    double         dnum;            /* real value */
    char           id[MAXID];       /* identifier */
    char           str[MAXSTR];     /* string */

    /* Allowed keywords & datasets */

    LPKEYVALUE    ValidKeywords;
    LPKEYVALUE    ValidSampleID;

    char           FileName[MAX_PATH];  
    int            lineno;          /* line counter for error reporting */

	char           SheetType[MAXSTR];  /* New 1.09 */

   } IT8, FAR* LPIT8;



/* ------------------------------------------------------ IT8 parsing routines */


/* A keyword */
typedef struct {
               const char *id;
               SYMBOL sy;

               } KEYWORD;

/* The keyword->symbol translation table. Sorting is required. */
static const KEYWORD TabKeys[] = {

           {"BEGIN_DATA",          SBEGIN_DATA },
           {"BEGIN_DATA_FORMAT",   SBEGIN_DATA_FORMAT },
           {"END_DATA",            SEND_DATA},
           {"END_DATA_FORMAT",     SEND_DATA_FORMAT},
           {"KEYWORD",             SKEYWORD},
           {"STRING",              SSTRING_SY}};

#define NUMKEYS (sizeof(TabKeys)/sizeof(KEYWORD))

/* Predefined properties */
static char* PredefinedProperties[] = {

            "NUMBER_OF_FIELDS",     /* Required - NUMBER OF FIELDS */
            "NUMBER_OF_SETS",       /* Required - NUMBER OF SETS */
            "ORIGINATOR",          /* Required - Identifies the specific system, organization or individual that created the data file. */
            "CREATED",             /* Required - Indicates date of creation of the data file. */
            "DESCRIPTOR",          /* Required  - Describes the purpose or contents of the data file. */
            "DIFFUSE_GEOMETRY",    /* The diffuse geometry used. Allowed values are "sphere" or "opal". */
            "MANUFACTURER",
            "MANUFACTURE",         /* Some broken Fuji targets does store this value */
            "PROD_DATE",           /* Identifies year and month of production of the target in the form yyyy:mm. */
            "SERIAL",              /* Uniquely identifies individual physical target. */

            "MATERIAL",            /* Identifies the material on which the target was produced using a code */
                                   /* uniquely identifying th e material. Th is is intend ed to be used for IT8.7 */
                                   /* physical targets only (i.e . IT8.7/1 a nd IT8.7/2). */

            "INSTRUMENTATION",     /* Used to report the specific instrumentation used (manufacturer and */
                                   /* model number) to generate the data reported. This data will often */
                                   /* provide more information about the particular data collected than an */
                                   /* extensive list of specific details. This is particularly important for */
                                   /* spectral data or data derived from spectrophotometry. */

            "MEASUREMENT_SOURCE",  /* Illumination used for spectral measurements. This data helps provide */
                                   /* a guide to the potential for issues of paper fluorescence, etc. */

            "PRINT_CONDITIONS",    /* Used to define the ch aracteristics of the printed sheet being reported. */
                                   /* Where standard conditions have been defined (e.g., SW OP at nominal) */
                                   /* named conditions may suffice. Otherwise, detailed in formation is */
                                   /* needed. */

            "SAMPLE_BACKING",      /* Identifies the backing material used behind the sample during */
                                   // measurement. Allowed values are “black”, “white”, or "na".

            "CHISQ_DOF"            /* Degrees of freedom associated with the Chi squared statistic */
            };

#define NUMPREDEFINEDPROPS (sizeof(PredefinedProperties)/sizeof(char *))


/* Predefined sample types on dataset */
static char* PredefinedSampleID[] = {

            "CMYK_C",         /* Cyan component of CMYK data expressed as a percentage */
            "CMYK_M",         /* Magenta component of CMYK data expressed as a percentage */
            "CMYK_Y",         /* Yellow component of CMYK data expressed as a percentage */
            "CMYK_K",         /* Black component of CMYK data expressed as a percentage */
            "D_RED",          /* Red filter density */
            "D_GREEN",        /* Green filter density */
            "D_BLUE",         /* Blue filter density */
            "D_VIS",          /* Visual filter density */
            "D_MAJOR_FILTER", /* Major filter d ensity */
            "RGB_R",          /* Red component of RGB data */
            "RGB_G",          /* Green component of RGB data */
            "RGB_B",          /* Blue com ponent of RGB data */
            "SPECTRAL_NM",    /* Wavelength of measurement expressed in nanometers */
            "SPECTRAL_PCT",   /* Percentage reflectance/transmittance */
            "SPECTRAL_DEC",   /* Reflectance/transmittance */
            "XYZ_X",          /* X component of tristimulus data */
            "XYZ_Y",          /* Y component of tristimulus data */
            "XYZ_Z",          /* Z component of tristimulus data */
            "XYY_X"           /* x component of chromaticity data */
            "XYY_Y",          /* y component of chromaticity data */
            "XYY_CAPY",       /* Y component of tristimulus data */
            "LAB_L",          /* L* component of Lab data */
            "LAB_A",          /* a* component of Lab data */
            "LAB_B",          /* b* component of Lab data */
            "LAB_C",          /* C*ab component of Lab data */
            "LAB_H",          /* hab component of Lab data */
            "LAB_DE"          /*  CIE dE */
            "LAB_DE_94",      /*  CIE dE using CIE 94 */
            "LAB_DE_CMC",     /*  dE using CMC */
            "LAB_DE_2000",    /* CIE dE using CIE DE 2000 */
            "MEAN_DE",        /* Mean Delta E (LAB_DE) of samples compared to batch average */
                              /* (Used for data files for ANSI IT8.7/1 and IT8.7/2 targets) */
            "STDEV_X",        /* Standard deviation of X (tristimulus data) */
            "STDEV_Y",        /* Standard deviation of Y (tristimulus data) */
            "STDEV_Z",        /* Standard deviation of Z (tristimulus data) */
            "STDEV_L",        /* Standard deviation of L* */
            "STDEV_A"         /* Standard deviation of a* */
            "STDEV_B",        /* Standard deviation of b* */
            "STDEV_DE",       /* Standard deviation of CIE dE */
            "CHI_SQD_PAR"};   /* The average of the standard deviations of L*, a* and b*. It is */
                              /* used to derive an estimate of the chi-squared parameter which is */
                              /* recommended as the predictor of the variability of dE */

#define NUMPREDEFINEDSAMPLEID (sizeof(PredefinedSampleID)/sizeof(char *))


/* Checks whatsever if c is a valid identifier middle char. */
static
BOOL isidchar(int c)
{
   return (isalnum(c) || c == '$' || c == '%' || c == '&' || c == '/' || c == '.' || c == '_');

}

/* Checks whatsever if c is a valid identifier first char. */
static
BOOL isfirstidchar(int c)
{
        return !isdigit(c) && isidchar(c);
}

/* Checks if c is a separator */
static
BOOL isseparator(int c)
{
        return (c == ' ' || c == '\t' || c == '\r');
}

/* a replacement for strupr(), just for compatiblity sake */

static
void xstrupr(char *cp)
{
        for (;*cp;cp++)
                if (*cp >= 'a' && *cp <= 'z')
                        *cp += 'A'-'a';
}

/* A replacement for (the nonstandard) filelenght */

static
int xfilelength(int fd)
{
#ifdef _MSC_VER
		return _filelength(fd);
#else
        struct stat sb;
        if (fstat(fd, &sb) < 0)
                return(-1);
        return(sb.st_size);
#endif


}

static
BOOL SynError(LPIT8 it8, const char *Txt, ...)
{
        char Buffer[256], ErrMsg[1024];
        va_list args;

        va_start(args, Txt);
        vsprintf(Buffer, Txt, args);
        va_end(args);

        sprintf(ErrMsg, "%s: Line %d, %s", it8->FileName, it8->lineno, Buffer);
        it8->sy = SSYNERROR;
        // cmsSignalError(LCMS_ERRC_WARNING, ErrMsg);
        return FALSE;
}

static
BOOL Check(LPIT8 it8, SYMBOL sy, const char* Err)
{
        if (it8 -> sy != sy)
                return SynError(it8, Err);
        return TRUE;
}



/* Read Next character from stream */
static
void NextCh(LPIT8 it8)
{
        it8->ch = *it8->Source;
        if (it8->ch) it8->Source++;
}


/* Try to see if current identifier is a keyword, if so return the referred symbol */
static
SYMBOL BinSrchKey(const char *id)
{
        int l = 1;
        int r = NUMKEYS;
        int x, res;

        while (r >= l)
        {
                x = (l+r)/2;
                res = strcmp(id, TabKeys[x-1].id);
                if (res == 0) return TabKeys[x-1].sy;
                if (res < 0) r = x - 1;
                else l = x + 1;
        }

        return SNONE;
}


/* 10 ^n */
static
double pow10(int n)
{
    return pow(10, n);
}


/*  Reads a Real number, tries to follow from integer number */
static
void ReadReal(LPIT8 it8, int inum)
{
        it8->dnum = (double) inum;

        while (isdigit(it8->ch)) {

        it8->dnum = it8->dnum * 10.0 + (it8->ch - '0');
        NextCh(it8);
        }

        if (it8->ch == '.') {        /* Decimal point */

                double frac = 0.0;      /* fraction */
                int prec = 0;           /* precission */

                NextCh(it8);               /* Eats dec. point */

                while (isdigit(it8->ch)) {

                        frac = frac * 10.0 + (it8->ch - '0');
                        prec++;
                        NextCh(it8);
                }

                it8->dnum = it8->dnum + (frac / pow10(prec));
        }

        /* Exponent, example 34.00E+20 */
        if (toupper(it8->ch) == 'E') {

                int e;
                int sgn;

                NextCh(it8); sgn = 1;

                if (it8->ch == '-') {

                        sgn = -1; NextCh(it8);
                }
                else
                if (it8->ch == '+') {

                        sgn = +1;
                        NextCh(it8);
                }


                e = 0;
                while (isdigit(it8->ch)) {

                        if ((double) e * 10L < INT_MAX)
                            e = e * 10 + (it8->ch - '0');

                        NextCh(it8);
                }

                e = sgn*e;

                it8 -> dnum = it8 -> dnum * pow10(e);
        }
}



/* Reads next symbol */
static
void InSymbol(LPIT8 it8)
{
        register char *idptr;
        register int k;
        SYMBOL key;
        int sng;

        do {

        while (isseparator(it8->ch))
                                NextCh(it8);

        if (isfirstidchar(it8->ch)) {          /* Identifier */


                k = 0;
                idptr = it8->id;

                do {

                if (++k < MAXID) *idptr++ = (char) it8->ch;

                NextCh(it8);

                } while (isidchar(it8->ch));

                *idptr = '\0';
                xstrupr(it8->id);

                key = BinSrchKey(it8->id);
                if (key == SNONE) it8->sy = SIDENT;
                else it8->sy = key;

        }
        else                         /* Is a number? */
        if (isdigit(it8->ch) || it8->ch == '.' || it8->ch == '-' || it8->ch == '+')
        {
        int sign = 1;

        if (it8->ch == '-') {
                sign = -1;
                NextCh(it8);
                }

        it8->inum = 0;
        it8->sy   = SINUM;

        while (isdigit(it8->ch))
        {
                if ((long) it8->inum * 10L > (long) INT_MAX)
                {
                ReadReal(it8, it8->inum);
                it8->sy = SDNUM;
                it8->dnum *= sign;
                return;
                }

        it8->inum = it8->inum * 10 + (it8->ch - '0');
        NextCh(it8);
        }

        if (it8->ch == '.') {

                ReadReal(it8, it8->inum);
                it8->sy = SDNUM;
                it8->dnum *= sign;
                return;
                }

        it8 -> inum *= sign;
        return;

        }
        else
        switch ((int) it8->ch) {

        case '\0':
        case '\x1a':
                    it8->sy = SEOF;
                    break;



        case '\n':
                  NextCh(it8);
                  it8->sy = SEOLN;
                  it8->lineno++;
                  break;

        /* Comment */

        case '#':
                 NextCh(it8);
                 while (it8->ch && it8->ch != '\n')
             NextCh(it8);

                 it8->sy = SCOMMENT;
                 break;

        /* String. I will support \", \n, \t and \\. */
    /* But otherwise I hardly doubt these will be used ... */

        case '\'':
        case '\"':
                  idptr = it8->str;
                  sng = it8->ch;
                  k = 0;
                  NextCh(it8);

                  while (k < MAXSTR && it8->ch != sng) {

                  if (it8->ch == '\n'|| it8->ch == '\r') k = MAXSTR+1;
                  else {

                        if (it8->ch == '\\')
                        {
                                NextCh(it8);

                                switch (it8->ch) {

                                case 'n': *idptr++ = '\n'; break;
                                case 'r': *idptr++ = '\r'; break;
                                case 't': *idptr++ = '\t'; break;
                                case '\\': *idptr++ = '\\'; break;

                                default:
                                        *idptr++ = (char) it8->ch;
                                }

                                NextCh(it8);
                        }
                        else
            {
                        *idptr++ = (char) it8->ch;
                        NextCh(it8);
                        }

                  k++;
          }
                  }

                  it8->sy = SSTRING;
                  *idptr = '\0';
                  NextCh(it8);
                  break;


        default:
                it8->sy = SSYNERROR;
                NextCh(it8);

       }

    } while (it8->sy == SCOMMENT);
}

/* Checks end of line separator */
static
BOOL CheckEOLN(LPIT8 it8)
{
        if (!Check(it8, SEOLN, "Expected separator")) return FALSE;
        while (it8 -> sy == SEOLN)
                        InSymbol(it8);
        return TRUE;

}

/* Skip a symbol */

static
void Skip(LPIT8 it8, SYMBOL sy)
{
        if (it8->sy == sy && it8->sy != SEOF)
                        InSymbol(it8);
}

/* Returns a string holding current value */
static
BOOL GetVal(LPIT8 it8, char* Buffer)
{
    switch (it8->sy) {

    case SIDENT:  strncpy(Buffer, it8->id, MAXID-1); break;
    case SINUM:   sprintf(Buffer, "%d", it8 -> inum); break;
    case SDNUM:   sprintf(Buffer, "%g", it8 -> dnum); break;
    case SSTRING: strncpy(Buffer, it8->str, MAXSTR-1); break;


    default:
         return SynError(it8, "Sample data expected");
    }

     return TRUE;
}

/* ---------------------------------------------------------- Memory management */



/* Frees an allocator and owned memory */
void cmsxIT8Free(LCMSHANDLE hIT8)
{
   LPIT8 it8 = (LPIT8) hIT8;

    if (it8 == NULL)
        return;

    if (it8->MemorySink) {

        LPOWNEDMEM p;
        LPOWNEDMEM n;

        for (p = it8->MemorySink; p != NULL; p = n) {

            n = p->Next;
            if (p->Ptr) free(p->Ptr);
            free(p);
        }
    }

    if (it8->FileBuffer)
        free(it8->FileBuffer);

    free(it8);
}


/* Allocates a chunk of data, keep linked list */
static
void* AllocChunk(LPIT8 it8, size_t size)
{
   LPOWNEDMEM ptr1;
   void* ptr = malloc(size);

        if (ptr) {

                ZeroMemory(ptr, size);
                ptr1 = (LPOWNEDMEM) malloc(sizeof(OWNEDMEM));

                if (ptr1 == NULL) {

                    free(ptr);
                    return NULL;
                }

                ZeroMemory(ptr1, sizeof(OWNEDMEM));

                ptr1-> Ptr        = ptr;
                ptr1-> Next       = it8 -> MemorySink;
                it8 -> MemorySink = ptr1;
        }

        return ptr;
}


/* Allocates a string */
static
char *AllocString(LPIT8 it8, const char* str)
{
    int Size = strlen(str)+1;
    char *ptr;
    ptr = (char *) AllocChunk(it8, Size);
    if (ptr) strncpy (ptr, str, Size);
    return ptr;
}  

/* Searches through linked list */

static
BOOL IsAvailableOnList(LPKEYVALUE p, const char* Key, LPKEYVALUE* LastPtr)
{
    for (;  p != NULL; p = p->Next) {

        if (LastPtr) *LastPtr = p;
        if (stricmp(Key, p->Keyword) == 0)
                                    return TRUE;
    }

    return FALSE;
}



/* Add a property into a linked list */
static
BOOL AddToList(LPIT8 it8, LPKEYVALUE* Head, const char *Key, const char* Value)
{
    LPKEYVALUE p;
    LPKEYVALUE last;


    /* Check if property is already in list (this is an error) */

    if (IsAvailableOnList(*Head, Key, &last)) {
                    cmsSignalError(LCMS_ERRC_ABORTED, "duplicate key <%s>", Key);
        return FALSE;
    }

        /* Allocate the container */
    p = (LPKEYVALUE) AllocChunk(it8, sizeof(KEYVALUE));
    if (p == NULL)
    {
        cmsSignalError(LCMS_ERRC_ABORTED, "AddToList: out of memory");
        return FALSE;
    }

    /* Store name and value */
    p->Keyword = AllocString(it8, Key);

    if (Value)
        p->Value   = AllocString(it8, Value);
    else
        p->Value   = NULL;

    p->Next    = NULL;

    /* Keep the container in our list */
    if (*Head == NULL)
        *Head = p;
    else
        last->Next = p;

    return TRUE;
}

static
BOOL AddAvailableProperty(LPIT8 it8, const char* Key)
{
        return AddToList(it8, &it8->ValidKeywords, Key, NULL);
}


static
BOOL AddAvailableSampleID(LPIT8 it8, const char* Key)
{
        return AddToList(it8, &it8->ValidSampleID, Key, NULL);
}



/* Init an empty container */
LCMSHANDLE cmsxIT8Alloc(void)
{
    LPIT8 it8;
    int i;

    it8 = (LPIT8) malloc(sizeof(IT8));
    if (it8 == NULL) return NULL;

    ZeroMemory(it8, sizeof(IT8));

    it8->HeaderList = NULL;
    it8->FileBuffer = NULL;
    it8->DataFormat = NULL;
    it8->Data       = NULL;
    it8->MemorySink = NULL;
    it8->ValidKeywords = NULL;
    it8->ValidSampleID = NULL;

    it8 -> sy = SNONE;
    it8 -> ch = ' ';
    it8 -> Source = NULL;
    it8 -> inum = 0;
    it8 -> dnum = 0.0;

    it8 -> lineno = 1;

	strcpy(it8->SheetType, "IT8.7/2");

    /* Initialize predefined properties & data */
    
    for (i=0; i < NUMPREDEFINEDPROPS; i++)
            AddAvailableProperty(it8, PredefinedProperties[i]);

    for (i=0; i < NUMPREDEFINEDSAMPLEID; i++)
            AddAvailableSampleID(it8, PredefinedSampleID[i]);


   return (LCMSHANDLE) it8;
}


const char* cdecl cmsxIT8GetSheetType(LCMSHANDLE hIT8)
{
	    LPIT8 it8 = (LPIT8) hIT8;

		return it8 ->SheetType;

}

BOOL  cmsxIT8SetSheetType(LCMSHANDLE hIT8, const char* Type)
{
		LPIT8 it8 = (LPIT8) hIT8;

		strncpy(it8 ->SheetType, Type, MAXSTR-1);
		return TRUE;
}



/* Sets a property */
BOOL cmsxIT8SetProperty(LCMSHANDLE hIT8, const char* Key, const char *Val)
{
    LPIT8 it8 = (LPIT8) hIT8;

    if (!Val) return FALSE;
    if (!*Val) return FALSE;

    return AddToList(it8, &it8 -> HeaderList, Key, Val);
}


BOOL cmsxIT8SetPropertyDbl(LCMSHANDLE hIT8, const char* cProp, double Val)
{
    char Buffer[256];

    sprintf(Buffer, "%g", Val);
    return cmsxIT8SetProperty(hIT8, cProp, Buffer);
}

/* Gets a property */
const char* cmsxIT8GetProperty(LCMSHANDLE hIT8, const char* Key)
{
    LPIT8 it8 = (LPIT8) hIT8;
    LPKEYVALUE p;

    if (IsAvailableOnList(it8 -> HeaderList, Key, &p))
    {
        return p -> Value;
    }
    return NULL;
}


double  cmsxIT8GetPropertyDbl(LCMSHANDLE hIT8, const char* cProp)
{
    const char *v = cmsxIT8GetProperty(hIT8, cProp);
    if (v) return atof(v);
    else return 0.0;
}

/* ----------------------------------------------------------------- Datasets */


static
void AllocateDataFormat(LPIT8 it8)
{
    if (it8 -> DataFormat) return;    /* Already allocated */

    it8 -> nSamples  = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_FIELDS"));

    if (it8 -> nSamples <= 0) {

        cmsSignalError(LCMS_ERRC_WARNING, "AllocateDataFormat: Unknown NUMBER_OF_FIELDS, assuming 10");
        it8 -> nSamples = 10;
        }

    it8 -> DataFormat = (char**) AllocChunk (it8, (it8->nSamples + 1) * sizeof(char *));
    if (it8->DataFormat == NULL)
    {
        cmsSignalError(LCMS_ERRC_ABORTED, "AllocateDataFormat: Unable to allocate dataFormat array");
    }

}

static
const char *GetDataFormat(LPIT8 it8, int n)
{
    if (it8->DataFormat)
        return it8->DataFormat[n];

    return NULL;
}

static
BOOL SetDataFormat(LPIT8 it8, int n, const char *label)
{
    if (n > it8 -> nSamples) return FALSE;

    if (!it8->DataFormat)
        AllocateDataFormat(it8);

    if (it8->DataFormat) {
                
        it8->DataFormat[n] = AllocString(it8, label);
    }

    return TRUE;
}


BOOL cmsxIT8SetDataFormat(LCMSHANDLE h, int n, const char *Sample)
{
        LPIT8 it8 = (LPIT8) h;
        return SetDataFormat(it8, n, Sample);
}

static
void AllocateDataSet(LPIT8 it8)
{
    if (it8 -> Data) return;    /* Already allocated */

    it8-> nSamples   = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
    it8-> nPatches   = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_SETS"));
    it8-> Data = (char**)AllocChunk (it8, (it8->nSamples + 1) * (it8->nPatches + 1) *sizeof (char*));
    if (it8->Data == NULL)
    {
        cmsSignalError(-1, "AllocateDataSet: Unable to allocate data array");
    }

}

static
char* GetData(LPIT8 it8, int nSet, int nField)
{
    int  nSamples = it8 -> nSamples;
    int  nPatches   = it8 -> nPatches;

    if (nSet >= nPatches || nField >= nSamples)
        return NULL;

    if (!it8->Data) return NULL;
    return it8->Data [nSet * nSamples + nField];
}

static
BOOL SetData(LPIT8 it8, int nSet, int nField, char *Val)
{
    if (!it8->Data)
        AllocateDataSet(it8);

    if (!it8->Data) return FALSE;


    if (nSet > it8 -> nPatches) {

            SynError(it8, "Patch %d out of range, there are %d datasets", nSet, it8 -> nPatches);
            return FALSE;
    }

    if (nField > it8 ->nSamples) {
            SynError(it8, "Sample %d out of range, there are %d datasets", nField, it8 ->nSamples);
            return FALSE;
    }

            
    it8->Data [nSet * it8 -> nSamples + nField] = AllocString(it8, Val);
    return TRUE;
}


/* --------------------------------------------------------------- File I/O */


/* Writes a string to file */
static
void WriteStr(FILE *f, char *str)
{
    if (str == NULL)
        fwrite(" ", 1, 1, f);
    else
        fwrite(str, 1, strlen(str), f);
}


/* Writes full header */
static
void WriteHeader(LPIT8 it8, FILE *fp)
{
    LPKEYVALUE p;

	WriteStr(fp, it8->SheetType);
    WriteStr(fp, "\n");
    for (p = it8->HeaderList; (p != NULL); p = p->Next)
    {
        if (!IsAvailableOnList(it8-> ValidKeywords, p->Keyword, NULL)) {

            WriteStr(fp, "KEYWORD\t\"");
            WriteStr(fp, p->Keyword);
            WriteStr(fp, "\"\n");

        }

        WriteStr(fp, p->Keyword);
        if (p->Value) {

            WriteStr(fp, "\t\"");
            WriteStr(fp, p->Value);
            WriteStr(fp, "\"");
        }
        WriteStr (fp, "\n");
    }

}


/* Writes the data format */
static
void WriteDataFormat(FILE *fp, LPIT8 it8)
{
       int i, nSamples;

    if (!it8 -> DataFormat) return;

       WriteStr(fp, "BEGIN_DATA_FORMAT\n");
       nSamples = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_FIELDS"));

       for (i = 0; i < nSamples; i++) {

              WriteStr(fp, it8->DataFormat[i]);
              WriteStr(fp, ((i == (nSamples-1)) ? "\n" : "\t"));
          }

       WriteStr (fp, "END_DATA_FORMAT\n");
}


/* Writes data array */
static
void WriteData(FILE *fp, LPIT8 it8)
{
       int  i, j;

       if (!it8->Data) return;

       WriteStr (fp, "BEGIN_DATA\n");

       it8->nPatches = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_SETS"));

       for (i = 0; i < it8-> nPatches; i++) {

              for (j = 0; j < it8->nSamples; j++) {

                     char *ptr = it8->Data[i*it8->nSamples+j];

                     WriteStr(fp, ((ptr == NULL) ? "0.00" : ptr));
                     WriteStr(fp, ((j == (it8->nSamples-1)) ? "\n" : "\t"));
              }
       }
       WriteStr (fp, "END_DATA\n");
}



/* Saves whole file */
BOOL cmsxIT8SaveToFile(LCMSHANDLE hIT8, const char* cFileName)
{
    FILE *fp;
    LPIT8 it8 = (LPIT8) hIT8;

    fp = fopen(cFileName, "wt");
    if (!fp) return FALSE;
    WriteHeader(it8, fp);
    WriteDataFormat(fp, it8);
    WriteData(fp, it8);
    fclose(fp);

    return TRUE;
}


/* Reads whole file in a memory block */
static
BOOL ReadFileInMemory(const char *cFileName, char **Buffer, size_t *Len)
{
       FILE *fp;
       size_t Size;
       char *Ptr;

       fp = fopen(cFileName, "rt");
       if (!fp) return FALSE;

       Size = xfilelength(fileno(fp));
       if (Size <= 0) {
           fclose(fp);
           return FALSE;
       }

       Ptr  = malloc(Size+1);

       Size = fread(Ptr, 1, Size, fp);
       fclose(fp);
       Ptr[Size] = '\0';

       *Buffer = Ptr;
       *Len    = Size;
       return TRUE;
}


/* -------------------------------------------------------------- Higer lever parsing */

static
BOOL DataFormatSection(LPIT8 it8)
{
    int iField = 0;
    BOOL Ignoring = FALSE;

    InSymbol(it8);   /* Eats "BEGIN_DATA_FORMAT" */
    CheckEOLN(it8);

    while (it8->sy != SEND_DATA_FORMAT &&
           it8->sy != SEOLN &&
               it8->sy != SEOF &&
               it8->sy != SSYNERROR)
    {

          if (it8->sy != SIDENT) {

                     cmsSignalError(LCMS_ERRC_ABORTED, "Sample type expected");
                     it8->sy = SSYNERROR;
                     return FALSE;
                     }

              if (!Ignoring && iField > it8->nSamples) {
                    cmsSignalError(LCMS_ERRC_WARNING, "More than NUMBER_OF_FIELDS fields. Extra is ignored\n");
                    Ignoring = TRUE;
                    }
              else  {
                     if (!SetDataFormat(it8, iField, it8->id)) return FALSE;
                     iField++;
            }

            InSymbol(it8);
            Skip(it8, SEOLN);
       }

       Skip(it8, SEOLN);
       Skip(it8, SEND_DATA_FORMAT);
       Skip(it8, SEOLN);
       return TRUE;
}



static
BOOL DataSection (LPIT8 it8)
{
    int  iField = 0;
    int  iSet   = 0;
    char Buffer[256];

    InSymbol(it8);   /* Eats "BEGIN_DATA" */
    CheckEOLN(it8);

    while (it8->sy != SEND_DATA && it8->sy != SEOF)
    {
        if (iField >= it8 -> nSamples) {
            iField = 0;
            iSet++;
            if (!CheckEOLN(it8))
                return FALSE;
        }

        if (it8->sy != SEND_DATA && it8->sy != SEOF) {

            if (!GetVal(it8, Buffer))
                return FALSE;

            if (!SetData(it8, iSet, iField, Buffer))
                return FALSE;

            iField++;

            Skip(it8, SEOLN);
            InSymbol(it8);
        }
    }

    Skip(it8, SEOLN);
    Skip(it8, SEND_DATA);
    Skip(it8, SEOLN);
    return TRUE;
}






static
BOOL HeaderSection (LPIT8 it8)
{
    char VarName[MAXID];
        char Buffer[MAXSTR];

        while (it8->sy != SEOF &&
               it8->sy != SSYNERROR &&
               it8->sy != SBEGIN_DATA_FORMAT &&
               it8->sy != SBEGIN_DATA) {


        switch (it8 -> sy) {

        case SKEYWORD:
                InSymbol(it8);
                if (!Check(it8, SSTRING, "Keyword expected")) return FALSE;
                if (!AddAvailableProperty(it8, it8 -> str)) return FALSE;
                InSymbol(it8);
                break;


        case SIDENT:
                strncpy(VarName, it8->id, MAXID-1);
                if (!IsAvailableOnList(it8-> ValidKeywords, VarName, NULL))
                        return SynError(it8, "Undefined keyword '%s'", VarName);

                InSymbol(it8);
                GetVal(it8, Buffer);
                cmsxIT8SetProperty((LCMSHANDLE) it8, VarName, Buffer);
                InSymbol(it8);
                break;


        case SEOLN: break;

        default:
                return SynError(it8, "expected keyword or identifier");
        }

    Skip(it8, SEOLN);
        }

    return TRUE;

}


static
BOOL ParseIT8(LPIT8 it8)
{
    InSymbol(it8);

    if (it8->sy == SIDENT) {
			
			strncpy(it8->SheetType, it8->id, MAXSTR-1);
            InSymbol(it8);

            /* if (!AddAvailableProperty(it8, it8 -> id)) return FALSE; */
            /* cmsxIT8SetProperty((LCMSHANDLE) it8, it8->id, NULL); */
    }

    Skip(it8, SEOLN);
    while (it8-> sy != SEOF &&
           it8-> sy != SSYNERROR) {
             switch (it8 -> sy) {

            case SBEGIN_DATA_FORMAT:
                if (!DataFormatSection(it8)) return FALSE; 
                    break;

            case SBEGIN_DATA:
                if (!DataSection(it8)) return FALSE; 
                    break;

            case SEOLN:
                     Skip(it8, SEOLN);
                    break;

            default:
                    if (!HeaderSection(it8)) return FALSE;
           }

    }
    return TRUE;
}


static
void CleanPatchName(char *cell)
{
       char cleaned[256], Buffer[256], ident[256];
       char *orig = cell, *id;
       int n, lOneNum;


       id = ident;
       while (*cell && isalpha(*cell))
       {
          *id++ = (char) toupper(*cell);
              cell++;
       }
       *id = 0;
       strcpy(cleaned, ident);


       n = 0;
       lOneNum = FALSE;
       while (*cell && isdigit(*cell))
       {
              n = n * 10 + (*cell -'0');
              cell++;
              lOneNum = TRUE;
       }

       if (lOneNum) {

              sprintf(Buffer, "%d", n);
              strcat(cleaned, Buffer);              
       }

       if (strcmp(cleaned, "GS0") == 0)
              strcpy(orig, "DMIN");
	   else
	   if (strcmp(cleaned, "GS23") == 0)
              strcpy(orig, "DMAX");
       else
              strcpy(orig, cleaned);
}


/* Init usefull pointers */

static
void CookPointers(LPIT8 it8)
{
    int idField, i;
	char* Fld;

    it8 -> SampleID = 0;
    for (idField = 0; idField < it8 -> nSamples; idField++)
    {
		Fld = it8->DataFormat[idField];
		if (!Fld) continue;

        if (strcmp(Fld, "SAMPLE_ID") == 0) {
                    it8 -> SampleID = idField;
                
        
        for (i=0; i < it8 -> nPatches; i++) {

                char *Data = GetData(it8, i, idField);
                if (Data) {
                    char Buffer[256];
                
                    strncpy(Buffer, Data, 255);
                    CleanPatchName(Buffer);

                    if (strlen(Buffer) <= strlen(Data))
                        strcpy(Data, Buffer);
                    else
                        SetData(it8, i, idField, Buffer);

                }
                }
        
        }
    }

}


/* ---------------------------------------------------------- Exported routines */


LCMSHANDLE cmsxIT8LoadFromMem(void *Ptr, size_t len)
{
    LCMSHANDLE hIT8 = cmsxIT8Alloc();
    LPIT8  it8 = (LPIT8) hIT8;

    if (!hIT8) return NULL;
	it8 ->FileBuffer = (char*) malloc(len + 1);

	strncpy(it8 ->FileBuffer, Ptr, len);
    strncpy(it8->FileName, "", MAX_PATH-1);
    it8-> Source = it8 -> FileBuffer;

    ParseIT8(it8);
    CookPointers(it8);

    free(it8->FileBuffer);
    it8 -> FileBuffer = NULL;

    return hIT8;


}


LCMSHANDLE cmsxIT8LoadFromFile(const char* cFileName)
{
    LCMSHANDLE hIT8 = cmsxIT8Alloc();
    LPIT8  it8 = (LPIT8) hIT8;
    size_t Len;

    if (!hIT8) return NULL;
    if (!ReadFileInMemory(cFileName, &it8->FileBuffer, &Len)) return NULL;

    strncpy(it8->FileName, cFileName, MAX_PATH-1);
    it8-> Source = it8 -> FileBuffer;
    
     if (ParseIT8(it8)) 
    {
       
         CookPointers(it8);
        free(it8->FileBuffer);
        it8 -> FileBuffer = NULL;
        return hIT8;
    }
    else 
    { 
        /* there was an error handle it */
        free(it8->FileBuffer);
        it8 -> FileBuffer = NULL;
        return NULL;
    }
    

   

}

int cmsxIT8EnumDataFormat(LCMSHANDLE hIT8, char ***SampleNames)
{
        LPIT8 it8 = (LPIT8) hIT8;

        *SampleNames = it8 -> DataFormat;
        return it8 -> nSamples;
}


int  cmsxIT8EnumProperties(LCMSHANDLE hIT8, char ***PropertyNames)
{
    LPIT8 it8 = (LPIT8) hIT8;
    LPKEYVALUE p;
    int n;
    char **Props;

    /* Pass#1 - count properties */

    n = 0;
    for (p = it8 -> HeaderList;  p != NULL; p = p->Next) {
        n++;
    }


    Props = (char **) malloc(sizeof(char *) * n);

    /* Pass#2 - Fill pointers */
    n = 0;
    for (p = it8 -> HeaderList;  p != NULL; p = p->Next) {
        Props[n++] = p -> Keyword;
    }

    *PropertyNames = Props;
    return n;
}

static
int LocatePatch(LPIT8 it8, const char* cPatch)
{
    int i;
    const char *data;

    for (i=0; i < it8-> nPatches; i++) {

        data = GetData(it8, i, it8->SampleID);
        
        if (data != NULL) {

                if (stricmp(data, cPatch) == 0)
                        return i;
                }
        }

        return -1;
}


static
int LocateEmptyPatch(LPIT8 it8, const char* cPatch)
{
    int i;
    const char *data;

    for (i=0; i < it8-> nPatches; i++) {

        data = GetData(it8, i, it8->SampleID);
        
        if (data == NULL) 
                    return i;
                
        }

        return -1;
}

static
int LocateSample(LPIT8 it8, const char* cSample)
{
    int i;
    const char *fld;

    for (i=0; i < it8->nSamples; i++) {

        fld = GetDataFormat(it8, i);
        if (stricmp(fld, cSample) == 0)
            return i;
    }

    return -1;
}


BOOL cmsxIT8GetDataSetByPos(LCMSHANDLE hIT8, int col, int row, char* Val, int ValBufferLen)
{
    LPIT8 it8 = (LPIT8) hIT8;
    const char *data =  GetData(it8, row, col);

    if (!data) 
    {
        *Val = '\0';
        return FALSE;
    }

    strncpy(Val, data, ValBufferLen-1);
    return TRUE;
}



BOOL  cmsxIT8GetDataSet(LCMSHANDLE hIT8, const char* cPatch,
                        const char* cSample,
                        char* Val, int ValBuffLen)
{
    LPIT8 it8 = (LPIT8) hIT8;
    int iField, iSet;


    iField = LocateSample(it8, cSample);
    if (iField < 0) {
        /* cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't find data field %s\n", cSample); */
        return FALSE;
    }


    iSet = LocatePatch(it8, cPatch);
    if (iSet < 0) {

            /* cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't find patch '%s'\n", cPatch); */
            return FALSE;
    }

    strncpy(Val, GetData(it8, iSet, iField), ValBuffLen-1);
    return TRUE;
}


BOOL cmsxIT8GetDataSetDbl(LCMSHANDLE it8, const char* cPatch, const char* cSample, double* v)
{
    char Buffer[20];

    if (cmsxIT8GetDataSet(it8, cPatch, cSample, Buffer, 20)) {

        *v = atof(Buffer);
        return TRUE;
    } else
        return FALSE;
}



BOOL cmsxIT8SetDataSet(LCMSHANDLE hIT8, const char* cPatch,
                        const char* cSample,
                        char *Val)
{
    LPIT8 it8 = (LPIT8) hIT8;
    int iField, iSet;


    iField = LocateSample(it8, cSample);

    if (iField < 0) {
    
        cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't find data field %s\n", cSample);
        return FALSE;
        }


        if (it8-> nPatches == 0) {

                AllocateDataFormat(it8);
                AllocateDataSet(it8);
                CookPointers(it8);
        }


        if (stricmp(cSample, "SAMPLE_ID") == 0)
        {
                    
                iSet   = LocateEmptyPatch(it8, cPatch);
                if (iSet < 0) {
                        cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't add more patches '%s'\n", cPatch);
                        return FALSE;
                }
                iField = it8 -> SampleID;
        }
        else {
                iSet = LocatePatch(it8, cPatch);
                if (iSet < 0) {

                    cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't find patch '%s'\n", cPatch);
                    return FALSE;
            }
        }

        return SetData(it8, iSet, iField, Val);
}


BOOL cmsxIT8SetDataSetDbl(LCMSHANDLE hIT8, const char* cPatch,
                        const char* cSample,
                        double Val)
{
        char Buff[256];

        sprintf(Buff, "%g", Val);
        return cmsxIT8SetDataSet(hIT8, cPatch, cSample, Buff);

}


const char* cmsxIT8GetPatchName(LCMSHANDLE hIT8, int nPatch, char* buffer)
{
		LPIT8 it8 = (LPIT8) hIT8;
		char* Data = GetData(it8, nPatch, it8->SampleID);
		if (!Data) return NULL;

		strcpy(buffer, Data);
		return buffer;
}


const char* cmsxIT8GenericPatchName(int nPatch, char* buffer)
{
    int row, col;

    if (nPatch >= cmsxIT8_NORMAL_PATCHES)
        return "$CUSTOM";

    if (nPatch >= (cmsxIT8_ROWS * cmsxIT8_COLS)) {

        nPatch -= cmsxIT8_ROWS * cmsxIT8_COLS;
        if (nPatch == 0)
            return "DMIN";
        else
        if (nPatch == cmsxIT8_GRAYCOLS - 1)
            return "DMAX";
        else
            sprintf(buffer, "GS%d", nPatch);
            return buffer;
    }


    row = nPatch / cmsxIT8_COLS;
    col = nPatch % cmsxIT8_COLS;

    sprintf (buffer, "%c%d", 'A'+row, col+1);
    return buffer;
}




int cmsxIT8GenericPatchNum(const char *name)
{
        int i;
        char Buff[256];


        for (i=0; i < cmsxIT8_TOTAL_PATCHES; i++)
                if (stricmp(cmsxIT8GenericPatchName(i, Buff), name) == 0)
                        return i;

        return -1;
}





