/*
 * This file is part of the Poliqarp suite.
 * 
 * Copyright (C) 2009, 2010 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 <foostring/foostring.h>

#include <sakura/backend-base.h>
#include <sakura/backend-corpus.h>
#include <sakura/backend-document.h>
#include <sakura/backend-interp.h>
#include <sakura/backend-meta.h>
#include <sakura/backend-orth.h>
#include <sakura/backend-subdocument.h>
#include <sakura/backend-tag.h>
#include <sakura/cdf.h>
#include <sakura/meta-value.h>

#include <bp/common.h>
#include <bp/io.h>
#include <bp/ng/corpus.h>
#include <bp/ng/segment.h>
#include <bp/ng/interp.h>
#include <bp/ng/list.h>

struct subdocument_type {
   char *name;
   FILE *fp;
};

static void subdocument_type_create(struct subdocument_type *this, const char *name,
   string_t path)
{
   assert(this != NULL);
   this->name = bp_strdup(name);
   this->fp = bp_open(path, "wb");
}

static struct subdocument_type *subdocument_type_alloc(const char *name,
   string_t path)
{
   struct subdocument_type *result = bp_malloc(sizeof *result);
   subdocument_type_create(result, name, path);
   return result;
}

static void subdocument_type_destroy(struct subdocument_type *this)
{
   if (this == NULL)
      return;
   free(this->name);
   this->name = NULL;
   bp_close(this->fp);
   this->fp = NULL;
}

static void subdocument_type_free(void *this)
{
   subdocument_type_destroy(this);
   free(this);
}

static void subdocument_type_append(struct subdocument_type *this, size_t pos)
{
   uint32_t datum;
   assert(this != NULL);
   datum = en4(pos);
   bp_write(this->fp, &datum, sizeof datum, 1);
}

static void corpus_read_pool(const struct corpus *this,
   struct binary_pool *pool,
   const char *image_format, const char *offset_format)
{
   char *image_path, *offset_path;

   assert(this != NULL);
   assert(pool != NULL);
   assert(image_format != NULL);
   assert(offset_format != NULL);

   image_path = string_aformat(image_format, this->base_name);
   offset_path = string_aformat(offset_format, this->base_name);

   binary_pool_add_dict(pool, image_path, offset_path);

   free(image_path);
   free(offset_path);
}

static const size_t tag_pool_size = 8191UL;
/* should be enough even for 4096 tags (which used to be a limit) */

static const size_t base_pool_size = 524287UL;
/* just a guess; enough for small (15M segments) Polish corpora */

static const size_t orth_pool_size = 524287UL;
/* just a guess; enough for small (15M segments) Polish corpora */

static const size_t interp_pool_size = 524287UL;
/* just a guess; enough for small (15M segments) Polish corpora */

static const size_t meta_value_pool_size = 65521UL;
/* wild guess */

static const size_t subdocument_pool_size = 251UL;
/* should be enough for everybody */

static const size_t meta_key_pool_size = 251UL;
/* should be enough for everybody */

