/*
 * UPIWIN - Micro Pi Windowing Framework Kernel
 * Copyright (C) 2019 Amy Bowersox/Erbosoft Metaverse Design Solutions
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *-------------------------------------------------------------------------
 */
#include <stdlib.h>
#include <string.h>
#include <zip.h>
#include "wintype.h"
#include "scode.h"
#include "config.h"
#include "log.h"
#include "resources.h"

typedef struct tagRESFILE {
  struct tagRESFILE *next;
  struct tagRESFILE *prev;
  zip_t *resource;
} RESFILE, *PRESFILE;

typedef struct tagRESOURCEINFO {
  zip_t *resource_file;
  zip_stat_t entryinfo;
} RESOURCEINFO, *PRESOURCEINFO;

/* conversion table from zip error codes to our HRESULT values */
static const struct tagCONVERSIONTABLE {
  int zip_err_code;
  HRESULT sys_err_code;
} conversion_table[] = {
  { ZIP_ER_OK,       S_OK },
  { ZIP_ER_SEEK,     STG_E_SEEKERROR },
  { ZIP_ER_READ,     STG_E_READFAULT },
  { ZIP_ER_WRITE,    STG_E_WRITEFAULT },
  { ZIP_ER_NOENT,    STG_E_FILENOTFOUND },
  { ZIP_ER_MEMORY,   E_OUTOFMEMORY },
  { ZIP_ER_INVAL,    E_INVALIDARG },
  { ZIP_ER_INTERNAL, E_UNEXPECTED },
  { -1, 0 }
};

/* references to system resource data in zip format */
extern uint8_t _binary_sysresources_zip_start[];
extern uint8_t _binary_sysresources_zip_end;
extern uint8_t _binary_sysresources_zip_size;

static zip_t *sysresource = NULL;   /* system resource file */
static PRESFILE resfiles = NULL;    /* all open resource files */

static HRESULT ziperror_to_hresult(zip_error_t *errinfo)
{
  register int i;

  for (i = 0; conversion_table[i].zip_err_code >= 0; i++)
    if (conversion_table[i].zip_err_code == errinfo->zip_err)
      return conversion_table[i].sys_err_code;
  return MAKE_SCODE(SEVERITY_ERROR, FACILITY_ZIP, errinfo->zip_err);
}

static BOOL check_in_list(PRESFILE presfile)
{
  register PRESFILE p = resfiles;
  do
  {
    if (p == presfile)
      return TRUE;
    p = p->next;
  } while (p != resfiles);
  return FALSE;
}

HRESULT Rsrc_load_file(PCSTR filename, PHRESFILE newhandle)
{
  HRESULT hr = S_OK;
  PRESFILE pfile = NULL;
  zip_source_t *source;
  zip_error_t errinfo;

  if (!newhandle)
    return E_POINTER;
  *newhandle = (HRESFILE)NULL;

  pfile = (PRESFILE)malloc(sizeof(RESFILE));
  if (!pfile)
    return E_OUTOFMEMORY;
  memset(pfile, 0, sizeof(RESFILE));

  zip_error_init(&errinfo);
  source = zip_source_file_create(filename, 0, 0, &errinfo);
  if (source)
  {
    pfile->resource = zip_open_from_source(source, ZIP_RDONLY, &errinfo);
    if (!(pfile->resource))
      zip_source_free(source);
  }
  if (!(pfile->resource))
    hr = ziperror_to_hresult(&errinfo);
  zip_error_fini(&errinfo);
  if (SUCCEEDED(hr))
  {
    if (resfiles)
    {
      pfile->next = resfiles;
      pfile->prev = resfiles->prev;
      resfiles->prev->next = pfile;
      resfiles->prev = pfile;
    }
    else
      pfile->next = pfile->prev = pfile;
    resfiles = pfile;
    *newhandle = (HRESFILE)pfile;
  }
  return hr;
}

static HRESULT internal_close(PRESFILE presfile)
{
  HRESULT hr = S_OK;

  if (zip_close(presfile->resource))
    hr = ziperror_to_hresult(zip_get_error(presfile->resource));
  if (resfiles == presfile)
  {
    resfiles = presfile->next;
    if (resfiles == presfile)
      resfiles = NULL;
  }
  presfile->prev->next = presfile->next;
  presfile->next->prev = presfile->prev;
  free(presfile);
  return hr;
}

HRESULT Rsrc_close_file(HRESFILE handle)
{
  if (check_in_list((PRESFILE)handle))
    return internal_close((PRESFILE)handle);
  return E_HANDLE;
}

