#include <vcl.h>
#pragma hdrstop

#include <ucl/ucl.h>
#include "lutil.h"
#include "uclwin.h"

unsigned long total_in = 0;
unsigned long total_out = 0;
/* don't compute or verify checksum, always use fast decompressor */
static ucl_bool opt_fast = 0;
/* magic file header for compressed files */
static const unsigned char magic[8] = { 0x00, 0xe9, 0x55, 0x43, 0x4c, 0xff, 0x01, 0x1a };

FeedbackProc *StdFeedback = NULL;

#define MSG(a) Application->MessageBox(AnsiString(a).c_str(),"Ошибка",MB_OK|MB_ICONERROR)

/*************************************************************************
// file IO
**************************************************************************/

ucl_uint xread(FILE *f, ucl_voidp buf, ucl_uint len, ucl_bool allow_eof)
{
  if (f == NULL)
    return 0;
  ucl_uint l;
  l = ucl_fread(f,buf,len);
  if (l > len)
  {
    MSG("UCL: неизвестная ошибка");
    return 0;
  }
  if (l != len && !allow_eof)
  {
    MSG("UCL: Ошибка чтения. Возможно, достигнут конец файла");
    return 0;
  }
  total_in += l;
  return l;
}

ucl_uint xwrite(FILE *f, const ucl_voidp buf, ucl_uint len)
{
  ucl_uint l;
  if (f != NULL)
  {
    l = ucl_fwrite(f,buf,len);
    if (l != len)
    {
      MSG ("UCL: Ошибка записи. ["+IntToStr((long)len)+" "+IntToStr((long)l)+"]. (диск заполнен?)");
      return 0;
    }
  }
  total_out += len;
  return len;
}

int xgetc(FILE *f)
{
  unsigned char c;
  xread(f,(ucl_voidp) &c,1,0);
  return c;
}

void xputc(FILE *f, int c)
{
  unsigned char cc = (unsigned char) c;
  xwrite(f,(const ucl_voidp) &cc,1);
}

/* read and write portable 32-bit integers */
ucl_uint32 xread32(FILE *f)
{
  unsigned char b[4];
  ucl_uint32 v;
  xread(f,b,4,0);
  v = (ucl_uint32) b[3] << 0;
  v |= (ucl_uint32) b[2] << 8;
  v |= (ucl_uint32) b[1] << 16;
  v |= (ucl_uint32) b[0] << 24;
  return v;
}

void xwrite32(FILE *f, ucl_uint32 v)
{
  unsigned char b[4];
  b[3] = (unsigned char) (v >>  0);
  b[2] = (unsigned char) (v >>  8);
  b[1] = (unsigned char) (v >> 16);
  b[0] = (unsigned char) (v >> 24);
  xwrite(f,b,4);
}

/*************************************************************************
// util
**************************************************************************/

static ucl_uint get_overhead(int method, ucl_uint size)
{
  if (method == 0x2b || method == 0x2d || method == 0x2e)
    return size/8 + 256;
  return 0;
}

static char method_name[64];

static ucl_bool set_method_name(int method, int level)
{
    method_name[0] = 0;
    if (level < 1 || level > 10)
        return 0;
    if (method == 0x2b)
        sprintf(method_name,"NRV2B-99/%d", level);
    else if (method == 0x2d)
        sprintf(method_name,"NRV2D-99/%d", level);
    else if (method == 0x2e)
        sprintf(method_name,"NRV2E-99/%d", level);
    else
        return 0;
    return 1;
}

/*************************************************************************
// compress
**************************************************************************/

int do_compress(FILE *fi, FILE *fo, int method, int level, ucl_uint block_size)
{
  int r = 0;
  ucl_byte *in = NULL;
  ucl_byte *out = NULL;
  ucl_uint in_len;
  ucl_uint out_len;
  ucl_uint32 flags = opt_fast ? 0 : 1;
  ucl_uint32 checksum;
  ucl_uint overhead = 0;
  total_in = total_out = 0;
  /*/
   * Step 1: write magic header, flags & block size, init checksum
  /*/
  xwrite(fo,magic,sizeof(magic));
  xwrite32(fo,flags);
  xputc(fo,method);           /* compression method */
  xputc(fo,level);            /* compression level */
  xwrite32(fo,block_size);
  checksum = ucl_adler32(0,NULL,0);
  /*/
   * Step 2: allocate compression buffers and work-memory
  /*/
  overhead = get_overhead(method,block_size);
  in = (ucl_byte *) ucl_malloc(block_size);
  out = (ucl_byte *) ucl_malloc(block_size + overhead);
  if (in == NULL || out == NULL)
  {
    MSG("UCL: Не хватает памяти");
    r = 1;
    goto err;
  }
  /*/
   * Step 3: process blocks
  /*/
  for (;;)
  {
    /* read block */
    in_len = xread(fi,in,block_size,1);
    if (in_len <= 0)
      break;
    /* update checksum */
    if (flags & 1)
      checksum = ucl_adler32(checksum,in,in_len);
    /* compress block */
    r = UCL_E_ERROR;
    if (method == 0x2b)
      r = ucl_nrv2b_99_compress(in,in_len,out,&out_len,0,level,NULL,NULL);
    else if (method == 0x2d)
      r = ucl_nrv2d_99_compress(in,in_len,out,&out_len,0,level,NULL,NULL);
    else if (method == 0x2e)
      r = ucl_nrv2e_99_compress(in,in_len,out,&out_len,0,level,NULL,NULL);
    if (r != UCL_E_OK || out_len > in_len + get_overhead(method,in_len))
    {
      /* this should NEVER happen */
      MSG ("UCL: внутренняя ошибка - сжатие невозможно: "+IntToStr(r));
      r = 2;
      goto err;
    }
    /* write uncompressed block size */
    xwrite32(fo,in_len);
    if (out_len < in_len)
    {
      /* write compressed block */
      xwrite32(fo,out_len);
      xwrite(fo,out,out_len);
    }
    else
    {
      /* not compressible - write uncompressed block */
      xwrite32(fo,in_len);
      xwrite(fo,in,in_len);
    }
    if (StdFeedback!=NULL)
      StdFeedback(total_in);
  }
  /* write EOF marker */
  xwrite32(fo,0);
  /* write checksum */
  if (flags & 1)
    xwrite32(fo,checksum);
  r = 0;
err:
  ucl_free(out);
  ucl_free(in);
  return r;
}