void corpus_create(struct corpus *this, const char *base_name, bool incremental)
{
   const char * const open_mode = incremental ? "ab" : "wb";
   assert(this != NULL);
   this->base_name = base_name;

   binary_pool_create(&this->tag_pool, tag_pool_size);
   binary_pool_create(&this->orth_pool, orth_pool_size);
   binary_pool_create(&this->dis_base_pool, base_pool_size);
   binary_pool_create(&this->amb_base_pool, base_pool_size);
   binary_pool_create(&this->dis_interp_pool, interp_pool_size);
   binary_pool_create(&this->amb_interp_pool, interp_pool_size);
   binary_pool_create(&this->subdocument_pool, subdocument_pool_size);
   binary_pool_create(&this->meta_value_pool, meta_value_pool_size);
   binary_pool_create(&this->meta_key_pool, meta_key_pool_size);
   if (incremental) {
      corpus_read_pool(this, &this->tag_pool, POLIQARP_TAG_IMAGE_FORMAT,
         POLIQARP_TAG_OFFSET_FORMAT);
      corpus_read_pool(this, &this->orth_pool, POLIQARP_ORTH_IMAGE_FORMAT,
         POLIQARP_ORTH_OFFSET_FORMAT);
      corpus_read_pool(this, &this->dis_base_pool, POLIQARP_BASE1_IMAGE_FORMAT,
         POLIQARP_BASE1_OFFSET_FORMAT);
      corpus_read_pool(this, &this->amb_base_pool, POLIQARP_BASE2_IMAGE_FORMAT,
         POLIQARP_BASE2_OFFSET_FORMAT);
      corpus_read_pool(this, &this->dis_interp_pool,
         POLIQARP_INTERP1_IMAGE_FORMAT, POLIQARP_INTERP1_OFFSET_FORMAT);
      corpus_read_pool(this, &this->amb_interp_pool,
         POLIQARP_INTERP2_IMAGE_FORMAT, POLIQARP_INTERP2_OFFSET_FORMAT);
      corpus_read_pool(this, &this->subdocument_pool,
         POLIQARP_SUBDOCUMENT_IMAGE_FORMAT, POLIQARP_SUBDOCUMENT_OFFSET_FORMAT);
      corpus_read_pool(this, &this->meta_key_pool,
         POLIQARP_META_KEY_IMAGE_FORMAT, POLIQARP_META_KEY_OFFSET_FORMAT);
      corpus_read_pool(this, &this->meta_value_pool,
         POLIQARP_META_VALUE_IMAGE_FORMAT, POLIQARP_META_VALUE_OFFSET_FORMAT);
   } else {
      /* Add dummy values to ensure non-emptiness. */
      binary_pool_add_string(&this->meta_value_pool, "");
      binary_pool_add_string(&this->meta_key_pool, "");
   }

   string_t file_name = string_create();
   string_format(file_name, POLIQARP_CORPUS_IMAGE_FORMAT, base_name);
   this->fp_corpus = bp_open(file_name, open_mode);
   string_format(file_name, POLIQARP_META_IMAGE_FORMAT, base_name);
   this->fp_meta = bp_open(file_name, open_mode);
   string_format(file_name, POLIQARP_META_CONFIG_FORMAT, base_name);
   this->fp_meta_types = bp_open(file_name, "wb");
   string_format(file_name, POLIQARP_CORPUS_DOCUMENT_FORMAT, base_name);
   this->fp_documents = bp_open(file_name, open_mode);
   string_free(file_name);
   this->subdocument_types = list_empty;

   this->nsegments =
      bp_file_size(this->fp_corpus) / sizeof (struct poliqarp_binary_segment);
   this->nmeta =
      bp_file_size(this->fp_meta) / sizeof (struct poliqarp_binary_metadata);
}

void corpus_destroy(struct corpus *this)
{
   if (this == NULL)
      return;
   binary_pool_destroy(&this->tag_pool);
   binary_pool_destroy(&this->orth_pool);
   binary_pool_destroy(&this->dis_base_pool);
   binary_pool_destroy(&this->amb_base_pool);
   binary_pool_destroy(&this->dis_interp_pool);
   binary_pool_destroy(&this->amb_interp_pool);
   binary_pool_destroy(&this->subdocument_pool);
   binary_pool_destroy(&this->meta_value_pool);
   binary_pool_destroy(&this->meta_key_pool);
   bp_close(this->fp_corpus);
   this->fp_corpus = NULL;
   bp_close(this->fp_meta);
   this->fp_meta = NULL;
   bp_close(this->fp_meta_types);
   this->fp_meta_types = NULL;
   bp_close(this->fp_documents);
   this->fp_documents = NULL;
   list_destroy(&this->subdocument_types, subdocument_type_free);
}

void corpus_document_create(struct corpus_document *this, struct corpus *corpus)
{
   assert(this != NULL);
   this->corpus = corpus;
   this->nsegments = 0;
   this->nmeta = 0;
   this->subdocument_ranges = list_empty;
   this->subdocument_stack = list_empty;
   this->segment_buffer = string_create();
   this->meta_buffer = string_create();
}

static struct corpus_document *corpus_document_alloc(struct corpus *corpus)
{
   struct corpus_document *result = bp_malloc(sizeof *result);
   corpus_document_create(result, corpus);
   return result;
}