HRESULT Rsrc_find_resource(HRESFILE hfile, PCSTR name, PCSTR type, PHRSRC presource)
{
  HRESULT hr = S_OK;
  PRESFILE pfile = (PRESFILE)hfile;
  PRESOURCEINFO info = NULL;
  zip_t *theresource;
  zip_int64_t index;

  if (pfile)
  {
    if (check_in_list(pfile))
      theresource = pfile->resource;
    else
      return E_HANDLE;
  }
  else
    theresource = sysresource;
  ASSERT(theresource);
  if (!name)
    return E_INVALIDARG;
  if (!presource)
    return E_POINTER;
  *presource = (HRSRC)NULL;

  index = zip_name_locate(theresource, name, ZIP_FL_NOCASE);
  if (index < 0)
    hr = ziperror_to_hresult(zip_get_error(theresource));
  if (SUCCEEDED(hr))
  {
    info = (PRESOURCEINFO)malloc(sizeof(RESOURCEINFO));
    if (info)
    {
      if (zip_stat_index(theresource, index, ZIP_FL_NOCASE, &(info->entryinfo)))
        hr = ziperror_to_hresult(zip_get_error(theresource));
      if (SUCCEEDED(hr))
      {
        info->entryinfo.index = index;
        info->entryinfo.valid |= ZIP_STAT_INDEX;
        info->resource_file = theresource;
        *presource = (HRSRC)info;
      }
      if (FAILED(hr))
        free(info);
    }
    else
      hr = E_OUTOFMEMORY;
  }
  return hr;
}

HRESULT Rsrc_free_resource(HRSRC resource)
{
  if (!resource)
    return E_HANDLE;
  free((PRESOURCEINFO)resource);
  return S_OK;
}

UINT32 Rsrc_sizeof_resource(HRSRC resource)
{
  PRESOURCEINFO p = (PRESOURCEINFO)resource;
  if (!p)
    return 0;
  if (p->entryinfo.valid & ZIP_STAT_SIZE)
    return (UINT32)(p->entryinfo.size);
  return 0;
}

HRESULT Rsrc_read_resource_here(HRSRC resource, PVOID buffer, UINT32 size, PUINT32 actual_read)
{
  HRESULT hr = S_OK;
  PRESOURCEINFO p = (PRESOURCEINFO)resource;
  UINT32 max_size = size;
  UINT32 already_read = 0;
  PBYTE pbuffer = (PBYTE)buffer;
  zip_int64_t nread;
  zip_file_t *fp;

  if (!p)
    return E_HANDLE;
  if (!buffer)
    return E_POINTER;
  if (p->entryinfo.valid & ZIP_STAT_SIZE)
  {
    if ((UINT32)(p->entryinfo.size) < max_size)
      max_size = (UINT32)(p->entryinfo.size);
    else if ((UINT32)(p->entryinfo.size) > max_size)
      hr = UPIWIN_S_PARTIALRSRC;
  }

  fp = zip_fopen_index(p->resource_file, p->entryinfo.index, ZIP_FL_NOCASE);
  if (fp)
  {
    while (SUCCEEDED(hr) && (max_size > 0))
    {
      nread = zip_fread(fp, pbuffer, max_size);
      if (nread < 0)
      {
        hr = ziperror_to_hresult(zip_get_error(p->resource_file));
        break;
      }
      pbuffer += (UINT_PTR)nread;
      max_size -= (UINT32)nread;
      already_read += (UINT32)nread;
    }
    zip_fclose(fp);
  }
  else
    hr = ziperror_to_hresult(zip_get_error(p->resource_file));
  if (actual_read)
    *actual_read = already_read;
  return hr;
}

static void rsrc_cleanup(void)
{
  while (resfiles)
    internal_close(resfiles);
  zip_close(sysresource);
  sysresource = NULL;
}

HRESULT Rsrc_setup(void)
{
  HRESULT hr = S_OK;
  zip_source_t *syssource;
  zip_error_t errinfo;

  Log(LDEBUG, "system resource length = %u", (UINT_PTR)(&_binary_sysresources_zip_size));
  zip_error_init(&errinfo);
  syssource = zip_source_buffer_create(_binary_sysresources_zip_start, (UINT_PTR)(&_binary_sysresources_zip_size),
                                       0, &errinfo);
  if (!syssource)
  {
    hr = ziperror_to_hresult(&errinfo);
    goto error_0;
  }

  sysresource = zip_open_from_source(syssource, ZIP_RDONLY, &errinfo);
  if (!sysresource)
  {
    hr = ziperror_to_hresult(&errinfo);
    goto error_1;
  }

  hr = Config_exitfunc(rsrc_cleanup);
  if (FAILED(hr))
    rsrc_cleanup();
  return hr;

error_1:
  zip_source_free(syssource);
error_0:
  zip_error_fini(&errinfo);
  return hr;
}
