#include <stdio.h>
#include <stdlib.h>
//#include <alloc.h>
#include <string.h>

#include <stringa.h>
#include <dbllist.h>

#include "cplib.h"

#if defined(__BORLANDC__) && !defined(__WIN32__)
 #include "cp_lib/cptran.h"
#else
 #include "cptran.h"
#endif


#ifndef setmem
void setmem(void *buff, int count, char number)
{
while(count>0)
	{
#ifdef NO_LVAL_CAST
        *((char *)buff) = number;
        buff=(char *)buff + 1;
#else
	
	*((char *)buff)++ = number;
#endif
	count--;
	}
}
#endif


/** Obtain filename with extension without leading path. */
const char *GetFullFileName(const char *PathName)
{
const char *NameStep;

 if(PathName==NULL) return(NULL);

 NameStep = PathName+strlen(PathName);
 while(NameStep>PathName)
	{
	if(*NameStep=='/' || *NameStep=='\\' || *NameStep==':')
		return(NameStep+1);
	NameStep--;
	}

return(PathName);
}


void codepage::InsertCharDesc(uint16_t position, const char *Name)
{
if(Name==NULL) return;
if(*Name==0) return;
if(position>=Allocated)
	{
        char ** const NewChars = (char **)realloc(characters,sizeof(char **)*(position+1));
	if(NewChars==NULL) goto NoMemory;
        characters = NewChars;
	do {
	   characters[Allocated++] = NULL;
	   } while(position>=Allocated);
	}

if(characters[position]!=NULL)
	{
        printf("\n<%s>",characters[position]);

	if(!strcmp(characters[position],Name)) return;
	printf("Warning: redefinition of character 0x%X \"%s\" -> \"%s\" in \"%s\"!\n",
		position,characters[position],Name,(name==NULL)?"":name);
	free(characters[position]);
	characters[position] = NULL;
	}

if( (characters[position]=strdup(Name))==NULL )
  {
NoMemory:
    puts("Fatal: Memory exhausted");
//  return;
    exit(-1);
  }
if(position>=MaxUsed) MaxUsed=position+1;
}

struct chardesc
	{
	uint16_t position;
	string name;
	};


/*This function read one line from the text file and store it to the string.*/
string & fGets2(FILE *f, string & pstr)
{
  char c;

  pstr="";
  while (!feof(f))
     {
     c = getc(f);
     if (c == '\n') break;
     if (c == '\r') continue;
     if ((unsigned char)c == 0xFF)
	if(feof(f)) break;	//Fix the situation, when 0xFF is read before eof

     pstr += c;
  }

 return(pstr);
}



string s;
#define CharSetSize 256
void ReadChar(FILE *f, codepage & cp)
{
static uint16_t previous=0;
uint16_t number=0,digits=0,n2=0;
char *text,*t2;

fGets2(f, s);
if(s=="") return;

text=s();
while(isspace(*text)) text++;
if(*text==0 || *text==';') return; // empty line - useless
while(isdigit(*text))
   {
   number = number*10 + *text-'0';
   text++;
   digits++;
   }
if(*text==',')
   {
   text++;
   while(isdigit(*text))
	{
	n2 = n2*10 + *text-'0';
	text++;
	}
   number = CharSetSize * number + n2;
   }
if(digits==0) number = previous++;
	 else previous= number;
while(isspace(*text)) text++;
if(*text==0 || *text==';') return; // only number - useless
t2=text;
while(*t2!=0)
	{
	if(*t2==';')
	    {
 	    *t2=0;
	    check(s);
	    break;
	    }
	t2++;
	}

n2=length(s);
while(isspace(s[n2-1]))
   {
   s[n2-1]=0;
   n2--;
   }

cp.InsertCharDesc(number,text);
}