static void corpus_document_destroy(struct corpus_document *this)
{
   size_t i;
   if (this == NULL)
      return;
   for (i = 0; i < this->subdocument_ranges.length; i++)
      list_destroy(this->subdocument_ranges.items[i], NULL);
   list_destroy(&this->subdocument_ranges, free);
   list_destroy(&this->subdocument_stack, NULL);
   string_free(this->segment_buffer);
   string_free(this->meta_buffer);
   this->segment_buffer = NULL;
   this->meta_buffer = NULL;
}

static void corpus_document_free(struct corpus_document *this)
{
   corpus_document_destroy(this);
   free(this);
}

struct corpus_document *corpus_start_document(struct corpus *this)
{
   return corpus_document_alloc(this);
}

int corpus_end_document(struct corpus_document *this, bool commit)
{
   assert(this != NULL);
   assert(this->corpus != NULL);
   if (commit && this->nsegments == 0) {
      bp_warning("a document with no segments; skipped");
      corpus_document_free(this);
      return -1;
   }
   if (commit)
   #pragma omp critical
   {
      assert(this->subdocument_stack.length == 0);
      assert(this->subdocument_ranges.length <= this->corpus->subdocument_types.length);
      size_t i, j;
      for (i = 0; i < this->subdocument_ranges.length; i++) {
         struct subdocument_type *subdocument_type = this->corpus->subdocument_types.items[i];
         const struct list *ranges = this->subdocument_ranges.items[i];
         if (ranges == NULL)
            continue;
         for (j = 0; j < ranges->length; j++)
            subdocument_type_append(subdocument_type, this->corpus->nsegments + (uintptr_t)ranges->items[j]);
      }
      uint32_t offsets[4] = {
         en4(this->corpus->nsegments),
         en4(this->corpus->nsegments + this->nsegments),
         en4(this->corpus->nmeta),
         en4(this->corpus->nmeta + this->nmeta),
      };
      bp_write(this->corpus->fp_documents, offsets, sizeof *offsets, 4);
      this->corpus->nsegments += this->nsegments;
      if (this->corpus->nsegments >= (1U << 31))
         bp_fatal("corpus grew too large; corpora with more than 2.1G segments are not supported");
      bp_write(this->corpus->fp_corpus, string_str(this->segment_buffer), string_len(this->segment_buffer), 1);
      this->corpus->nmeta += this->nmeta;
      if (this->corpus->nmeta >= (1U << 31))
         bp_fatal("corpus grew too large; corpora with more than 2.1G metadata entries are not supported");
      bp_write(this->corpus->fp_meta, string_str(this->meta_buffer), string_len(this->meta_buffer), 1);
   }
   corpus_document_free(this);
   return 0;
}

static void corpus_write_pool(const struct corpus *this,
   struct binary_pool *pool,
   const char *image_format, const char *offset_format)
{
   FILE *fp = NULL;
   string_t path = string_create();

   assert(this != NULL);
   assert(pool != NULL);
   assert(image_format != NULL);
   assert(offset_format != NULL);

   binary_pool_make_dense_array(pool);
   binary_pool_sort_data(pool, binary_pool_compare_freq);
   string_format(path, image_format, this->base_name);
   fp = bp_open(path, "wb");
   binary_pool_write_data(pool, fp);
   bp_close(fp);

   string_format(path, offset_format, this->base_name);
   fp = bp_open(path, "wb");
   binary_pool_write_offsets(pool, fp);
   bp_close(fp);

   string_free(path);
}

static void corpus_write_sorted_pool(const struct corpus *this,
   struct binary_pool *pool,
   const char *image_format, const char *offset_format,
   const char *index_format)
{
   FILE *fp = NULL;
   string_t path = string_create();

   corpus_write_pool(this, pool, image_format, offset_format);
   assert(index_format != NULL);

   binary_pool_sort_index(pool, binary_pool_compare_ialpha);
   string_format(path, index_format, this->base_name, "alpha");
   fp = bp_open(path, "wb");
   binary_pool_write_index(pool, fp);
   bp_close(fp);

   binary_pool_sort_index(pool, binary_pool_compare_iatergo);
   string_format(path, index_format, this->base_name, "atergo");
   fp = bp_open(path, "wb");
   binary_pool_write_index(pool, fp);
   bp_close(fp);

   string_free(path);
}

