/*
 * This file is part of the Poliqarp suite.
 * 
 * Copyright (C) 2008-2009 by Instytut Podstaw Informatyki Polskiej
 * Akademii Nauk (IPI PAN; Institute of Computer Science, Polish
 * Academy of Sciences; cf. www.ipipan.waw.pl).  All rights reserved.
 * 
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation and appearing in the file gpl.txt included in the packaging
 * of this file.  (See http://www.gnu.org/licenses/translations.html for
 * unofficial translations.)
 * 
 * A commercial license is available from IPI PAN (contact
 * Michal.Ciesiolka@ipipan.waw.pl or ipi@ipipan.waw.pl for more
 * information).  Licensees holding a valid commercial license from IPI
 * PAN may use this file in accordance with that license.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
 * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE.
 */

#include <poliqarp-config.h>

#include <stdio.h>
#include <getopt.h>
#include <unistd.h>
#ifdef _WIN32
#include <windows.h>
#endif

#include <sakura/abi.h>
#include <sakura/poliqarp.h>
#include <foostring/foostring.h>

#include <bp/common.h>
#include <bp/io.h>

int verbosity = 1;
static char* base_path = NULL;

static void display_usage(const char *argv0)
{
   bp_display_usage(argv0,
      "<corpus>",
      "Convert a legacy (version 1) binary Poliqarp corpus\n"
      "to the new format (version 2).",
      "  -h, --help              display this help and exit\n"
      "  -v, --version           display version information and exit\n"
      "  -q, --quiet             be quiet, suppress progress information\n",
      NULL);
}

static char *pick_a_path()
{
#ifdef _WIN32 
   /* Take care of poor Windows users, show them an open dialog. */
   static char buffer[PATH_MAX + 1];
   OPENFILENAMEA ofn;
   string_t filter = string_init("Poliqarp corpora");
   string_append_char(filter, '\0');
   string_format_append(filter, POLIQARP_CORPUS_IMAGE_FORMAT, "*");
   string_append_char(filter, '\0');
   string_append_char(filter, '\0');
   memset(&ofn, 0, sizeof(ofn)); 
   ofn.lStructSize = sizeof(ofn); 
   ofn.hwndOwner = NULL;
   ofn.lpstrFilter = string_str(filter);
   ofn.lpstrCustomFilter = 0;
   ofn.nMaxCustFilter = 0;
   ofn.nFilterIndex = 1;
   ofn.lpstrFile = buffer;
   ofn.nMaxFile = PATH_MAX;
   ofn.lpstrFileTitle = NULL;
   ofn.lpstrInitialDir = NULL;
   ofn.lpstrTitle = "Open";
   ofn.lpstrDefExt = NULL;
   ofn.lpfnHook = NULL;
   ofn.lCustData = 0;
   ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
   ofn.lStructSize = sizeof(OPENFILENAMEA);
   if (GetOpenFileNameA(&ofn))
   {
      size_t len, ext_len;
      len = strlen(buffer);
      ext_len = strlen(POLIQARP_CORPUS_IMAGE_FORMAT) - 2;
      if (len > ext_len)
         buffer[len - ext_len] = '\0';
      string_free(filter);
      return buffer;
   }
   string_free(filter);
   return NULL;
#else
   /* Under Unix a command-line interface is preferred. */
   return NULL;
#endif
}

static void cmdline_process(int argc, char *argv[])
{
   const struct option options[] = {
      { "help",          0, NULL, 'h' },
      { "version",       0, NULL, 'v' },
      { "quiet",         0, NULL, 'q' },
      { 0,               0, 0,    0 }
   };
   int opt;


   while ((opt = getopt_long(argc, argv, "hvq", options, NULL)) != -1)
   {
      switch (opt) {
         case 'h': 
            display_usage(*argv);
            break;
         case 'v':
            bp_display_version();
            break;
         case 'q':
            bp_set_verbosity(0);
            break;
         default:
            exit(EXIT_FAILURE);
      }
   }
   if (optind == argc) { 
      base_path = pick_a_path();
      if (base_path != NULL)
         return;
      display_usage(*argv);
   }
   base_path = argv[optind++];
   if (optind != argc)
      display_usage(*argv);
}


