/*
 * $Id$
 *
 * Copyright (C) 1993-1999 by Jochen Wiedmann and Marcin Orlowski
 * Copyright (C) 2002-2015 FlexCat Open Source Team
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#if defined(AMIGA)
  #include <proto/codesets.h>
#else
  #include <iconv.h>
  #include <errno.h>
#endif

#include "flexcat.h"
#include "readprefs.h"
#include "swapfuncs.h"
#include "showfuncs.h"
#include "scancd.h"
#include "scanct.h"
#include "createcat.h"
#include "globals.h"
#include "utils.h"
#include "openlibs.h"
#include "SDI_compiler.h"

const char VString[] = VERS " [" SYSTEMSHORT "/" CPU "] (" EXE_DATE ")\n" EXE_COPYRIGHT;
const char EString[] = "Contact: http://sf.net/p/flexcat/";

/// MyExit

void MyExit(int Code)
{
#ifdef AMIGA
  if((NumberOfWarnings > 0 || Code != 0) && !NoBeep)
    DisplayBeep(NULL);
#endif
  CloseFlexCatCatalog();
  CloseLibs();
  exit(Code);
}

///

#ifndef AMIGA

/*
 * This array is designed for mapping upper and lower case letters
 * together for a case independent comparison.  The mappings are
 * based upon ascii character sequences.
 */

typedef unsigned char uc;
static const unsigned char charmap[] =
{
  (uc)'\000', (uc)'\001', (uc)'\002', (uc)'\003', (uc)'\004', (uc)'\005', (uc)'\006', (uc)'\007',
  (uc)'\010', (uc)'\011', (uc)'\012', (uc)'\013', (uc)'\014', (uc)'\015', (uc)'\016', (uc)'\017',
  (uc)'\020', (uc)'\021', (uc)'\022', (uc)'\023', (uc)'\024', (uc)'\025', (uc)'\026', (uc)'\027',
  (uc)'\030', (uc)'\031', (uc)'\032', (uc)'\033', (uc)'\034', (uc)'\035', (uc)'\036', (uc)'\037',
  (uc)'\040', (uc)'\041', (uc)'\042', (uc)'\043', (uc)'\044', (uc)'\045', (uc)'\046', (uc)'\047',
  (uc)'\050', (uc)'\051', (uc)'\052', (uc)'\053', (uc)'\054', (uc)'\055', (uc)'\056', (uc)'\057',
  (uc)'\060', (uc)'\061', (uc)'\062', (uc)'\063', (uc)'\064', (uc)'\065', (uc)'\066', (uc)'\067',
  (uc)'\070', (uc)'\071', (uc)'\072', (uc)'\073', (uc)'\074', (uc)'\075', (uc)'\076', (uc)'\077',
  (uc)'\100', (uc)'\141', (uc)'\142', (uc)'\143', (uc)'\144', (uc)'\145', (uc)'\146', (uc)'\147',
  (uc)'\150', (uc)'\151', (uc)'\152', (uc)'\153', (uc)'\154', (uc)'\155', (uc)'\156', (uc)'\157',
  (uc)'\160', (uc)'\161', (uc)'\162', (uc)'\163', (uc)'\164', (uc)'\165', (uc)'\166', (uc)'\167',
  (uc)'\170', (uc)'\171', (uc)'\172', (uc)'\133', (uc)'\134', (uc)'\135', (uc)'\136', (uc)'\137',
  (uc)'\140', (uc)'\141', (uc)'\142', (uc)'\143', (uc)'\144', (uc)'\145', (uc)'\146', (uc)'\147',
  (uc)'\150', (uc)'\151', (uc)'\152', (uc)'\153', (uc)'\154', (uc)'\155', (uc)'\156', (uc)'\157',
  (uc)'\160', (uc)'\161', (uc)'\162', (uc)'\163', (uc)'\164', (uc)'\165', (uc)'\166', (uc)'\167',
  (uc)'\170', (uc)'\171', (uc)'\172', (uc)'\173', (uc)'\174', (uc)'\175', (uc)'\176', (uc)'\177',
  (uc)'\200', (uc)'\201', (uc)'\202', (uc)'\203', (uc)'\204', (uc)'\205', (uc)'\206', (uc)'\207',
  (uc)'\210', (uc)'\211', (uc)'\212', (uc)'\213', (uc)'\214', (uc)'\215', (uc)'\216', (uc)'\217',
  (uc)'\220', (uc)'\221', (uc)'\222', (uc)'\223', (uc)'\224', (uc)'\225', (uc)'\226', (uc)'\227',
  (uc)'\230', (uc)'\231', (uc)'\232', (uc)'\233', (uc)'\234', (uc)'\235', (uc)'\236', (uc)'\237',
  (uc)'\240', (uc)'\241', (uc)'\242', (uc)'\243', (uc)'\244', (uc)'\245', (uc)'\246', (uc)'\247',
  (uc)'\250', (uc)'\251', (uc)'\252', (uc)'\253', (uc)'\254', (uc)'\255', (uc)'\256', (uc)'\257',
  (uc)'\260', (uc)'\261', (uc)'\262', (uc)'\263', (uc)'\264', (uc)'\265', (uc)'\266', (uc)'\267',
  (uc)'\270', (uc)'\271', (uc)'\272', (uc)'\273', (uc)'\274', (uc)'\275', (uc)'\276', (uc)'\277',
  (uc)'\300', (uc)'\341', (uc)'\342', (uc)'\343', (uc)'\344', (uc)'\345', (uc)'\346', (uc)'\347',
  (uc)'\350', (uc)'\351', (uc)'\352', (uc)'\353', (uc)'\354', (uc)'\355', (uc)'\356', (uc)'\357',
  (uc)'\360', (uc)'\361', (uc)'\362', (uc)'\363', (uc)'\364', (uc)'\365', (uc)'\366', (uc)'\367',
  (uc)'\370', (uc)'\371', (uc)'\372', (uc)'\333', (uc)'\334', (uc)'\335', (uc)'\336', (uc)'\337',
  (uc)'\340', (uc)'\341', (uc)'\342', (uc)'\343', (uc)'\344', (uc)'\345', (uc)'\346', (uc)'\347',
  (uc)'\350', (uc)'\351', (uc)'\352', (uc)'\353', (uc)'\354', (uc)'\355', (uc)'\356', (uc)'\357',
  (uc)'\360', (uc)'\361', (uc)'\362', (uc)'\363', (uc)'\364', (uc)'\365', (uc)'\366', (uc)'\367',
  (uc)'\370', (uc)'\371', (uc)'\372', (uc)'\373', (uc)'\374', (uc)'\375', (uc)'\376', (uc)'\377',
};

