#include "dvd_load_ifo.h"
#include "config.h"
#include "msg.h"
#include "ka_mem.h"
#include "ka_log.h"
#include "ro_file.h"
#include <string.h>
#include "input/ka_indvd.h"
#include "swis.h"

/**
 * Scans a VIDEO_TS folder for IFO, BUP and VOB files.
 */
static void DVDParsePath(dvd_desc_t* dvd)
{
  int entry, i, j;
  ro_file_info ro;

  for (entry = 0; entry != -1; )
  {
    entry = Dir_ReadEntry(dvd->path_video, entry, &ro);
    dvd_file_desc_t* fi = NULL;

    if (ro.name[0])
    {
      if (!stricmp("VIDEO_TS/BUP", ro.name))
        fi = &dvd->ifos[0].bup;
      else if (!stricmp("VIDEO_TS/IFO", ro.name))
        fi = &dvd->ifos[0].ifo;
      else if (!stricmp("VIDEO_TS/VOB", ro.name))
        fi = &dvd->ifos[0].vobs[0];
      else
      {
        char c = ro.name[4];
        ro.name[4] = 0;

        if (!stricmp("VTS_", ro.name))
        {
          ro.name[4] = c;

          if ((ro.name[4] >= '0') && (ro.name[4] <= '9')
          &&  (ro.name[5] >= '0') && (ro.name[5] <= '9')
          &&  (ro.name[6] = '_')
          &&  (ro.name[7] >= '0') && (ro.name[7] <= '9'))
          {
            i = 10 * (ro.name[4] - '0') + (ro.name[5] - '0');
            j = (ro.name[7] - '0');
            if (!stricmp("/VOB", ro.name + 8))
              fi = &dvd->ifos[i].vobs[j];
            else if (j == 0)
            {
              if (!stricmp("/BUP", ro.name + 8))
                fi = &dvd->ifos[i].bup;
              else if (!stricmp("/IFO", ro.name + 8))
                fi = &dvd->ifos[i].ifo;
            }
          }
        }

        ro.name[4] = c;
      }
    }

    if (fi)
    {
      strcpy(fi->name, ro.name);
      fi->size = ro.length;
    }
  }

  dvd->nr_ifos = 0;
  uint64_t start = 0;
  for(i = 0; i <= DVD_IFOS_MAX; i++)
  {
    dvd_ifo_desc_t* idesc = &dvd->ifos[i];
    if (!idesc->ifo.size && !idesc->bup.size)
      break;
    dvd->nr_ifos++;

    idesc->ifo.start = start;
    idesc->ifo.end = start += idesc->ifo.size;
    for(j = 0; j <= DVD_VOBS_MAX; j++)
    {
      if (j && !idesc->vobs[j].size)
        break;
      idesc->vobs[j].start = start;
      idesc->vobs[j].end = start += idesc->vobs[j].size;
    }
    idesc->bup.start = start;
    idesc->bup.end = start += idesc->bup.size;

    idesc->nr_vobs = j;
    idesc->vob_menu_start = idesc->vobs[0].start;
    idesc->vob_menu_end = idesc->vobs[0].end;
    idesc->vob_menu_size = idesc->vobs[0].size;
    idesc->vob_title_start = idesc->vobs[1].start;
    idesc->vob_title_end = idesc->bup.start;
    idesc->vob_title_size = idesc->vob_title_end - idesc->vob_title_start;
  }

  dvd_desc_stats(dvd);
}

/**
 *  Load first LB of IFO, derive total size from it and read the extra required LB.
 *  Note: via OpenCD info->size is 0 on entry
 */
static int loadIfo(ka_error_t* pErrorBlock, const char* filename, const dvd_file_desc_t* ifo, uint8_t** pp, uint8_t** ppend)
{
  uint32_t size = (uint32_t) ifo->size;
  uint8_t* p = ka_mem_alloc(size);
  if (p == NULL)
  {
    ka_error_fill(pErrorBlock, ka_error_nomem);
    return -1;
  }

  FILE* f = fopen(filename, "rb");
  if (f == NULL)
  {
    sprintf(&pErrorBlock->errmess[0], "Could not open file %s", filename);
    goto error;
  }

  if (fread(p, 1, size, f) != size)
  {
    sprintf(&pErrorBlock->errmess[0], "Could not read %d bytes from %s", size, ifo->name);
    goto error;
  }

  *pp = p;
  *ppend = p + size;

  return 0;

error:
  if (f != NULL) fclose(f);
  ka_mem_free(p);
  return -1;
}

static int loadVmgIfo(ka_error_t* pErrorBlock, char* path, char* leaf, dvd_ifo_desc_t* ifo)
{
  uint8_t* buf = NULL;
  uint8_t* end;
  int ret = -1;

  strcpy(leaf, ifo->ifo.name);
  if (!loadIfo(pErrorBlock, path, &ifo->ifo, &buf, &end))
  {
    if(!dvd_read_vmg_ifo(pErrorBlock, &ifo->vmg, buf, end))
    {
      ret = 0;
      goto exit;
    }
    ka_mem_free(buf);
    buf = NULL;
  }

  // Try with BUP
  strcpy(leaf, ifo->bup.name);
  if (!loadIfo(pErrorBlock, path, &ifo->bup, &buf, &end)
  &&  !dvd_read_vmg_ifo(pErrorBlock, &ifo->vmg, buf, end))
    ret = 0;

exit:
  if (buf) ka_mem_free(buf);

  return ret;
}