static void upgrade_corpus_image(bool finalize)
{
   static string_t new_corpus_image_path;
   static string_t corpus_image_path;
   if (!finalize)
   {
      FILE *legacy_corpus_image; 
      FILE *new_corpus_image; 
      size_t corpus_size, progress = 0, progress_helper = 0;
      bp_printf("Upgrading the corpus image...\n");
      corpus_image_path = string_create();
      new_corpus_image_path = string_create();
      string_format(corpus_image_path, POLIQARP_CORPUS_IMAGE_FORMAT, base_path);
      string_format(new_corpus_image_path, "%S.tmp", corpus_image_path);
      legacy_corpus_image = bp_open(corpus_image_path, "rb");
      new_corpus_image = bp_open(new_corpus_image_path, "wb");
      corpus_size = bp_file_size(legacy_corpus_image) /
         sizeof (union poliqarp_legacy_binary_segment);
      bp_show_progress(0);
      while (!feof(legacy_corpus_image))
      {
         size_t i;
         union poliqarp_legacy_binary_segment legacy_segments[BUFSIZ];
         struct poliqarp_binary_segment new_segments[BUFSIZ];
         size_t n = bp_read(legacy_corpus_image, legacy_segments, sizeof legacy_segments[0], BUFSIZ);
         for (i = 0; i < n; i++) {
            new_segments[i] = poliqarp_convert_legacy_binary_segment(legacy_segments[i]);
            POLIQARP_SEGMENT_LE_TO_HE(new_segments[i]);
         }
         progress_helper += 100 * n;
         while (progress_helper >= corpus_size)
         {
            progress_helper -= corpus_size;
            bp_show_progress(++progress);
         }
         bp_write(new_corpus_image, new_segments, sizeof new_segments[0], n);
      }
      bp_close(new_corpus_image);
      bp_close(legacy_corpus_image);
      bp_printf("\n\n");
   }
   else
   {
      bp_printf("Finalizing the corpus image upgrade...\n");
      bp_unlink(corpus_image_path);
      bp_rename(new_corpus_image_path, corpus_image_path);
      string_free(corpus_image_path);
      string_free(new_corpus_image_path);
   }
}