/// Stricmp

int Stricmp(const char *str1, const char *str2)
{
  unsigned char u1, u2;

  for(;;)
  {
    u1 = (unsigned char)*str1++;
    u2 = (unsigned char)*str2++;

    if(charmap[u1] != charmap[u2])
      return charmap[u1] - charmap[u2];

    if(u1 == '\0')
      return 0;
  }
}

///
/// Strnicmp

int Strnicmp(const char *str1, const char *str2, int len)
{
  unsigned char u1, u2;

  for(; len != 0; --len)
  {
    u1 = (unsigned char)*str1++;
    u2 = (unsigned char)*str2++;
    if(charmap[u1] != charmap[u2])
      return charmap[u1] - charmap[u2];

    if(u1 == '\0')
      return 0;
  }

  return 0;
}
#endif
///
/// utf8_strlen

size_t utf8_strlen(const char *str)
{
  size_t ix = strlen(str);
  size_t i, q;

  for(q=0, i=0; i < ix; i++, q++)
  {
    int c = (unsigned char)str[i];

    if(c >= 0 && c <= 127)
      i += 0;
    else if((c & 0xe0) == 0xc0)
      i += 1;
    else if((c & 0xf0) == 0xe0)
      i += 2;
    else if((c & 0xf8) == 0xf0)
      i += 3;
    else
      return 0; // invalid utf8
  }

  return q;
}
///
/// AllocString

/* This allocates a string */

char *AllocString(const char *str)
{
  char *ptr;

  if((ptr = malloc(strlen(str) + 1)) == NULL)
    MemError();

  strcpy(ptr, str);

  return ptr;
}

///
/// Add a string to an already allocated one

char *AddString(char *str, const char *astr)
{
  char *ptr;

  if((ptr = malloc(strlen(str) + strlen(astr) + 1)) == NULL)
    MemError();

  strcpy(ptr, str);
  strcat(ptr, astr);

  free(str);

  return ptr;
}