int CreateTranslator(Translator & trn, const codepage & cp1, const codepage & cp2)
{
uint16_t i,j;

trn.erase();

if((trn.Data=(uint16_t *)calloc(cp1.MaxUsed,sizeof(uint16_t)))==NULL)	// Initialise by zero.
  {
  printf(" >>Memory exhausted!");
  return -1;
  }
//setmem(trn.Data,cp1.MaxUsed,0); - calloc initialises memory.

trn.FromCp = &cp1;
trn.ToCp = &cp2;

for(i=0;i<cp1.MaxUsed;i++)
    {
    if(cp1.characters[i]==NULL) continue;
    for(j=0;j<cp2.MaxUsed;j++)
	{
	if(cp2.characters[j]==NULL) continue;
	if(!strcmp(cp1.characters[i],cp2.characters[j]))
		{
		trn.Data[i] = j;
		break;
		}
	}
    }

return 0;
}


bool LoadCodePage(string cpfilename, codepage & cp, int ShowAll)
{
FILE *Fcp;

 printf("%s%s",ShowAll?"\nInfo: Loading codepage ":", ",cpfilename());

 cp.name = strdup(GetFullFileName(cpfilename));
 cpfilename+=".enc";

 if((Fcp=fopen(cpfilename,"r"))==NULL )
	{
	printf("\nError: cannot open file:%s!!!",cpfilename());
	return false;
	}

 while(!feof(Fcp))
	ReadChar(Fcp,cp);

 fclose(Fcp);
 return true;
}


void ReadTextChar(FILE *f,doublelist & ll)
{
string s,CharN,desc;
int i;

fGets2(f, s);
if(s=="") return;

i=0;

while(isspace(s[i])) i++;
if(s[i]==';')
	return;
while(!isspace(s[i]) && (s[i]!=0) ) {CharN+=s[i];i++;}
CharN.trim();
if(CharN=="") return;

while(isspace(s[i])) i++;
while(i < length(s))
	{
	if(s[i]==';' || s[i]==0) break;
	desc+=s[i];
	i++;
	}
desc.trim();

ll.Add(CharN,desc);
}


void LoadTextCodePage(string cpfilename,doublelist & ll)
{
FILE *Fcp;

 printf("Info: Loading text codepage %s\n",cpfilename());

 cpfilename+=".txc";

 if( (Fcp=fopen(cpfilename,"r"))==NULL )
	{
	printf("Error: cannot open file:%s!!!\n",cpfilename());
	return;
	}

 while(!feof(Fcp))
	ReadTextChar(Fcp,ll);

 fclose(Fcp);
}


void CreateCodePage(string cpfilename,codepage & cp,doublelist & ll)
{
int i;

cp.name=strdup(cpfilename);

i=0;
while( ll[i]!=NULL)
	{
	cp.InsertCharDesc(i,ll.Member(i,1));
	i++;
	}

}


void WriteStringTable(FILE *F,const codepage & cp,doublelist & ll)
{
int i;

if(F==NULL) return;
printf("Info: Writing string table %s.\n",cp.name);

fprintf(F,"const char *Table_%s[]={\n",cp.name);

i=0;
while( ll[i]!=NULL)
	{
	fprintf(F,"  \"%s\"",ll.Member(i,0));
	i++;
	if(ll[i]==NULL) fprintf(F,"};\n");
		   else fprintf(F,",\n");
	}

}


int WSparseCpTranslatorConsumption(uint16_t Max, uint16_t EmptyBankCount)
{
unsigned BanksTotal = (Max + 255) / 256;

return 512*(BanksTotal-EmptyBankCount) + 4*BanksTotal;
}


int BSparseCpTranslatorConsumption(uint16_t Max, uint16_t EmptyBankCount)
{
unsigned BanksTotal = (Max + 255) / 256;

return 256*(BanksTotal-EmptyBankCount) + 4*BanksTotal;
}