void corpus_commit(struct corpus *this)
{
   struct poliqarp_cdf_info cdf;
   assert(this != NULL);
   corpus_write_pool(this, &this->tag_pool, POLIQARP_TAG_IMAGE_FORMAT,
      POLIQARP_TAG_OFFSET_FORMAT);
   corpus_write_sorted_pool(this, &this->orth_pool, POLIQARP_ORTH_IMAGE_FORMAT,
      POLIQARP_ORTH_OFFSET_FORMAT, POLIQARP_ORTH_INDEX_FORMAT);
   corpus_write_pool(this, &this->dis_base_pool, POLIQARP_BASE1_IMAGE_FORMAT,
      POLIQARP_BASE1_OFFSET_FORMAT);
   corpus_write_pool(this, &this->amb_base_pool, POLIQARP_BASE2_IMAGE_FORMAT,
      POLIQARP_BASE2_OFFSET_FORMAT);
   corpus_write_pool(this, &this->dis_interp_pool,
      POLIQARP_INTERP1_IMAGE_FORMAT, POLIQARP_INTERP1_OFFSET_FORMAT);
   corpus_write_pool(this, &this->amb_interp_pool,
      POLIQARP_INTERP2_IMAGE_FORMAT, POLIQARP_INTERP2_OFFSET_FORMAT);
   corpus_write_pool(this, &this->subdocument_pool,
      POLIQARP_SUBDOCUMENT_IMAGE_FORMAT, POLIQARP_SUBDOCUMENT_OFFSET_FORMAT);
   corpus_write_pool(this, &this->meta_value_pool,
      POLIQARP_META_VALUE_IMAGE_FORMAT, POLIQARP_META_VALUE_OFFSET_FORMAT);
   if (bp_file_size(this->fp_meta) == 0)
   {
      struct poliqarp_binary_metadata binary_meta;
      memset(&binary_meta, 0, sizeof binary_meta);
      bp_warning("corpus without metadata");
      bp_write(this->fp_meta, &binary_meta, sizeof binary_meta, 1);
   }
   poliqarp_cdf_create(&cdf);
   if (poliqarp_cdf_write(this->base_name, &cdf) == -1)
      bp_system_error("could not write CDF file");
}

int corpus_add_segment(struct corpus_document *this, const struct segment *segment)
{
   int rc = 0;
   struct poliqarp_binary_segment binary_segment;
   size_t i, orth_space_id, tag_id, dis_id, amb_id;
   string_t amb_interps, dis_interps;
   assert(this != NULL);
   assert(this->corpus != NULL);
   assert(segment != NULL);
   #pragma omp critical
   {
      orth_space_id = binary_pool_add_string(&this->corpus->orth_pool, segment->orth)->id * 2 + segment->space;
      amb_interps = string_create();
      dis_interps = string_create();
      for (i = 0; i < segment->interps.length; i++) {
         size_t base_id;
         struct poliqarp_binary_interp binary_interp;
         const struct interp *interp = segment->interps.items[i];
         tag_id = binary_pool_add_string(&this->corpus->tag_pool, interp->tag)->id;
         if (interp->disamb) {
            base_id = binary_pool_add_string(&this->corpus->dis_base_pool, interp->base)->id;
            binary_interp.base_id = en4(base_id);
            binary_interp.tag_id = en4(tag_id);
            string_append_buf(dis_interps, (const char*)&binary_interp, sizeof binary_interp);
         }
         base_id = binary_pool_add_string(&this->corpus->amb_base_pool, interp->base)->id;
         binary_interp.base_id = en4(base_id);
         binary_interp.tag_id = en4(tag_id);
         string_append_buf(amb_interps, (const char*)&binary_interp, sizeof binary_interp);
      }
      if (string_len(amb_interps) == 0) {
         bp_warning("segments without interpretations are not supported");
         rc = -1;
         goto done;
      }
      if (string_len(dis_interps) == 0) {
         bp_warning("segments without disambiguated interpretations are not supported");
         rc = -1;
         goto done;
      }
      dis_id = binary_pool_add(&this->corpus->dis_interp_pool, string_str(dis_interps),
         string_len(dis_interps))->id;
      amb_id = binary_pool_add(&this->corpus->amb_interp_pool, string_str(amb_interps),
         string_len(amb_interps))->id;
      binary_segment.orth_space_id = en4(orth_space_id);
      binary_segment.interp_disamb_id = en4(dis_id);
      binary_segment.interp_amb_id = en4(amb_id);
      string_append_buf(this->segment_buffer, (const char*) &binary_segment, sizeof binary_segment);
      this->nsegments++;
   done:
      string_free(amb_interps);
      string_free(dis_interps);
   }
   return rc;
}