///
/// Convert a string from one charset to another

#ifdef AMIGA
char *ConvertString(char *str, const char *from_charset, const char *to_charset)
{
  char *result = NULL;
  BOOL fromIsUTF8 = (Stricmp(from_charset, "UTF-8") == 0 || Stricmp(from_charset, "UTF8") == 0);
  BOOL toIsUTF8 = (Stricmp(to_charset, "UTF-8") == 0 || Stricmp(to_charset, "UTF8") == 0);

  if(fromIsUTF8 == TRUE && toIsUTF8 == TRUE)
  {
    // no need to convert from UTF8 to UTF8
    // just return a plain copy of the string
    result = strdup(str);
  }
  else
  {
    struct codeset *dstCodeset;

    dstCodeset = CodesetsFind((STRPTR)to_charset,
                              CSA_FallbackToDefault, FALSE,
                              TAG_DONE);
    if(dstCodeset != NULL)
    {
      ULONG dstLen = 0;
      char *dstText = NULL;
      int errPtr = 0;

      if(fromIsUTF8 == TRUE)
      {
        dstText = CodesetsUTF8ToStr(CSA_Source,      str,
                                    CSA_DestCodeset, dstCodeset,
                                    CSA_DestLenPtr,  &dstLen,
                                    CSA_ErrPtr,      &errPtr,
                                    TAG_DONE);
      }
      else
      {
        struct codeset *srcCodeset;

        srcCodeset = CodesetsFind((STRPTR)from_charset,
                                  CSA_FallbackToDefault, FALSE,
                                  TAG_DONE);

        if(srcCodeset != NULL)
        {
          dstText = CodesetsConvertStr(CSA_Source,        str,
                                       CSA_SourceCodeset, srcCodeset,
                                       CSA_DestCodeset,   dstCodeset,
                                       CSA_DestLenPtr,    &dstLen,
                                       CSA_ErrPtr,        &errPtr,
                                       TAG_DONE);
        }
        else
          ShowWarn(MSG_ERR_UNKNOWN_SOURCE_CHARSET, from_charset);
      }

      if(dstText != NULL && dstLen != 0 && errPtr == 0)
      {
        char *buf;

        // copy the converted string into a separately allocated string
        if((buf = malloc(dstLen+1)) != NULL)
        {
          memcpy(buf, dstText, dstLen);
          buf[dstLen] = '\0';
          result = buf;
        }

        CodesetsFreeA(dstText, NULL);

        if(buf == NULL)
          MemError();
      }
      else
      {
        if(errPtr != 0)
          ShowWarn(MSG_ERR_INVALID_CHARS_FOUND, errPtr);

        if(dstText == NULL)
          MemError();
      }
    }
    else
      ShowWarn(MSG_ERR_UNKNOWN_DESTINATION_CHARSET, to_charset);
  }

  return result;
}
#else
char *ConvertString(char *str, const char *from_charset, const char *to_charset)
{
  char *result = NULL;
  iconv_t ict;

  if((ict = iconv_open(to_charset, from_charset)) != (iconv_t)-1)
  {
    size_t inleft = strlen(str);
    char *buf;

    if((buf = malloc(inleft+1)) != NULL)
    {
      size_t outleft = inleft;
      char *outbuf = buf;

      if(iconv(ict, &str, &inleft, &outbuf, &outleft) != (size_t)-1)
      {
        *outbuf = '\0';
        result = buf;
      }
      else
      {
        ShowWarn(MSG_ERR_ICONV_FAILED, strerror(errno));
        free(buf);
      }
    }
    else
      MemError();

    iconv_close(ict);
  }
  else
    ShowWarn(MSG_ERR_ICONV_OPEN_FAILED, strerror(errno));

  return result;
}
#endif

///
/// Add catalog chunk

/* This adds a new catalog chunk to the list of catalog
   chunks. */

char *AddCatalogChunk(char *ID, const char *string)
{
  struct CatalogChunk *cc, **ccptr;

  if((cc = malloc(sizeof(*cc))) == NULL)
    MemError();

  cc->Next = NULL;
  cc->ID = *((ULONG *)ID);
  cc->ChunkStr = AllocString(string);

  /* Put the new chunk at the end of the chunk list. */

  for(ccptr = &FirstChunk; *ccptr != NULL; ccptr = &(*ccptr)->Next)
  {
  }
  *ccptr = cc;

  return cc->ChunkStr;
}

