#ifndef __internal_h__
#define __internal_h__

#include "libunshield.h"
#include "lib/unshield_config.h"

#if HAVE_STDINT_H
#include <stdint.h>
#elif HAVE_INTTYPES_H
#include <inttypes.h>
#endif

#include <stdbool.h>
#include <stdio.h>  /* for FILE */

#include "cabfile.h"

typedef struct _StringBuffer StringBuffer;

struct _StringBuffer
{
  StringBuffer* next;
  char* string;
};

typedef struct _Header Header;

struct _Header
{
  Header*   next;
  int       index;
  uint8_t*  data;
  size_t    size;
  int       major_version;

  /* shortcuts */
  CommonHeader    common;
  CabDescriptor   cab;
  uint32_t*       file_table;
  FileDescriptor** file_descriptors;

  int component_count;
  UnshieldComponent** components;

  int file_group_count;
  UnshieldFileGroup** file_groups;

  StringBuffer* string_buffer;
};

struct _Unshield
{
  Header* header_list;
  char* filename_pattern;
  const UnshieldIoCallbacks* io_callbacks;
  void* io_userdata;
};

/*
   Internal component functions
 */

UnshieldComponent* unshield_component_new(Header* header, uint32_t offset);
void unshield_component_destroy(UnshieldComponent* self);


/* 
   Internal file group functions
 */

UnshieldFileGroup* unshield_file_group_new(Header* header, uint32_t offset);
void unshield_file_group_destroy(UnshieldFileGroup* self);


/*
   Helpers
 */

char *unshield_get_base_directory_name(Unshield *unshield);
long int unshield_get_path_max(Unshield* unshield);
FILE* unshield_fopen_for_reading(Unshield* unshield, int index, const char* suffix);
long long unshield_fsize(Unshield* unshield, FILE* file);
bool unshield_read_common_header(uint8_t** buffer, CommonHeader* common);

const char* unshield_get_utf8_string(Header* header, const void* buffer);
const char* unshield_header_get_string(Header* header, uint32_t offset);
uint8_t* unshield_header_get_buffer(Header* header, uint32_t offset);
char *unshield_get_last_path_separator(char *path);

static inline void* unshield_fopen(Unshield* unshield, const char *filename, const char *modes)
{
    return unshield->io_callbacks->fopen(filename, modes, unshield->io_userdata);
}

static inline int unshield_fseek(Unshield* unshield, void *file, long int offset, int whence)
{
    return unshield->io_callbacks->fseek(file, offset, whence, unshield->io_userdata);
}

static inline long int unshield_ftell(Unshield* unshield, void *file)
{
    return unshield->io_callbacks->ftell(file, unshield->io_userdata);
}

static inline size_t unshield_fread(Unshield* unshield, void *ptr, size_t size, size_t n, void *file)
{
    return unshield->io_callbacks->fread(ptr, size, n, file, unshield->io_userdata);
}

static inline size_t unshield_fwrite(Unshield* unshield, const void *ptr, size_t size, size_t n, void *file)
{
    return unshield->io_callbacks->fwrite(ptr, size, n, file, unshield->io_userdata);
}

static inline int unshield_fclose(Unshield* unshield, void *ptr)
{
    return unshield->io_callbacks->fclose(ptr, unshield->io_userdata);
}

static inline void * unshield_opendir(Unshield* unshield, const char *name)
{
    return unshield->io_callbacks->opendir(name, unshield->io_userdata);
}

static inline int unshield_closedir(Unshield* unshield, void *dir)
{
    return unshield->io_callbacks->closedir(dir, unshield->io_userdata);
}

static inline struct dirent* unshield_readdir(Unshield* unshield, void *dir)
{
    return unshield->io_callbacks->readdir(dir, unshield->io_userdata);
}

/*
   Constants
 */

#define HEADER_SUFFIX   "hdr"
#define CABINET_SUFFIX  "cab"

/*
   Macros for safer development
 */

#define FREE(ptr)       { if (ptr) { free(ptr); (ptr) = NULL; } }
#define STRDUP(str)     ((str) ? strdup(str) : NULL)
#define NEW(type, count)      ((type*)calloc(count, sizeof(type)))
#define NEW1(type)      ((type*)calloc(1, sizeof(type)))
#define FCLOSE(unshield, file) if (file) { unshield_fclose(unshield, file); (file) = NULL; }
#define FSIZE(unshield, file)     ((file) ? unshield_fsize(unshield, file) : 0)
#define STREQ(s1,s2)    (0 == strcmp(s1,s2))

static inline uint16_t get_unaligned_le16(const uint8_t *p)
{
    return p[0] | p[1] << 8;
}

static inline uint32_t get_unaligned_le32(const uint8_t *p)
{
    return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
}

static inline uint64_t get_unaligned_le64(const uint8_t *p)
{
    return (uint64_t)get_unaligned_le32(p + 4) << 32 | get_unaligned_le32(p);
}

#define READ_UINT16(p)   get_unaligned_le16(p)
#define READ_UINT32(p)   get_unaligned_le32(p)
#define READ_UINT64(p)   get_unaligned_le64(p)

#define READ_INT16(p)   ((int16_t)READ_UINT16(p))
#define READ_INT32(p)   ((int32_t)READ_UINT32(p))


#endif 