static struct subdocument_type *corpus_create_subdocument_type(struct corpus_document *this, const char *name)
{
   struct subdocument_type *result;
   assert(this != NULL);
   assert(name != NULL);
   string_t path = string_create();
   string_format(path, POLIQARP_SUBDOCUMENT_ITEM_FORMAT, this->corpus->base_name, name);
   result = subdocument_type_alloc(name, path);
   string_free(path);
   return result;
}

void corpus_push_subdocument(struct corpus_document *this, const char *name)
{
   char *dup_name = NULL;
   assert(this != NULL);
   assert(this->corpus != NULL);
   if (name == NULL || !ascii_isalpha(*name)) {
      name = dup_name = NULL;
   } else {
      const char *name_ptr = name;
      for (name_ptr = name; *name_ptr != '\0'; name_ptr++) {
         if (!ascii_isalnum(*name_ptr)) {
            name = NULL;
            break;
         }
      }
   }
   if (name != NULL) 
   #pragma omp critical
   {
      struct subdocument_type *type;
      struct list *ranges;
      size_t id = binary_pool_add_string(&this->corpus->subdocument_pool, name)->id;
      while (id >= this->corpus->subdocument_types.length)
         list_append(&this->corpus->subdocument_types, NULL);
      while (id >= this->subdocument_ranges.length)
         list_append(&this->subdocument_ranges, NULL);
      type = this->corpus->subdocument_types.items[id];
      if (type == NULL) {
         type = corpus_create_subdocument_type(this, name);
         this->corpus->subdocument_types.items[id] = type;
      }
      ranges = this->subdocument_ranges.items[id];
      if (ranges == NULL) {
         ranges = bp_malloc(sizeof (struct list));
         *ranges = list_empty;
         this->subdocument_ranges.items[id] = ranges;
      }
      dup_name = type->name;
      list_append(ranges, (void*)(uintptr_t)this->nsegments);
   }
   list_append(&this->subdocument_stack, dup_name);
}

void corpus_pop_subdocument(struct corpus_document *this)
{
   char *name;
   assert(this != NULL);
   assert(this->corpus != NULL);
   name = list_pop(&this->subdocument_stack);
   if (name != NULL) 
   #pragma omp critical
   {
      struct list *ranges;
      size_t id = binary_pool_add_string(&this->corpus->subdocument_pool, name)->id;
      ranges = this->subdocument_ranges.items[id];
      assert(ranges != NULL);
      assert(ranges->length > 0);
      list_append(ranges, (void*)(uintptr_t)this->nsegments);
   }
}