///
/// gethex

/* This translates an hex character. */

int gethex(int c)
{
  if(c >= '0' && c <= '9')
    return c - '0';
  else if(c >= 'a' && c <= 'f')
    return c - 'a' + 10;
  else if(c >= 'A' && c <= 'F')
    return c - 'A' + 10;

  ShowError(MSG_ERR_EXPECTEDHEX);

  return 0;
}

///
/// getoctal

/* This translates an octal digit. */

int getoctal(int c)
{
  if(c >= '0' && c <= '7')
    return c - '0';

  ShowError(MSG_ERR_EXPECTEDOCTAL);

  return 0;
}

///
/// ReadLine

/* Reading a line is somewhat complicated in order to allow lines of any
   length.

   Inputs: fp - the file, where the input comes from
       AllowComment - TRUE if a leading semicolon should force the
              line to be interpreted as a comment */

char *ReadLine(FILE *fp, UNUSED int AllowComment)
{
  char *NewLine = NULL;
  int c = '\0';
  int Len = 0, LineLen = 0;
  int FirstChar = TRUE;
  int BackslashSeen = FALSE;
  int BackslashSeenOn = 0;    /* Position where the last backslash was seen. */
  int CommentLine = FALSE;    /* If TRUE, we should ignore any trailing \'s */

  while(c != EOF)
  {
    if(Len + 10 > LineLen)
    {
      NewLine = realloc(NewLine, LineLen + BUFSIZE);
      LineLen += BUFSIZE;
    }

    c = getc(fp);

    if(FirstChar)
    {
      if(c == EOF)
      {
      	if(NewLine != NULL)
          free(NewLine);
        return NULL;
      }

      if(c == ';')
      {
        CommentLine = TRUE;
      }

      FirstChar = FALSE;
    }

    switch(c)
    {
      case '\r':
        break;

      case '\n':
        ++ScanLine;
        if(BackslashSeen)
        {
          NewLine[Len++] = c;
          BackslashSeen = FALSE;
          break;
        }
        c = EOF;

      case EOF:
        break;

      /* Check for trailing \\ */
      case '\\':
        {
          if(!CommentLine)
          {
            if(BackslashSeen)
            {
              if(BackslashSeenOn ==(Len - 1))
              {
                BackslashSeen = FALSE;
                NewLine[Len++] = c;
                break;
              }
            }

            BackslashSeen = TRUE;
            BackslashSeenOn = Len;
          }

          NewLine[Len++] = c;
          break;
        }


      default:
        BackslashSeen = FALSE;
        NewLine[Len++] = c;
    }
  }

  NewLine[Len] = '\0';

  return NewLine;

}

///
/// OverSpace

/* This removes trailing blanks. */

void OverSpace(char **strptr)
{
  int c;

  while((c = **strptr) == ' ' || c == '\t')
  {
     (*strptr)++;
  }
}

///
/// Expunge

void Expunge(void)
{
#ifdef AMIGA
  if(DoExpunge)
  {
    struct Library *localeBase;

    // this may look utterly wrong since we are trying to RemLibrary() a library
    // which we just opened. But this is the most convenient way to invoke the
    // Expunge() function of locale.library, which will just remove any still
    // opened but unused catalog file from memory without removing locale.library
    // itself.
    if((localeBase = OpenLibrary( "locale.library", 0)) != NULL)
    {
      RemLibrary(localeBase);
      CloseLibrary(localeBase);
    }
  }
#endif // AMIGA
}

///
/// ReadChar

/* ReadChar scans an input line and translates the backslash characters.
   Inputs: char *  - a pointer to a string pointer; the latter points
           to the next character to be read and points behind
           the read bytes after executing ReadChar
       dest  - a pointer to a buffer, where the read bytes should be
           stored
   Result: number of bytes that are written to dest (between 0 and 2) */