/*This procedure stores translator file to the disk*/
int WriteTranslator(const codepage & cp_from, const codepage & cp_to, Translator &trn, FILE *F)
{
uint16_t i, b, Max, EmptyBanks;
char *cp_from_name, *cp_to_name;
char TranslatorType=0;
long Banks[256];

 if(F==NULL) return -1;
 if(trn.Data==NULL)
   {
   printf("Error: Translator %s -> %s has no data!\n", cp_from.name, cp_to.name);
   return -1;
   }

 //printf("Info: Writing translator %s -> %s\n",cp_from.name,cp_to.name);
 Max = cp_from.MaxUsed;
 while(Max>0)
   {
   if(trn.Data[Max-1]!=0 && trn.Data[Max-1]!=0xFFFF) break;
   Max--;
   }

 cp_from_name=StrStr(cp_from.name,"/");
 if(cp_from_name==NULL) cp_from_name=cp_from.name;
                   else cp_from_name++;
 cp_to_name=StrStr(cp_to.name,"/");
 if(cp_to_name==NULL) cp_to_name=cp_to.name;
                 else cp_to_name++;

 setmem(Banks,sizeof(Banks),0);
 TranslatorType = 2;		// BYTE array
 for(i=0; i<Max; i++)
   {
   if(trn.Data[i]!=0) Banks[i>>8]++;
   if(trn.Data[i]>=256) TranslatorType=1;  // uint16_t array
   }
 EmptyBanks=0;
 for(b=0;b<=(Max-1)>>8;b++)
   {
   if(Banks[b]==0) EmptyBanks++;
   }
 //printf(" Empty:%d %s_%s Max:%d %d ",EmptyBanks,cp_from_name,cp_to_name,Max,Max>>8);

 if(Max==0) TranslatorType=0;
 if(EmptyBanks>1)
  {
  if(TranslatorType==2)		// BYTE
    {
      if(BSparseCpTranslatorConsumption(Max,EmptyBanks)<Max)
	TranslatorType = 4;
    }
  else				// WORD
   {
     if(WSparseCpTranslatorConsumption(Max,EmptyBanks) < 2*Max)
     	TranslatorType = 3;
   }
  }

 switch(TranslatorType)
  {
  case 0:fprintf(F,"\nCpTranslator %s%s(\"%sTO%s\");\n\n",
           cp_from_name,cp_to_name,
           cp_from_name,cp_to_name);
         break;

  case 1:fprintf(F,"const uint16_t %s_%s[]={",cp_from_name,cp_to_name);
	 for(i=0;i<Max;i++)
		{
		fprintf(F,"  %u%s", trn.Data[i], (i==Max-1)?"};":",");
		if(cp_from.characters[i]!=NULL && trn.Data[i]!=0)
		fprintf(F,"	/* %s */", cp_from.characters[i]);
		fputc('\n',F);
		}
         fprintf(F,"\nWCpTranslator %s%s(\"%sTO%s\",%u,%s_%s);\n\n",
           cp_from_name,cp_to_name,
           cp_from_name,cp_to_name,(unsigned)Max,
           cp_from_name,cp_to_name);
         break;

  case 2:fprintf(F,"const uint8_t %s_%s[]={",cp_from_name,cp_to_name);
  	 for(i=0;i<Max;i++)
		{
//		printf("  %d,\n",trn[i]);
		fprintf(F,"  %u%s",trn.Data[i],
			i==Max-1?"};":",");
		if(cp_from.characters[i]!=NULL && trn.Data[i]!=0)
		fprintf(F,"	/* %s */",cp_from.characters[i]);
		fputc('\n',F);
		}
  	 fprintf(F,"\nBCpTranslator %s%s(\"%sTO%s\",%u,%s_%s);\n\n",
           cp_from_name,cp_to_name,
           cp_from_name,cp_to_name,(unsigned)Max,
           cp_from_name,cp_to_name);
         break;

  case 3:for(b=0;b<=(Max-1)>>8;b++)
  	     {
             if(Banks[b]==0) continue;
             fprintf(F,"const uint16_t %s_%sB%d[]={",cp_from_name,cp_to_name,b);
             for(i=b*256;i<(b+1)*256;i++)
		{
		fprintf(F,"  %d%s", (i>=Max)?0:trn.Data[i],
			i==((b+1)*256-1)?"};":",");
		if(i<Max)
		  if(cp_from.characters[i]!=NULL && trn.Data[i]!=0)
		     fprintf(F,"	/* %s */",cp_from.characters[i]);
		fputc('\n',F);
		}
             }
        fputc('\n',F);     
        fprintf(F,"const uint16_t *%s_%sB[]={",cp_from_name,cp_to_name);
        for(b=0;b<=(Max-1)>>8;b++)
  	     {
             if(Banks[b]==0) fprintf(F," NULL%s",(b==(Max-1)>>8)?"};":",");
                       else  fprintf(F," %s_%sB%d%s",cp_from_name,cp_to_name,(int)b,(b==(Max-1)>>8)?"};":",");
             fputc('\n',F);          
             }
        fprintf(F,"\nWSparseCpTranslator %s%s(\"%sTO%s\",%u,%s_%sB);\n\n",
           cp_from_name,cp_to_name,
           cp_from_name,cp_to_name,(unsigned)((Max-1)>>8)+1,
           cp_from_name,cp_to_name);
        break;

  case 4:for(b=0;b<=(Max-1)>>8;b++)
  	     {
             if(Banks[b]==0) continue;
             fprintf(F,"const uint8_t %s_%sB%d[]={",cp_from_name,cp_to_name,b);
             for(i=b*256;i<(b+1)*256;i++)
		{
		fprintf(F,"  %d%s", (i>=Max)?0:trn.Data[i],
			i==((b+1)*256-1)?"};":",");
		if(i<Max)
		  if(cp_from.characters[i]!=NULL && trn.Data[i]!=0)
		     fprintf(F,"	/* %s */",cp_from.characters[i]);
		fputc('\n',F);
		}
             }
        fputc('\n',F);     
        fprintf(F,"const uint8_t *%s_%sB[]={",cp_from_name,cp_to_name);
        for(b=0; b<=(Max-1)>>8; b++)
  	     {
             if(Banks[b]==0) fprintf(F," NULL%s",(b==(Max-1)>>8)?"};":",");
                       else  fprintf(F," %s_%sB%d%s",cp_from_name,cp_to_name,(int)b,(b==(Max-1)>>8)?"};":",");
             fputc('\n',F);          
             }
        fprintf(F,"\nBSparseCpTranslator %s%s(\"%sTO%s\",%u,%s_%sB);\n\n",
           cp_from_name,cp_to_name,
           cp_from_name,cp_to_name,(unsigned)((Max-1)>>8)+1,
           cp_from_name,cp_to_name);
        break;
  }
  return 0;
}