/*************************************************************************/
/* decompress / test                                                     */
/*                                                                       */
/* We are using overlapping (in-place) decompression to save some        */
/* memory - see overlap.c.                                               */
/*************************************************************************/

int do_decompress(FILE *fi, FILE *fo)
{
  int r = 0;
  ucl_byte *buf = NULL;
  ucl_uint buf_len;
  unsigned char m [ sizeof(magic) ];
  ucl_uint32 flags;
  int method;
  int level;
  ucl_uint block_size;
  ucl_uint32 checksum;
  ucl_uint overhead = 0;
  total_in = total_out = 0;
  /*/
   * Step 1: check magic header, read flags & block size, init checksum
  /*/
  if (xread(fi,m,sizeof(magic),1) != sizeof(magic) || memcmp(m,magic,sizeof(magic)) != 0)
  {
    MSG("UCL: Ошибка заголовка файла - он не сжат UCL`ем");
    r = 1;
    goto err;
  }
  flags = xread32(fi);
  method = xgetc(fi);
  level = xgetc(fi);
  block_size = xread32(fi);
  overhead = get_overhead(method,block_size);
  if (overhead == 0 || !set_method_name(method, level))
  {
    MSG("UCL: Ошибка заголовка файла - неизвестный метод "+IntToStr(method)+" (уровень "+IntToStr(level)+")");
    r = 2;
    goto err;
  }
  if (block_size < 1024 || block_size > 8*1024*1024L)
  {
    MSG("UCL: Ошибка заголовка файла - неверный размер блока: "+IntToStr((long)block_size));
    r = 3;
    goto err;
  }
  checksum = ucl_adler32(0,NULL,0);
  /*/
   * Step 2: allocate buffer for in-place decompression
  /*/
  buf_len = block_size + overhead;
  buf = (ucl_byte *) ucl_malloc(buf_len);
  if (buf == NULL)
  {
    MSG("UCL: мало памяти");
    r = 4;
    goto err;
  }
  /*/
   * Step 3: process blocks
  /*/
  for (;;)
  {
    ucl_byte *in;
    ucl_byte *out;
    ucl_uint in_len;
    ucl_uint out_len;
    /* read uncompressed size */
    out_len = xread32(fi);
    /* exit if last block (EOF marker) */
    if (out_len == 0)
      break;
    /* read compressed size */
    in_len = xread32(fi);
    /* sanity check of the size values */
    if (in_len > block_size || out_len > block_size || in_len == 0 || in_len > out_len)
    {
      MSG("UCL: Ошибка размера блока - данные повреждены");
      r = 5;
      goto err;
    }
    /* place compressed block at the top of the buffer */
    in = buf + buf_len - in_len;
    out = buf;
    /* read compressed block data */
    xread(fi,in,in_len,0);
    if (in_len < out_len)
    {
      /* decompress - use safe decompressor as data might be corrupted */
      ucl_uint new_len = out_len;
      if (method == 0x2b)
      {
        if (opt_fast)
          r = ucl_nrv2b_decompress_8(in,in_len,out,&new_len,NULL);
        else
          r = ucl_nrv2b_decompress_safe_8(in,in_len,out,&new_len,NULL);
      }
      else if (method == 0x2d)
      {
        if (opt_fast)
          r = ucl_nrv2d_decompress_8(in,in_len,out,&new_len,NULL);
        else
          r = ucl_nrv2d_decompress_safe_8(in,in_len,out,&new_len,NULL);
      }
      else if (method == 0x2e)
      {
        if (opt_fast)
          r = ucl_nrv2e_decompress_8(in,in_len,out,&new_len,NULL);
        else
          r = ucl_nrv2e_decompress_safe_8(in,in_len,out,&new_len,NULL);
      }
      if (r != UCL_E_OK || new_len != out_len)
      {
        MSG("UCL: наружение сжатия данных: ошибка "+IntToStr(r)+" (0x"+IntToHex(method,2)+": "+IntToStr((long)in_len)+"/"+IntToStr((long)out_len)+"/"+IntToStr((long)new_len)+")");
        r = 6;
        goto err;
      }
      /* write decompressed block */
      xwrite(fo,out,out_len);
      /* update checksum */
      if ((flags & 1) && !opt_fast)
        checksum = ucl_adler32(checksum,out,out_len);
    }
    else
    {
      /* write original (incompressible) block */
      xwrite(fo,in,in_len);
      /* update checksum */
      if ((flags & 1) && !opt_fast)
        checksum = ucl_adler32(checksum,in,in_len);
    }
    if (StdFeedback!=NULL)
      StdFeedback(total_in);
  }
  /* read and verify checksum */
  if (flags & 1)
  {
    ucl_uint32 c = xread32(fi);
    if (!opt_fast && c != checksum)
    {
      MSG("UCL: ошибка проверки контрольной суммы - данные повреждены\n");
      r = 7;
      goto err;
    }
  }
  r = 0;
err:
  ucl_free(buf);
  return r;
}