int ReadChar(char **strptr, char *dest)
{
  char c;
  int i;

  switch(c = *((*strptr)++))
  {
    case '\\':
    {
      switch(c = tolower((int)*((*strptr)++)))
      {
        case '\n':
          return(0);
        break;

        case 'b':
          *dest = '\b';
        break;

        case 'c':
          *dest = '\233';
        break;

        case 'e':
          *dest = '\033';
        break;

        case 'f':
          *dest = '\f';
        break;

        case 'g':
          *dest = '\007';
        break;

        case 'n':
          *dest = '\n';
        break;

        case 'r':
          *dest = '\r';
        break;

        case 't':
          *dest = '\t';
        break;

        case 'v':
          *dest = '\013';
        break;

        case 'x':
        {
          *dest = gethex((int)**strptr);
          (*strptr)++;
          c = **strptr;
          if((c >= '0' && c <= '9') ||
             (c >= 'a' && c <= 'f') ||
             (c >= 'A' && c <= 'F'))
          {
            *dest =(*dest << 4) + gethex((int)c);
            (*strptr)++;
          }
        }
        break;

        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        {
          *dest = getoctal((int)c);

          for(i = 0; i < 2; i++)
          {
            c = **strptr;
            if(c >= '0' && c <= '7')
            {
              *dest =(*dest << 3) + getoctal((int)c);
              (*strptr)++;
            }
          }
        }
        break;

        case ')':
        case '\\':
          *(dest++) = '\\';
          *dest = c;
          return(2);
        break;

        default:
          *dest = c;
        break;
      }
    }
    break;

    default:
    {
      *dest = c;
    }
    break;
  }
  return(1);
}

///
/// AllocFileName

/* This function creates a copy of a filename, and optionally
   removes an ending and pathname components, if desired.
   Inputs: filename  - the filename to copy
       howto   - a set of bits
             bit 0: 1 = remove ending, 0 = leave it
             bit 1: 1 = remove pathname, 0 = leave it
   Result: the copy of the filename
*/

char *AllocFileName(char *filename, int howto)
{
  char *tempstr, *ptr;

  if((tempstr = strdup(filename)) == NULL)
  {
    MemError();
    MyExit(10);
  }

  /* Remove pathname components, if desired. */

  if(howto & 2)
  {
    if((ptr = strchr(tempstr, ':')) != NULL)
    {
      tempstr = ptr + 1;
    }
    if((ptr = strrchr(tempstr, '/')) != NULL)
    {
      tempstr = ptr + 1;
    }
  }

  /* Remove ending, if desired. */

  if(howto & 1)
  {
    if((ptr = strrchr(tempstr, '.')) != NULL)
    {
      *ptr = '\0';
    }
  }

  return(tempstr);
}

///
/// AddFileName

/* This function adds a pathname and a filename to a full
   filename.
   Inputs: pathname - the leading pathname
       filename - the filename
   Result: The new filename */

char *AddFileName(char *pathname, char *filename)
{
  char *buffer;

#ifdef AMIGA
  int size = strlen(pathname) + strlen(filename) + 2;

  if((buffer = malloc(size)) == NULL)
  {
    MemError();
    MyExit(10);
  }

  strcpy(buffer, pathname);
  AddPart((char *)buffer, (char *)filename, size);
#else
  if(asprintf(&buffer, "%s/%s", pathname, filename) < 0)
  {
    MemError();
    MyExit(10);
  }
#endif

  return buffer;
}

///
/// Usage

/* The Usage function describes the program's calling syntax. */

void Usage(void)
{
  fprintf(stderr, "%s\n", VString);
  fprintf(stderr, "%s\n", EString);
  fprintf(stderr,
      "\n" \
      "%s\n" \
      "     FlexCat CDFILE/A,CTFILE,POFILE,CATALOG/K,NEWCTFILE/K,SOURCES/M,\n" \
      "             WARNCTGAPS/S,NOOPTIM/S,FILL/S,FLUSH/S,NOBEEP/S,\n" \
      "             QUIET/S,NOLANGTOLOWER/S,NOBUFFEREDIO/S,MODIFIED/S,\n" \
      "             CODESET/K,COPYMSGNEW/S,OLDMSGNEW/K\n" \
      "\n", MSG_USAGE_HEAD);
  fprintf(stderr, "%s\n", MSG_USAGE);
  MyExit(5);
}

///
/// wbmain

/* Dice's entry point for workbench programs */

#if defined(AMIGA)  &&  defined(_DCC)
void wbmain(struct WBStartup *wbmsg)
{
  fprintf(stderr, "FlexCat can't be run from Workbench!\n\n");
  fprintf(stderr, "Open a shell session and type FlexCat\n");
  fprintf(stderr, "for syntax and more information.\n");

  exit(5);
}
#endif
///