void corpus_add_meta_value(struct corpus_document *this,
   const struct meta_key *key, const char *value)
{
   size_t i, j;
   size_t key_id;
   int rc;
   struct list binary_meta_list = list_empty;
   struct poliqarp_binary_metadata *binary_meta;
   assert(this != NULL);
   assert(this->corpus != NULL);
   assert(key != NULL);
   if (value == NULL) {
      binary_meta = bp_malloc(sizeof *binary_meta);
      binary_meta->value_as.text = en4(0);
      binary_meta->type = en4(POLIQARP_METADATA_UNDEFINED);
      list_append(&binary_meta_list, binary_meta);
      binary_meta = NULL;
   } else if (key->type == META_TYPE_DATE) {
      binary_meta = bp_malloc(sizeof *binary_meta);
      rc = poliqarp_meta_date_parse(value, &binary_meta->value_as.date);
      if (rc != 0) {
         /* As a special exception, we support whitespace-separated year ranges here. */
         char *endptr;
         unsigned long min, max;
         errno = 0;
         min = strtoul(value, &endptr, 10);
         if (errno != 0 || *endptr != ' ')
            goto date_format_error;
         max = strtoul(endptr + 1, &endptr, 10);
         if (errno != 0 || *endptr != '\0')
            goto date_format_error;

         binary_meta->type = en4(POLIQARP_METADATA_DATE);
         binary_meta->value_as.date.month = 0;
         binary_meta->value_as.date.day = 0;
         for (unsigned long i = min; i <= max; i++) {
            if (binary_meta == NULL) {
               binary_meta = bp_malloc(sizeof *binary_meta);
               binary_meta->type = en4(POLIQARP_METADATA_DATE);
               binary_meta->value_as.date.month = 0;
               binary_meta->value_as.date.day = 0;
            }
            assert(binary_meta != NULL);
            binary_meta->value_as.date.year = i;
            if (binary_meta->value_as.date.year != i)
               /* Integer overflow */
               continue;
            binary_meta->value_as.date.year = en2(i);
            list_append(&binary_meta_list, binary_meta);
            binary_meta = NULL;
         }
date_format_error:
         if (binary_meta_list.length == 0) {
            bp_warning("invalid date: %s", value);
            binary_meta->value_as.text = en4(0);
            binary_meta->type = en4(POLIQARP_METADATA_UNDEFINED);
            list_append(&binary_meta_list, binary_meta);
            binary_meta = NULL;
         } else if (binary_meta != NULL) {
            free(binary_meta);
            binary_meta = NULL;
         }
      } else {
         binary_meta->value_as.date.year = en2(binary_meta->value_as.date.year);
         binary_meta->type = en4(POLIQARP_METADATA_DATE);
         list_append(&binary_meta_list, binary_meta);
         binary_meta = NULL;
      }
   } else 
   #pragma omp critical
   {
      size_t text_id = binary_pool_add_string(&this->corpus->meta_value_pool, value)->id;
      binary_meta = bp_malloc(sizeof *binary_meta);
      binary_meta->type = en4(key->multiple ? POLIQARP_METADATA_MULTI : POLIQARP_METADATA_SINGLE);
      binary_meta->value_as.text = en4(text_id);
      list_append(&binary_meta_list, binary_meta);
      binary_meta = NULL;
   }
   assert(binary_meta == NULL);
   assert(key->names.length > 0);
   for (i = 0; i < key->names.length; i++) {
      key_id = binary_pool_add_string(&this->corpus->meta_key_pool, key->names.items[i])->id;
      key_id = en4(key_id);
      for (j = 0; j < binary_meta_list.length; j++) {
         binary_meta = ((struct poliqarp_binary_metadata*) binary_meta_list.items[j]);
         binary_meta->key = key_id;
         string_append_buf(this->meta_buffer, (const char*) binary_meta, sizeof *binary_meta);
         this->nmeta++;
      }
   }
   list_destroy(&binary_meta_list, free);
}

void corpus_add_meta_key(struct corpus *this, const struct meta_key *key)
{
   size_t i;
   char tp;
   assert(this != NULL);
   assert(key != NULL);
   if (key->type == META_TYPE_DATE) {
      if (key->multiple)
         bp_warning("multiple date meta type is currently not supported");
      tp = 'D';
   }
   else
      tp = 'S';
   assert(key->names.length > 0);
   for (i = 0; i < key->names.length; i++) {
      const char *name = key->names.items[i];
      const char *name_ptr;
      for (name_ptr = name; *name_ptr != '\0'; name_ptr++) {
         if (!ascii_isgraph(*name_ptr) && *(unsigned char*)name_ptr < 0x80) {
            bp_warning("using non-printable characters in key names is discouraged and might be not fully supported");
            break;
         }
      }
      fprintf(this->fp_meta_types, "%c %s\n", tp, name);
      if (ferror(this->fp_meta_types))
         bp_system_error("write error");
      binary_pool_add_string(&this->meta_key_pool, name);
   }
}

void corpus_commit_meta_keys(struct corpus *this)
{
   assert(this != NULL);
   corpus_write_pool(this, &this->meta_key_pool, POLIQARP_META_KEY_IMAGE_FORMAT,
      POLIQARP_META_KEY_OFFSET_FORMAT);
}