/*************************************************************************
//
**************************************************************************/

/* open input file */
static FILE *xopen_fi(const char *name)
{
  FILE *f;
  f = fopen(name,"rb");
  if (f == NULL)
  {
    MSG("%s: невозможно открыть входной файл \""+AnsiString(name)+"\"\n");
    return 0;
  }
#if defined(HAVE_STAT) && defined(S_ISREG)
  {
    struct stat st;
#if defined(HAVE_LSTAT)
    if (lstat(name,&st) != 0 || !S_ISREG(st.st_mode))
#else
    if (stat(name,&st) != 0 || !S_ISREG(st.st_mode))
#endif
    {
      MSG("UCL: %s странный файл :)\n", name);
      fclose(f);
      return 0;
    }
  }
#endif
  return f;
}

/* open output file */
static FILE *xopen_fo(const char *name)
{
  FILE *f;
  f = fopen(name,"rb");
  if (f != NULL && Application->MessageBox ( ("UCL: Файл \""+AnsiString(name)+"\" уже существует. Перезаписать?").c_str(),"Подтвердите замену",MB_YESNO|MB_ICONQUESTION)==IDNO)
  {
    fclose(f);
    return 0;
  }
  f = fopen(name,"wb");
  if (f == NULL)
  {
    MSG("UCL: Невозможно открыть/создать файл \""+AnsiString(name)+"\"");
    return 0;
  }
  return f;
}

AnsiString __fastcall UclCompressFile (AnsiString InFile, AnsiString OutFile, int opt_method = 0x2b, int opt_level = 7)
{
  ucl_uint opt_block_size = (2*1024*1024L);
  if (!FileExists (InFile))
  {
    MSG ("UCL: Файл \""+InFile+"\" не существует");
    return "Ошибка";
  }
  FILE *fi = NULL;
  FILE *fo = NULL;
  if (!set_method_name(opt_method, opt_level))
  {
    MSG ("UCL: Внутренняя ошибка");
    return "UCL: Внутренняя ошибка";
  }
  fi = xopen_fi(InFile.c_str());
  fo = xopen_fo(OutFile.c_str());
  int r = do_compress(fi,fo,opt_method,opt_level,opt_block_size);
  fclose (fi);
  fclose (fo);
  if (r == 0)
    return ("UCL: Сжат файл \""+ExtractFileName(InFile)+"\" в файл \""+ExtractFileName(OutFile)+"\" методом "+AnsiString(method_name)+". Было: "+IntToStr(total_in)+", стало: "+IntToStr(total_out));
  else return ("UCL: Произошла ошибка при сжатии файла \""+InFile+"\"");
}

AnsiString __fastcall UclDecompressFile (AnsiString InFile, AnsiString OutFile)
{
  if (!FileExists (InFile))
  {
    MSG ("UCL: Файл \""+InFile+"\" не существует");
    return "";
  }
  FILE *fi = xopen_fi(InFile.c_str());
  FILE *fo = xopen_fo(OutFile.c_str());
  int r = do_decompress(fi,fo);
  fclose (fi);
  fclose (fo);
  if (r == 0)
    return ("UCL: Распакован файл \""+ExtractFileName(InFile)+"\" в файл \""+ExtractFileName(OutFile)+"\". Размер сжатого файла: "+IntToStr(total_in)+", размер распакованного файла: "+IntToStr(total_out));
  else return ("UCL: Произошла ошибка при распаковке файла \""+InFile+"\"");
}

void __fastcall UclFileInfo (AnsiString File, long &CompressedSize, long &DecompressedSize)
{
  if (!FileExists (File))
  {
    MSG ("UCL: Файл \""+File+"\" не существует");
    return;
  }
  FILE *fi = xopen_fi(File.c_str());
  int r = do_decompress(fi,NULL);
  fclose (fi);
  CompressedSize = total_in;
  DecompressedSize = total_out;
}
