/*
 * 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 <stdlib.h>
#include <string.h>
#include <errno.h>

#include <sakura/poliqarp.h>

#include "sessopt.h"

#define MAX_CONTEXT_CHARS     500
#define MAX_CONTEXT_SEGMENTS  20
#define MAX_WCONTEXT_CHARS    5000
#define MAX_WCONTEXT_SEGMENTS 200

typedef int (*optproc_t)(struct sessopt *, const char *);

struct option_hook {
   char *name;
   optproc_t proc;
};

static int process_width(const char *value, struct context_width *width)
{
   char *endptr;
   unsigned long res;
   errno = 0;
   res = strtoul(value, &endptr, 10);
   if (value == endptr)
      return -1; /* no digits converted */
   if (errno == ERANGE || res != (size_t)res)
      return -1; /* integer overflow */
   if (*endptr == '\0') {
      width->charspec = false;
      width->width = res;
      return 0;
   }
   if ((*endptr == 'c' || *endptr == 'C') && *(endptr + 1) == '\0') {
      width->charspec = true;
      width->width = res;
      return 0;
   }
   return -1;
}

static int process_flag(char value, int *what, int flag)
{
   if (value == '1') {
      *what |= flag;
      return 0;
   }
   if (value == '0') {
      *what &= (~flag);
      return 0;
   }
   return -1;
}

static int process_lemmata(const char *value, int *what)
{
   if (process_flag(value[0], what, SHOW_LEMMATA_LC) ||
       process_flag(value[1], what, SHOW_LEMMATA_LM) ||
       process_flag(value[2], what, SHOW_LEMMATA_RM) ||
       process_flag(value[3], what, SHOW_LEMMATA_RC) ||
       value[4] != '\0')
      return -1;
   else
      return 0;
}

static int process_tags(const char *value, int *what)
{
   if (process_flag(value[0], what, SHOW_TAGS_LC) ||
       process_flag(value[1], what, SHOW_TAGS_LM) ||
       process_flag(value[2], what, SHOW_TAGS_RM) ||
       process_flag(value[3], what, SHOW_TAGS_RC) ||
       value[4] != '\0')
      return -1;
   else
      return 0;
}

static int process_ids(const char *value, int *what)
{
   if (process_flag(value[0], what, SHOW_IDS_LC) ||
       process_flag(value[1], what, SHOW_IDS_LM) ||
       process_flag(value[2], what, SHOW_IDS_RM) ||
       process_flag(value[3], what, SHOW_IDS_RC) ||
       value[4] != '\0')
      return -1;
   else
      return 0;
}

static inline int check_width(const struct context_width *width)
{
   if (width->charspec && (width->width <= 0 || width->width > 
       MAX_CONTEXT_CHARS))
      return -1;
   if (!width->charspec && (width->width <= 0 || width->width > 
       MAX_CONTEXT_SEGMENTS))
      return -1;
   return 0;
}

static inline int wide_check_width(const struct context_width *width)
{
   if (width->charspec && (width->width <= 0 || width->width > 
       MAX_WCONTEXT_CHARS))
      return -1;
   if (!width->charspec && (width->width <= 0 || width->width > 
       MAX_WCONTEXT_SEGMENTS))
      return -1;
   return 0;
}

static int left_context_hook(struct sessopt *options, const char *value)
{
   struct context_width width;
   if (process_width(value, &width) || check_width(&width)) 
      return PE_INVVAL;
   options->left_context_width = width;
   return 0;
}

static int right_context_hook(struct sessopt *options, const char *value)
{
   struct context_width width;
   if (process_width(value, &width) || check_width(&width)) 
      return PE_INVVAL;
   options->right_context_width = width;
   return 0;
}

static int wide_context_hook(struct sessopt *options, const char *value)
{
   struct context_width width;
   if (process_width(value, &width) || wide_check_width(&width)) 
      return PE_INVVAL;
   options->wide_context_width = width;
   return 0;
}

static int lemmata_hook(struct sessopt *options, const char *value)
{
   int res = options->retrieve;
   if (process_lemmata(value, &res))
      return PE_INVVAL;
   options->retrieve = res;
   return 0;
}

static int tags_hook(struct sessopt *options, const char *value)
{
   int res = options->retrieve;
   if (process_tags(value, &res))
      return PE_INVVAL;
   options->retrieve = res;
   return 0;
}

static int ids_hook(struct sessopt *options, const char *value)
{
   int res = options->retrieve;
   if (process_ids(value, &res))
      return PE_INVVAL;
   options->retrieve = res;
   return 0;
}

static int notification_interval_hook(struct sessopt *options, 
   const char *value)
{
   char *endptr;
   unsigned long res = strtoul(value, &endptr, 10);
   if (*value == '\0' || *endptr != '\0' || res > 1000)
      return PE_INVVAL;
   options->interval = res;
   return 0;
}

static int disamb_hook(struct sessopt *options, const char *value)
{
   bool val = true;
   if (strcmp(value, "0") == 0)
      val = false;
   else if (strcmp(value, "1") != 0)
      return PE_INVVAL;
   options->disamb = val;
   return 0;
}

static int query_flags_hook(struct sessopt *options, const char *value)
{
   int opt = 0;
   if (strlen(value) != 4)
      return PE_INVVAL;
   if (value[0] == '1') opt |= POLIQARP_QFLAG_QUERY_I;
   if (value[1] == '1') opt |= POLIQARP_QFLAG_QUERY_X;
   if (value[2] == '1') opt |= POLIQARP_QFLAG_META_I;
   if (value[3] == '1') opt |= POLIQARP_QFLAG_META_X;
   options->qflags = opt;
   return 0;
}

static int rewrite_hook(struct sessopt *options, const char *value)
{
   if (!*value)
      return PE_INVVAL;
   string_clear(options->rewrite);
   string_append_str(options->rewrite, value);
   return 0;
}

static int random_sample_hook(struct sessopt *options, const char *value)
{
   if (strcmp(value, "0") == 0)
      options->random_sample = false;
   else if (strcmp(value, "1") == 0)
      options->random_sample = true;
   else
      return PE_INVVAL;
   return 0;
}

static const struct option_hook hooks[] = {
   { "left-context-width", left_context_hook },
   { "right-context-width", right_context_hook },
   { "wide-context-width", wide_context_hook },
   { "retrieve-lemmata", lemmata_hook },
   { "retrieve-tags", tags_hook },
   { "retrieve-ids", ids_hook },
   { "notification-interval", notification_interval_hook },
   { "query-flags", query_flags_hook },
   { "disamb", disamb_hook },
   { "rewrite", rewrite_hook },
   { "random-sample", random_sample_hook },
};

static const struct sessopt default_options = {
   { 5, false },   /* left context */
   { 5, false },   /* right context */
   { 50, false },  /* wide context */
   SHOW_LEMMATA_LM | SHOW_LEMMATA_RM | SHOW_TAGS_LM | SHOW_TAGS_RM,
   0,
   true,
   POLIQARP_QFLAG_META_I | POLIQARP_QFLAG_META_X,
   NULL
};

void sessopt_init(struct sessopt *options)
{
   *options = default_options;
   options->rewrite = string_init("default");
}

void sessopt_free(struct sessopt *options)
{
   string_free(options->rewrite);
}

int sessopt_set(struct sessopt *options, const char *name, const char *value)
{
   size_t i;
   for (i = 0; i < sizeof(hooks) / sizeof(hooks[0]); i++) 
      if (!strcmp(hooks[i].name, name))
         return hooks[i].proc(options, value);
   return PE_INVOPT;
}