static int loadVtsIfo(ka_error_t* pErrorBlock, char* path, char* leaf, dvd_ifo_desc_t* ifo, int i)
{
  uint8_t* buf = NULL;
  uint8_t* end;
  int ret = -1;

  strcpy(leaf, ifo->ifo.name);
  if (!loadIfo(pErrorBlock, path, &ifo->ifo, &buf, &end))
  {
    if(!dvd_read_vts_ifo(pErrorBlock, &ifo->vts, i, buf, end))
    {
      ret = 0;
      goto exit;
    }
    ka_mem_free(buf);
    buf = NULL;
  }

  // Try with BUP
  strcpy(leaf, ifo->bup.name);
  if (!loadIfo(pErrorBlock, path, &ifo->bup, &buf, &end)
  &&  !dvd_read_vts_ifo(pErrorBlock, &ifo->vts, i, buf, end))
    ret = 0;

exit:
  if (buf) ka_mem_free(buf);

  return ret;
}

/**
 * Attempts to locate the VIDEO_TS folder from given path,
 * scan the folder for IFO, BUP and VOB files,
 * and parse the IFO files.
 */
static int DVDOpen(ka_input_t* pInput, dvd_desc_t* dvd, const char* path)
{
  char* leaf;

  if (path == NULL)
    return -1;

  const char* folder = strrchr(path, FILE_FILESYSTEM_CHAR);
  if (!folder)
    folder = path;
  else
    folder++;

  while (folder)
  {
    if (!stricmp("VIDEO_TS.VIDEO_TS/IFO", folder)
    ||  !stricmp("VIDEO_TS", folder))
    {
      break;
    }
    folder = strchr(folder, FILE_FOLDER_CHAR);
    if (folder) folder++;
  }

  if (!folder)
    return 1; // Not a DVD

  int path_length = (folder - path) + 8;

  dvd->path_video = ka_mem_calloc(path_length + 15); // allow for leafname
  if (!dvd->path_video)
  {
    ka_error_fill(pInput->pErrorBlock, ka_error_nomem);
    return -1;
  }

  strncpy(dvd->path_video, path, path_length);
  dvd->path_video[path_length] = 0;
  leaf = dvd->path_video + path_length + 1;

  if (config.debug & cfg_printnavstats)
    ka_log(ka_log_simple, "DVD folder %s", dvd->path_video);

  DVDParsePath(dvd);

  if ((!dvd->ifos[0].ifo.size && !dvd->ifos[0].bup.size)
  ||  (!dvd->ifos[1].ifo.size && !dvd->ifos[1].bup.size)
  ||  (!dvd->ifos[1].vobs[1].size))
  {
    return 1; // Not a DVD
  }

  leaf[-1] = '.';
  if (loadVmgIfo(pInput->pErrorBlock, dvd->path_video, leaf, &dvd->ifos[0]))
  {
    leaf[-1] = 0;
    return -1;
  }

  // Avoid trying to crack key
  dvd->ifos[0].title_key_known = 1;

  for(int i = 1; i < dvd->nr_ifos; i++)
  {
    if (loadVtsIfo(pInput->pErrorBlock, dvd->path_video, leaf, &dvd->ifos[i], i))
    {
      leaf[-1] = 0;
      return -1;
    }

    // Avoid trying to crack key
    dvd->ifos[i].title_key_known = 1;
  }

  leaf[-1] = 0;
  return 0;
}

static int DVDOpenCD(ka_input_t* pInput, const char* path, dvd_desc_t* dvd)
{
  dvd->path_video = ka_mem_calloc(strlen(path) + 1);
  if (!dvd->path_video)
  {
    ka_error_fill(pInput->pErrorBlock, ka_error_nomem);
    return -1;
  }

  strcpy(dvd->path_video, path);

  if (config.debug & cfg_printnavstats)
    ka_log(ka_log_simple, "DVD device CDFS::%s", path + 6);

  if (ka_indvd_initDevice(pInput, path))
    return -1;

  return dvd_io_load_desc(ka_indvd_getDvdIo(pInput), dvd);
}

int dvd_desc_load(ka_input_t* pInput, const char* path, dvd_desc_t** pdvd, int check)
{
  int rc = 0;

  dvd_desc_t* dvd = *pdvd = ka_mem_calloc(sizeof(*dvd));
  if (!dvd)
  {
    ka_error_fill(pInput->pErrorBlock, ka_error_nomem);
    return -1;
  }

  if (!strncmp(path, "::CDFS", 6))
  {
    rc = DVDOpenCD(pInput, path, dvd);
    if (rc) goto error;

    if (dvd->nr_ifos < 2)
    {
      ka_error_fill(pInput->pErrorBlock, "err:Not a DVD");
      goto error;
    }
  }
  else
  {
    rc = DVDOpen(pInput, dvd, path);
    if (rc)
      goto error;
  }

  if (check)
  {
    rc = dvd_desc_validate(pInput->pErrorBlock, dvd);
    if (rc)
      goto error;
  }

  return 0;

error:
  dvd_desc_free(pdvd);
  *pdvd = NULL;

  return rc;
}

int dvd_desc_getTitleKey(ka_input_t* pInput, dvd_ifo_desc_t* ifo)
{
  if (!ifo->title_key_known)
  {
    uint32_t start = (uint32_t) (ifo->vob_menu_start / DVD_LB_SIZE);
    uint32_t end   = (uint32_t) (ifo->vob_title_end / DVD_LB_SIZE);
    // No menu ?
    if (start == 0)
      start = (uint32_t) (ifo->vob_title_start / DVD_LB_SIZE);
    dvd_io_t* io = ka_indvd_getDvdIo(pInput);
    if (dvd_io_getTitleKey(io, start, end, ifo->title_key) != NULL)
      return -1;
    ifo->title_key_known = 1;
  }

  return 0;
}