static void upgrade_interp(bool finalize)
{
   static string_t old_interp_image_paths[2];
   static string_t old_interp_offsets_paths[2];
   static string_t interp_image_paths[2];
   static string_t interp_offsets_paths[2];
   static string_t new_interp_image_paths[2];
   static string_t new_interp_offsets_paths[2];
   if (!finalize)
   {
      int i;
      bp_printf("Upgrading interpretation dictionaries...\n");
      for (i = 0; i < 2; i++)
      {
         old_interp_image_paths[i] = string_create();
         old_interp_offsets_paths[i] = string_create();
         interp_image_paths[i] = string_create();
         interp_offsets_paths[i] = string_create();
         new_interp_image_paths[i] = string_create();
         new_interp_offsets_paths[i] = string_create();
      }
      string_format(old_interp_image_paths[0], POLIQARP_OLD_INTERP1_IMAGE_FORMAT, base_path);
      string_format(old_interp_image_paths[1], POLIQARP_OLD_INTERP2_IMAGE_FORMAT, base_path);
      string_format(old_interp_offsets_paths[0], POLIQARP_OLD_INTERP1_OFFSET_FORMAT, base_path);
      string_format(old_interp_offsets_paths[1], POLIQARP_OLD_INTERP2_OFFSET_FORMAT, base_path);
      string_format(interp_image_paths[0], POLIQARP_INTERP1_IMAGE_FORMAT, base_path);
      string_format(interp_image_paths[1], POLIQARP_INTERP2_IMAGE_FORMAT, base_path);
      string_format(interp_offsets_paths[0], POLIQARP_INTERP1_OFFSET_FORMAT, base_path);
      string_format(interp_offsets_paths[1], POLIQARP_INTERP2_OFFSET_FORMAT, base_path);
      for (i = 0; i < 2; i++)
      {
         size_t progress = 0, progress_helper = 0;
         size_t k;
         newdict_offset_t off;
         struct newdict legacy_interp;
         FILE *new_interp_image;
         FILE *new_interp_offsets;
         int status = newdict_open(&legacy_interp,
            string_str(interp_image_paths[i]),
            string_str(interp_offsets_paths[i]),
            false);
         if (status != 0) {
            status = newdict_open(&legacy_interp,
               string_str(old_interp_image_paths[i]),
               string_str(old_interp_offsets_paths[i]),
               false);
            if (status != 0)
               bp_fatal("unable to open interp-%d dictionary", i + 1);
         }
         string_format(new_interp_image_paths[i], "%S.tmp", interp_image_paths[i]);
         string_format(new_interp_offsets_paths[i], "%S.tmp", interp_offsets_paths[i]);
         new_interp_image = bp_open(new_interp_image_paths[i], "wb");
         new_interp_offsets = bp_open(new_interp_offsets_paths[i], "wb");
         off = 0;
         bp_show_progress(0);
         for (k = 0; k < legacy_interp.num_items; k++)
         {
            size_t m;
            size_t count = GET_LENGTH(&legacy_interp, k) /
               sizeof (union poliqarp_legacy_binary_interp);
            newdict_offset_t length = count * sizeof (struct poliqarp_binary_interp);
            newdict_offset_t length_le = en4(length);
            bp_write(new_interp_image, &length_le, sizeof length, 1);
            off += sizeof length;
            newdict_offset_t offset_le = en4(off);
            bp_write(new_interp_offsets, &offset_le, sizeof offset_le, 1);
            for (m = 0; m < count; m++)
            {
               struct poliqarp_binary_interp interp =
                  poliqarp_convert_legacy_binary_interp(
                     ((union poliqarp_legacy_binary_interp*)
                     GET_ITEM(&legacy_interp, k))[m]
                  );
               POLIQARP_INTERP_LE_TO_HE(interp);
               bp_write(new_interp_image, &interp, sizeof interp, 1);
            }
            off += m * sizeof (struct poliqarp_binary_interp);
            progress_helper += 100;
            while (progress_helper >= legacy_interp.num_items)
            {
               progress_helper -= legacy_interp.num_items;
               bp_show_progress(++progress);
            }
         }
         bp_close(new_interp_image);
         bp_close(new_interp_offsets);
         newdict_close(&legacy_interp);
         bp_printf("\n");
      }
      bp_printf("\n");
   }
   else
   {
      int i;
      bp_printf("Finalizing interpretation dictionaries upgrade...\n");
      for (i = 0; i < 2; i++)
      {
         bp_try_unlink(old_interp_image_paths[i]);
         bp_try_unlink(interp_image_paths[i]);
         bp_rename(new_interp_image_paths[i], interp_image_paths[i]);
         bp_try_unlink(old_interp_offsets_paths[i]);
         bp_try_unlink(interp_offsets_paths[i]);
         bp_rename(new_interp_offsets_paths[i], interp_offsets_paths[i]);
         string_free(interp_image_paths[i]);
         string_free(new_interp_image_paths[i]);
         string_free(interp_offsets_paths[i]);
         string_free(new_interp_offsets_paths[i]);
      }
   }
}

static void upgrade_cdf(bool finalize)
{
   static struct poliqarp_cdf_info cdf;
   if (!finalize)
   {
      bp_printf("Reading the CDF file...\n");
      if (poliqarp_cdf_read(base_path, &cdf) != 0) {
         if (errno == ENOENT)
            bp_warning("CDF file is missing; either this is a version 1 corpus or an incomplete corpus");
         else
            bp_system_error("could not read CDF file");
      }
      if (cdf.version != 1)
         bp_fatal("only corpora created with Poliqarp <= 1.2 can be converted");
      if (cdf.endianness != ENDIAN_LE)
         bp_fatal("big-endian corpora are not supported");
      bp_printf("\n");
   }
   else
   {
      bp_printf("Saving the CDF file...\n");
      if (poliqarp_cdf_write(base_path, &cdf) != 0)
         bp_system_error("unable to write CDF file.");
   }
}

int main(int argc, char *argv[])
{
   bp_init("bpupgrade");
   cmdline_process(argc, argv);
   upgrade_cdf(false);
   upgrade_corpus_image(false);
   upgrade_interp(false);
   upgrade_corpus_image(true);
   upgrade_interp(true);
   upgrade_cdf(true);
   bp_printf("Done.\n");
   return EXIT_SUCCESS;
}

/* vim:set ts=3 sw=3 et:*/
