#include "csf.h"
#include "csfimpl.h"
#include <string.h>

static void UINT1tLdd(size_t nrCells, void *Buf)
{
	size_t i;
	UINT1 *buf = (UINT1 *)Buf;

	for(i=0; i < (size_t)nrCells; i++)
		if (buf[i] != MV_UINT1)
		{
			buf[i] %= (UINT1)10;
			if (buf[i] == 0)
				buf[i] = MV_UINT1;
		}
}

static void INT2tLdd(size_t nrCells, void *Buf)
{
	size_t i;
	INT2  *inBuf  = (INT2  *)Buf;
	UINT1 *outBuf = (UINT1 *)Buf;
	for(i=0; i < (size_t)nrCells; i++)
		if (inBuf[i] != MV_INT2)
		{
			outBuf[i] = (UINT1)(ABS(inBuf[i]) % 10);
			if (outBuf[i] == 0)
				outBuf[i] = MV_UINT1;
		}
		else
			outBuf[i] = MV_UINT1;
}

#define TOBOOL(nr, buf, srcType)\
{\
	size_t i;\
/* loop must be upward to prevent overwriting of values \
 * not yet converted \
 */ \
 	PRECOND(sizeof(srcType) >= sizeof(UINT1));\
	for(i=0; i < (size_t)nr; i++)\
		if (IS_MV_##srcType( ((srcType *)buf)+i) )\
			((UINT1 *)buf)[i] = MV_UINT1;\
		else\
			((UINT1 *)buf)[i] = ((srcType *)buf)[i] != ZERO_##srcType;\
}

static void INT1tBoolean(size_t nrCells, void *buf)
{ TOBOOL(nrCells, buf, INT1); }

static void INT2tBoolean(size_t nrCells, void *buf)
{ TOBOOL(nrCells, buf, INT2); }

static void INT4tBoolean(size_t nrCells, void *buf)
{ TOBOOL(nrCells, buf, INT4); }

static void UINT1tBoolean(size_t nrCells, void *buf)
{ TOBOOL(nrCells, buf, UINT1); }

static void UINT2tBoolean(size_t nrCells, void *buf)
{ TOBOOL(nrCells, buf, UINT2); }

static void UINT4tBoolean(size_t nrCells, void *buf)
{ TOBOOL(nrCells, buf, UINT4); }

static void REAL4tBoolean(size_t nrCells, void *buf)
{ TOBOOL(nrCells, buf, REAL4); }

static void REAL8tBoolean(size_t nrCells, void *buf)
{ TOBOOL(nrCells, buf, REAL8); }

#define CONV_BIG_TO_SMALL(nr, buf, destType, srcType)\
{\
	size_t i;\
/* loop must be upward to prevent overwriting of values \
 * not yet converted: \
 */ \
 	PRECOND(sizeof(srcType) >= sizeof(destType)); /* upward loop OK */ \
	for(i=0; i < (size_t)nr; i++)\
		if (IS_MV_##srcType( ((srcType *)buf)+i) )\
		    SET_MV_##destType( ((destType *)buf)+i);\
		else\
			((destType *)buf)[i] = (destType)(((srcType *)buf)[i]);\
}

#define CONV_SMALL_TO_BIG(nr, buf, destType, srcType)\
{\
	size_t i = (size_t)nr;\
/* loop must be downward to prevent overwriting of values \
 * not yet converted: \
 */ \
 	PRECOND(sizeof(srcType) <= sizeof(destType)); /* downward loop OK */ \
	do { i--;\
		if (IS_MV_##srcType( ((srcType *)buf)+i) )\
		    SET_MV_##destType( ((destType *)buf)+i);\
		else\
			((destType *)buf)[i] = (destType)(((srcType *)buf)[i]);\
	}while ( i != 0);\
}

static void UINT1tINT4(size_t nrCells, void *buf)
{ CONV_SMALL_TO_BIG(nrCells, buf, INT4, UINT1);}

static void UINT1tREAL4(size_t nrCells, void *buf)
{ CONV_SMALL_TO_BIG(nrCells, buf, REAL4, UINT1);}

static void UINT1tREAL8(size_t nrCells, void *buf)
{ CONV_SMALL_TO_BIG(nrCells, buf, REAL8, UINT1);}

static void INT4tUINT1(size_t nrCells, void *buf)
{ CONV_BIG_TO_SMALL(nrCells, buf, UINT1, INT4);}

static void INT2tUINT1(size_t nrCells, void *buf)
{ CONV_BIG_TO_SMALL(nrCells, buf, UINT1, INT2);}

static void UINT2tUINT1(size_t nrCells, void *buf)
{ CONV_BIG_TO_SMALL(nrCells, buf, UINT1, UINT2);}

static void INT4tREAL4(size_t nrCells, void *buf)
{ CONV_BIG_TO_SMALL(nrCells, buf, REAL4, INT4);}

static void INT4tREAL8(size_t nrCells, void *buf)
{ CONV_SMALL_TO_BIG(nrCells, buf, REAL8, INT4);}

static void REAL4tUINT1(size_t nrCells, void *buf)
{ CONV_BIG_TO_SMALL(nrCells, buf, UINT1, REAL4);}

static void REAL4tINT4(size_t nrCells, void *buf)
{ CONV_BIG_TO_SMALL(nrCells, buf, INT4, REAL4);}

static void REAL4tREAL8(size_t nrCells, void *buf)
{ CONV_SMALL_TO_BIG(nrCells, buf, REAL8, REAL4);}

static void REAL8tUINT1(size_t nrCells, void *buf)
{ CONV_BIG_TO_SMALL(nrCells, buf, UINT1, REAL8);}

static void REAL8tINT4(size_t nrCells, void *buf)
{ CONV_BIG_TO_SMALL(nrCells, buf, INT4, REAL8);} 

static void REAL8tREAL4(size_t nrCells, void *buf)
{ CONV_BIG_TO_SMALL(nrCells, buf, REAL4, REAL8);}

static void Transform2( size_t nrCells, void *buf, CSF_CR destCellRepr, CSF_CR currCellRepr);

static void INT1tINT4(size_t nrCells, void *buf)
{ Transform2(nrCells, buf, CR_INT4, CR_INT1);}

static void INT1tREAL4(size_t nrCells, void *buf)
{ Transform2(nrCells, buf, CR_REAL4, CR_INT1);}

static void INT1tREAL8(size_t nrCells, void *buf)
{ Transform2(nrCells, buf, CR_REAL8, CR_INT1);}

static void INT2tINT4(size_t nrCells, void *buf)
{ Transform2(nrCells, buf, CR_INT4, CR_INT2);}

static void INT2tREAL4(size_t nrCells, void *buf)
{ Transform2(nrCells, buf, CR_REAL4, CR_INT2);}

static void INT2tREAL8(size_t nrCells, void *buf)
{ Transform2(nrCells, buf, CR_REAL8, CR_INT2);}

static void UINT2tINT4(size_t nrCells, void *buf)
{ Transform2(nrCells, buf, CR_INT4, CR_UINT2);}

static void UINT2tREAL4(size_t nrCells, void *buf)
{ Transform2(nrCells, buf, CR_REAL4, CR_UINT2);}

static void UINT2tREAL8(size_t nrCells, void *buf)
{ Transform2(nrCells, buf, CR_REAL8, CR_UINT2);}

static void UINT4tREAL4(size_t nrCells, void *buf)
{ Transform2(nrCells, buf, CR_REAL4, CR_UINT4);}

static void UINT4tREAL8(size_t nrCells, void *buf)
{ Transform2(nrCells, buf, CR_REAL8, CR_UINT4);}


static void ConvertToINT2( size_t nrCells, void *buf, CSF_CR src)
{
	if (IS_SIGNED(src))
	{
		POSTCOND(src == CR_INT1);
		CONV_SMALL_TO_BIG(nrCells,buf, INT2, INT1);
	}
	else  
	{
		POSTCOND(src == CR_UINT1);
		CONV_SMALL_TO_BIG(nrCells,buf, INT2, UINT1);
	}
}

static void ConvertToINT4( size_t nrCells, void *buf, CSF_CR src)
{
	if (IS_SIGNED(src))
	{
		POSTCOND(src == CR_INT2);
		CONV_SMALL_TO_BIG(nrCells,buf, INT4, INT2);
	}
	else  
	{
		POSTCOND(src == CR_UINT2);
		CONV_SMALL_TO_BIG(nrCells,buf, INT4, UINT2);
	}
}


static void UINT1tUINT2(
size_t nrCells,
void *buf)
{
	CONV_SMALL_TO_BIG(nrCells, buf, UINT2, UINT1);
}

static void UINT2tUINT4(
size_t nrCells,
void *buf)     
{
	CONV_SMALL_TO_BIG(nrCells, buf, UINT4, UINT2);
}

static void ConvertToREAL4( size_t nrCells, void *buf, CSF_CR src)
{
	size_t i;

	i = (size_t)nrCells;

	if (IS_SIGNED(src))
	{
		POSTCOND(src == CR_INT4);
		INT4tREAL4(nrCells, buf);
	}
	else
	{
		POSTCOND(src == CR_UINT4);
		{
			do {
				i--;
				if ( ((UINT4 *)buf)[i] == MV_UINT4 )
					((UINT4 *)buf)[i] = MV_UINT4;
				else
					((REAL4 *)buf)[i] = (REAL4)((UINT4 *)buf)[i];
			    }
			while(i != 0);
		}
	}
}

static void Transform2(
	size_t nrCells,
	void  *buf,
	CSF_CR destCellRepr, /* the output representation */
	CSF_CR currCellRepr)  /* at start of while this is the representation
				read in the MAP-file */
{
  /* subsequent looping changes the to the new
   * converted type
   */
	while(currCellRepr != destCellRepr)
	{
		switch(currCellRepr)
		{
			case CR_INT1:    ConvertToINT2(nrCells, buf,
						currCellRepr);
					currCellRepr = CR_INT2;
					break;
			case CR_INT2:    ConvertToINT4(nrCells, buf, 
						currCellRepr);
					currCellRepr = CR_INT4;
					break;
			case CR_INT4:    ConvertToREAL4(nrCells, buf,
						currCellRepr);
					currCellRepr = CR_REAL4;
					break;
			case CR_UINT1: 	if (IS_SIGNED(destCellRepr))
					{
						ConvertToINT2(nrCells, buf,
							currCellRepr);
						currCellRepr = CR_INT2;
					}
					else
					{
						UINT1tUINT2(nrCells, buf);
						currCellRepr = CR_UINT2;
					}
					break;
			case CR_UINT2:   if (destCellRepr == CR_INT4)
					{
						ConvertToINT4(nrCells, buf,
							currCellRepr);
					 	currCellRepr = CR_INT4;
					 }
					 else
					 {
						UINT2tUINT4(nrCells, buf);
						currCellRepr = CR_UINT4;
					 }
					 break;
			case CR_UINT4:   ConvertToREAL4(nrCells, buf,
						currCellRepr);
					currCellRepr = CR_REAL4;
					break;
			default       :	POSTCOND(currCellRepr == CR_REAL4);
					REAL4tREAL8(nrCells, buf);
					currCellRepr = CR_REAL8;
		}
	}
}

/* OLD STUFF 
void TransForm(
	const MAP *map,
	size_t nrCells,
	void *buf)
{
	Transform2(nrCells, buf, map->types[READ_AS], map->types[STORED_AS]);
}
*/

#define illegal NULL
#define same    CsfDummyConversion

static const CSF_CONV_FUNC ConvTable[8][8] = {
/* ConvTable[source][destination] */
/* INT1     , INT2      , INT4      , UINT1     , UINT2     , UINT4     , REAL4     , REAL8        */
/* 0        , 1         , 2         , 3         , 4         , 5         , 6         , 7      ind   */
/* 0x04     , 0x05      , 0x06      , 0x00      , 0x01      , 0x02      , 0x0A      , 0x0B low-nib */
{  same     , illegal   , INT1tINT4 , illegal   , illegal   , illegal   , INT1tREAL4, INT1tREAL8}, /* INT1 */
{ illegal   ,  same     , INT2tINT4 ,INT2tUINT1 , illegal   , illegal   , INT2tREAL4, INT2tREAL8}, /* INT2 */
{ illegal   , illegal   ,  same     ,INT4tUINT1 , illegal   , illegal   , INT4tREAL4, INT4tREAL8}, /* INT4 */
{ illegal   , illegal   ,UINT1tINT4 ,  same     ,UINT1tUINT2, illegal   ,UINT1tREAL4,UINT1tREAL8}, /* UINT1 */
{ illegal   , illegal   ,UINT2tINT4 , UINT2tUINT1 ,  same     ,UINT2tUINT4,UINT2tREAL4,UINT2tREAL8}, /* UINT2 */
{ illegal   , illegal   , illegal   , illegal   , illegal   ,  same     ,UINT4tREAL4,UINT4tREAL8}, /* UINT4 */
{ illegal   , illegal   ,REAL4tINT4 ,REAL4tUINT1, illegal   , illegal   ,  same     ,REAL4tREAL8}, /* REAL4 */
{ illegal   , illegal   ,REAL8tINT4 ,REAL8tUINT1, illegal   , illegal   ,REAL8tREAL4,  same     }  /* REAL8 */
};

static const CSF_CONV_FUNC boolConvTable[8] = {
INT1tBoolean, INT2tBoolean, INT4tBoolean,
UINT1tBoolean, UINT2tBoolean, UINT4tBoolean,
REAL4tBoolean, REAL8tBoolean };


static const char  convTableIndex[12] = {
	   3 /* UINT1 */,  4 /* UINT2 */,  5 /* UINT4 */, -1 /* 0x03  */,
	   0 /*  INT1 */,  1 /*  INT2 */,  2 /*  INT4 */, -1 /* 0x07  */,
	  -1 /*  0x08 */, -1 /*  0x09 */,  6 /* REAL4 */,  7 /* REAL8 */
};

static CSF_CONV_FUNC ConvFuncBool(CSF_CR cr)
{
	PRECOND(CSF_UNIQ_CR_MASK(cr) < 12);
	PRECOND(convTableIndex[CSF_UNIQ_CR_MASK(cr)] != -1);
	
	return boolConvTable[(int)convTableIndex[CSF_UNIQ_CR_MASK(cr)]];
}

static CSF_CONV_FUNC ConvFunc(CSF_CR destType, CSF_CR srcType)
{

	PRECOND(CSF_UNIQ_CR_MASK(destType) < 12);
	PRECOND(CSF_UNIQ_CR_MASK(srcType) < 12);
	PRECOND(convTableIndex[CSF_UNIQ_CR_MASK(srcType)] != -1);
	PRECOND(convTableIndex[CSF_UNIQ_CR_MASK(destType)] != -1);
	/* don't complain on illegal, it can be attached
	 * to a app2file while there's no WRITE_MODE
	 * if it is an error then it is cached in RputSomeCells
	 */
	return 
         ConvTable[(int)convTableIndex[CSF_UNIQ_CR_MASK(srcType)]]
 	          [(int)convTableIndex[CSF_UNIQ_CR_MASK(destType)]];
}

static int HasInFileCellReprType2(CSF_CR cr)
{
	char  type2[12] = {
	   1 /* UINT1 */,  0 /* UINT2 */,  0 /* UINT4 */,  0 /* 0x03  */,
	   0 /*  INT1 */,  0 /*  INT2 */,  1 /*  INT4 */,  0 /* 0x07  */,
	   0 /*  0x08 */,  0 /*  0x09 */,  1 /* REAL4 */,  1 /* REAL8 */};

	PRECOND(CSF_UNIQ_CR_MASK(cr) < 12);
	
	return (int)type2[CSF_UNIQ_CR_MASK(cr)];
}

/* set the cell representation the application will use
 * RuseAs enables an application to use cell values
 * in a different format than they are stored in the map.
 * Cell values are converted when getting (Rget*-functions) and
 * putting (Rput*-functions) cells if necessary.
 * Thus no conversions are applied if cell representation and/or 
 * value scale already match.
 * Any conversions between the version 2 cell representations, 
 * (CR_UINT1, CR_INT4, CR_REAL4 and CR_REAL8) is possible. 
 * Conversion from a non version 2 cell representation to a version
 * 2 cell representation is only possible when you don't
 * have write access to the cells.
 * Conversion rules are exactly as described in K&R 2nd edition section A6.
 * 
 * Two special conversions are possible if you don't
 * have write access to the cells or if the in-file cell representation is
 * UINT1:
 * (1) VS_BOOLEAN: successive calls to the Rget*-functions returns the result of
 * value != 0 
 * , that is 0 or 1 in UINT1 format. The in-file cell representation can be
 * anything, except if the value scale is VS_DIRECTION or VS_LDD.
 * (2) VS_LDD: successive calls to the Rget*-functions returns the result of
 * value % 10 
 * , that is 1 to 9 in UINT1 format (0's are set to MV_UINT1). 
 * The in-file cell representation must be CR_UINT1 or CR_INT2 and 
 * the value scale must be VS_LDD, VS_CLASSIFIED or VS_NOTDETERMINED.
 *
 * NOTE
 * that you must use Rmalloc() to get enough space for both the in-file and
 * app cell representation. 
 *
 * returns 
 * 0 if conversion obeys rules given here. 1 if not (conversions 
 * will not take place).
 *
 * Merrno
 * CANT_USE_AS_BOOLEAN CANT_USE_WRITE_BOOLEAN
 * CANT_USE_WRITE_LDD
 * CANT_USE_AS_LDD
 * CANT_USE_WRITE_OLDCR
 * ILLEGAL_USE_TYPE
 *
 * EXAMPLE
 * .so examples/maskdump.tr
 */

int RuseAs(
	MAP *m,          /* map handle */
	CSF_CR useType)   /* CR_UINT1,CR_INT4, CR_REAL4, CR_REAL8, VS_BOOLEAN or VS_LDD */
{ 

  CSF_CR inFileCR = RgetCellRepr(m);
  CSF_VS inFileVS = RgetValueScale(m);
  int hasInFileCellReprType2 =  HasInFileCellReprType2(inFileCR);
  int useTypeNoEnumIn;
  int useTypeNoEnum;

  /* It is very inconvenient that both, VS and CR are taken as arguments
   * for this function, and previously were used in the switch statement
   * now, at least 'special conversions' handled first.
   */

  /* Hopefully Coverity will no longer detect that useTypeNoEnum */
  /* comes from useType */
  useTypeNoEnumIn = useType;
  memcpy(&useTypeNoEnum, &useTypeNoEnumIn, sizeof(int));

  if(useTypeNoEnum == VS_BOOLEAN){
    switch(inFileVS) {
      case VS_LDD:
      case VS_DIRECTION: {
        M_ERROR(CANT_USE_AS_BOOLEAN);
        return 1;
      }
      case VS_BOOLEAN: {
        POSTCOND(inFileCR == CR_UINT1);
        m->appCR = CR_UINT1;
        m->file2app = same;
        m->app2file = same;
        return 0;
      }
      default: {
        if((!hasInFileCellReprType2) && WRITE_ENABLE(m)) {
          /* cellrepr is old one, we can't write that */
          M_ERROR(CANT_USE_WRITE_BOOLEAN);
          return 1;
        }
        m->appCR = CR_UINT1;
        m->file2app  = ConvFuncBool(inFileCR);
        m->app2file = ConvFunc(inFileCR, CR_UINT1);
        return 0;
      }
    }
  }
  else if (useTypeNoEnum == VS_LDD){
    switch(inFileVS) {
      case VS_LDD: {
        POSTCOND(inFileCR == CR_UINT1);
        m->appCR = CR_UINT1;
        m->file2app = same;
        m->app2file = same;
        return 0;
      }
      case VS_CLASSIFIED: 
      case VS_NOTDETERMINED: {
        switch(inFileCR) {
          case CR_UINT1: {
            m->appCR = CR_UINT1;
            m->file2app  = UINT1tLdd;
            m->app2file = same;
            return 0;
          }
          case CR_INT2: {
            if(WRITE_ENABLE(m)) {
              M_ERROR(CANT_USE_WRITE_LDD);
              return 1;
            }
            m->appCR = CR_UINT1;
            m->file2app  = INT2tLdd;
            m->app2file = illegal;
            return 0;
          }
          default: {
            /* This should never happen.
             * Shut up compiler.
             */
            assert(0);
          }
        }
      }
      default: {
        M_ERROR(CANT_USE_AS_LDD);
        return 1;
      }
    }
  }

  switch(useType) {
    case CR_UINT1:
    case CR_INT4 :
    case CR_REAL4:
    case CR_REAL8: {
      if((!hasInFileCellReprType2) && WRITE_ENABLE(m)) {
        /* cellrepr is old one, we can't write that */
        M_ERROR(CANT_USE_WRITE_OLDCR);
        return 1;
      }
      m->appCR = useType;
      m->file2app  = ConvFunc(useType, inFileCR);
      m->app2file = ConvFunc(inFileCR, useType);
      POSTCOND(m->file2app != NULL);
      return 0;
    }
    default: {
      M_ERROR(ILLEGAL_USE_TYPE);
      return 1;
    }
  }
  /* NOTREACHED */
}