void Translator::erase(void)
{
  if(Data!=NULL) {free(Data);Data=NULL;}
  ToCp = FromCp = NULL; 
}


void codepage::Erase(void)
{
  if(characters)
  {
    for(int i=0; i<MaxUsed; i++)
    {
      if(characters[i]!=NULL)
      {
        free(characters[i]);
        characters[i] = NULL;
      }
    }
    free(characters);
    characters=NULL;
  }
  MaxUsed = Allocated = 0;
}


codepage::~codepage(void)
{
  Erase();
  if(name) {free(name);name=NULL;}
};
  


/*This procedure displays errors that occured inside atoms unit*/
#ifdef ERROR_HANDLER
void RaiseError(int ErrNo, const void *Instance)
{
static int LastErrNo=0;
static const void *LastInstance=NULL;
const char *UnitName="";
  //if(Verbosing<=-1) return;
  if(LastErrNo!=ErrNo || LastInstance!=Instance)
    {
     switch(ErrNo >> 8)
	{
        case 1: UnitName="Strings"; break;	// StringsId = 0x100,
        case 2: UnitName="Sets"; break;	// SetsId =    0x200,
        case 3: UnitName="Lists"; break;	// ListsId =   0x300,
        case 4: UnitName="Matrix"; break;	// MatrixId =  0x400,
	case 5: UnitName="Stack"; break;	// StackId =   0x500,
	case 6: UnitName="Interval"; break;	// IntervalId =0x600,
	case 7: UnitName="Raster"; break;	// RasterId =  0x700,
	case 8: UnitName="DblList"; break;	// DblListId = 0x800
	}
    fprintf(stderr,"\nERROR: Internal inside ATOMS unit%s 0x%X object:(%p)",
	UnitName, ErrNo, Instance);
    LastErrNo=ErrNo;LastInstance=Instance;
    }
}
#endif
