/*
 * This file is part of the Poliqarp suite.
 * 
 * Copyright (C) 2004-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 <sakura/backend-syntax.h>

#include <sakura/dict.h>
#include <foostring/foostring.h>

static int syntax_next_bit(void *data)
{
   struct poliqarp_backend_syntax *syntax = 
      (struct poliqarp_backend_syntax *)data;
   uint8_t *buffer = (uint8_t *)syntax->data.image;
   int res = buffer[syntax->pos / 8] & (1 << (syntax->pos % 8));
   syntax->pos++;
   return res;
}

int poliqarp_backend_syntax_open(struct poliqarp_backend_syntax *this,
   const char *base_name, struct poliqarp_error *error)
{
   bool have_dict, have_image, have_offsets, have_groups;
   char *path;
   int rc;
   have_dict = have_image = have_offsets = have_groups = false;
   rc = poliqarp_newdict_open(&this->dict_types, base_name, true,
      "%s.poliqarp.grtype.image", "%s.poliqarp.grtype.offset",
      _("Unable to open group types dictionary"), error);
   if (rc != 0) {
      if (errno == ENOENT) {
         /* Presumably there's no syntax data. */
         poliqarp_error_message_set(error, NULL);
         this->syntax = false;
         return 0;
      }
      goto error;
   }
   have_dict = true;

   path = string_aformat("%s.poliqarp.syntax.image", base_name);
   if (path == NULL)
      goto error_message;
   rc = create_file_map(&this->data, path);
   if (rc != 0) {
      poliqarp_error_from_system(error, _("Unable to open syntax image"
         " (%s)"), path);
   }
   free(path);
   if (rc != 0)
      goto error;
   have_image = true;

   this->numbits = 8 * this->data.size - 8 + 
      ((uint8_t *)this->data.image)[this->data.size - 1];
  
   path = string_aformat("%s.poliqarp.syntax.offset", base_name);
   if (path == NULL)
      goto error_message;
   rc = tinydb_open(&this->offsets, path, sizeof(uint32_t));
   if (rc != 0) {

   }
   free(path);
   if (rc != 0)
      goto error;
   have_offsets = true;
   
   this->size = 4096;
   this->groups = malloc(this->size * sizeof(struct poliqarp_syntax_group));
   if (this->groups == NULL)
      goto error_message;
   have_groups = true;

   this->bitstream.next_bit = syntax_next_bit;
   this->bitstream.data = this;
   this->pos = this->start = this->end = 0;
   this->syntax = true;
   return 0;

error_message:
   poliqarp_error_from_system(error, _("Unable initialize syntax backend"));
error:
   if (have_dict)
      newdict_close(&this->dict_types);
   if (have_image)
      destroy_file_map(&this->data);
   if (have_offsets)
      tinydb_close(&this->offsets);
   if (have_groups)
      free(this->groups);
   return -1;
}

void poliqarp_backend_syntax_close(struct poliqarp_backend_syntax *this)
{
   if (this->syntax) {
      free(this->groups);
      tinydb_close(&this->offsets);
      destroy_file_map(&this->data);
      newdict_close(&this->dict_types);
      this->syntax = false;
   }
}

static int poliqarp_backend_syntax_next_internal(struct poliqarp_backend_syntax *this,
   int recursive)
{
   int synchro;
   size_t old_end = this->end;
   struct poliqarp_syntax_group *group = this->groups + this->end;
   if (this->pos >= this->numbits)
      return -1;
   synchro = this->bitstream.next_bit(this);
   if (synchro)
      group->from = decode_binary(&this->bitstream, 32);
   else
      group->from = this->lastfrom + decode_gamma(&this->bitstream) - 1;
   this->lastfrom = group->from;
   group->to = group->from + decode_gamma(&this->bitstream) - 1;
   group->type = decode_gamma(&this->bitstream) - 1;
   if (group->type == POLIQARP_SYNTAX_GROUP_COORD)
      group->u.coord.length = decode_gamma(&this->bitstream);
   else {
      int len = clog2(group->to - group->from + 1);
      int havesynh = this->bitstream.next_bit(this);
      int havesemh = this->bitstream.next_bit(this);
      group->u.noncoord.synh = havesynh 
         ? group->from + decode_binary(&this->bitstream, len)
         : POLIQARP_SYNTAX_GROUP_UNKNOWN;
      group->u.noncoord.semh = havesemh
         ? group->from + decode_binary(&this->bitstream, len)
         : POLIQARP_SYNTAX_GROUP_UNKNOWN;
   }
   this->end++;
   if (this->end == this->size)
      this->end = 0;
   if (this->end == this->start) {
      size_t oldsize = this->size;
      this->size *= 2;
      this->groups = realloc(this->groups, this->size * sizeof(struct poliqarp_syntax_group));
      memcpy(this->groups + oldsize, this->groups, this->start * sizeof(struct poliqarp_syntax_group));
      this->end += oldsize;
      group = this->groups + old_end;
   }
   if (group->type == POLIQARP_SYNTAX_GROUP_COORD && recursive) {
      size_t i;
      for (i = 0; i < group->u.coord.length; i++) 
         if (poliqarp_backend_syntax_next_internal(this, 0) == -1)
            return -1;
   }
   return 0;
}

int poliqarp_backend_syntax_next(struct poliqarp_backend_syntax *this)
{
   return poliqarp_backend_syntax_next_internal(this, 1);
}

void poliqarp_backend_syntax_reset(struct poliqarp_backend_syntax *this)
{
   this->start = this->end = this->pos = 0;
}