/*----------------------------------------------------------------------+
|                                                                       |
|   Copyright (C) 2010 Nokia Corporation.                               |
|                                                                       |
|   Author: Ilya Dogolazky <ilya.dogolazky@nokia.com>                   |
|                                                                       |
|   This file is part of Iodata                                         |
|                                                                       |
|   Iodata is free software; you can redistribute it and/or modify      |
|   it under the terms of the GNU Lesser General Public License         |
|   version 2.1 as published by the Free Software Foundation.           |
|                                                                       |
|   Iodata is distributed in the hope that it will be useful, but       |
|   WITHOUT ANY WARRANTY;  without even the implied warranty  of        |
|   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                |
|   See the GNU Lesser General Public License  for more details.        |
|                                                                       |
|   You should have received a copy of the GNU  Lesser General Public   |
|  License along with Iodata. If not, see http://www.gnu.org/licenses/  |
|                                                                       |
+----------------------------------------------------------------------*/

#ifndef MAEMO_IODATA_VALIDATOR_H
#define MAEMO_IODATA_VALIDATOR_H

#include <exception>
#include <string>
#include <typeinfo>
using namespace std;

#include "iodata.h"

namespace iodata {
enum validator_flag {
    RECORD = 1 << 0,
    ARRAY = 1 << 1,
    BITMASK = 1 << 2,
    INTEGER = 1 << 3,
    BYTES = 1 << 4,
    MANDATORY = 1 << 5,
};

struct record_type;
#if 0
  struct node ;

  struct node
  {
    string name ;
    /*u*/int32_t integer_value ;
    string bytes_value ;
    bitmask bitmask_value ;
    validator_flag flag ;
    record_type *type ;
    string type_name ;
  } ;
#else
struct node;
struct node_integer;
struct node_bytes;
struct node_bitmask;
struct node_record;

struct node
{
    string name;
    bool is_array, is_mandatory;
    node(const string &n, bool a, bool m)
        : name(n)
        , is_array(a)
        , is_mandatory(m)
    {}
    virtual ~node() {}
    virtual bool is_integer() const { return false; }
    virtual bool is_bytes() const { return false; }
    virtual bool is_bitmask() const { return false; }
    virtual bool is_record() const { return false; }
    virtual const char *node_name() const = 0;
};

struct node_integer : public node
{
    int32_t value;
    node_integer(const string &n, bool a, bool m, int32_t v)
        : node(n, a, m)
        , value(v)
    {}
    ~node_integer() {}
    bool is_integer() const { return true; }
    const char *node_name() const { return "node_integer"; }
};

struct node_bytes : public node
{
    string value;
    node_bytes(const string &n, bool a, bool m, const string &v)
        : node(n, a, m)
        , value(v)
    {}
    ~node_bytes() {}
    bool is_bytes() const { return true; }
    const char *node_name() const { return "node_bytes"; }
};

struct node_bitmask : public node
{
    bitmask value;
    node_bitmask(const string &n, bool a, bool m, const bitmask &v)
        : node(n, a, m)
        , value(v)
    {}
    ~node_bitmask() {}
    bool is_bitmask() const { return true; }
    const char *node_name() const { return "node_bitmask"; }
};

struct node_record : public node
{
    string type_name;
    record_type *type;
    node_record(const string &n, bool a, bool m, const string &tn, record_type *t = NULL)
        : node(n, a, m)
        , type_name(tn)
    {
        type = t;
    }
    ~node_record() {}
    bool is_record() const { return true; }
    const char *node_name() const { return "node_record"; }
};
#endif

struct record_type
{
    string name;
    vector<node *> nodes;
};

template<typename T>
T *cast_and_check(item *p)
{
    if (T *p_cast = dynamic_cast<T *>(p))
        return p_cast;
    throw exception((string) T::static_class_name() + " expected, but " + p->class_name()
                    + " found");
}

struct validator
{
    vector<string> v_namespace;
    string v_function;

    struct exception : public iodata::exception
    {
        string node_path;
        exception(const string &m)
            : iodata::exception(m){};
        exception &prepend_path(const string &name);
        exception &prepend_index(int i);
        string info() const
        {
            return (string) "iodata::validator::exception, " + message() + " at " + node_path;
        }
        ~exception() throw() {}
    };

#if 0
    static bit_codec type_codec ;
    static void init_type_codec() ;
#endif
    static validator *from_file(const char *path);
    iodata::record *record_from_file(const char *path, const char *record_type, string &message);
    bool record_to_file(const char *path,
                        const char *record_type,
                        iodata::record *data,
                        string &serialized);
    validator();
    ~validator();
#if 0
    {
      static bool first = true ;
      if(first)
        init_type_codec(), first=false ;
#if 0
      indent_step = 2 ;
      screen_width = 80 ;
#endif
    }
#endif
    map<string, record_type *> types;
    bool is_static; // flag used by structures generated by iodata-type-to-c++ compiler
    void load(const record *lang);
    void link();
    void set_static();

#if 0
    bool read_run(record *data) ;
    bool write_run(record *data) ;
    void check_node(item *p, const node &n, bool maybe_array) ;
    void check_record(record *p, const record_type *r) ;
    void check_record(record *p, const string &type_name) ;
#endif

#if 0
    void prepare_output(item *p, const node &n, bool maybe_array, bool is_mandatory) ;
    void prepare_output(record *p, const record_type *r, bool braces) ;
    void prepare_output(record *p, const string &type_name) ;

    int indent_step, screen_width ;
    void output_indent(ostream &os, int indent) ;
    int indent_to_width(int indent) ;
    void output_record(ostream &os, record *p, const record_type *r, bool braces, int width, int indent) ;
#endif

    void check_record_after_read(record *p, const string &type_name)
    {
        return check_record(p, type_by_name(type_name), false);
    }
    void check_record_before_write(record *p, const string &type_name)
    {
        return check_record(p, type_by_name(type_name), true);
    }

private:
    const record_type *type_by_name(const string &name);
    void check_record(record *p, const record_type *r, bool write);
    void check_unknown_fields(record *p, const record_type *r);
    void check_fields(record *p, const record_type *r, bool add_defaults, item *items[], unsigned N);
    void check_children(record *p, const record_type *r, bool write, item *items[], unsigned N);
    void check_defaults(record *p, const record_type *r, item *items[], unsigned N);
};
} // namespace iodata

namespace iodata {
class output
{
    vector<unsigned> width, length;
    char *buffer;
    unsigned allocated, position, alloc_step;
    void realloc_to(unsigned size);
    bool first;
    unsigned len_position, width_position;
    unsigned width_of_added_string(const string &s);
    unsigned length_of_added_string(const string &s);
    unsigned prepare(item *it);
    unsigned get_length() { return length[len_position++]; }
    void print_chunk()
    {
        unsigned len = get_length();
        os.write(buffer + position, len);
        position += len;
    }
    void hard_reset();
    void soft_reset();
    void do_printing(item *it, bool oneliner);

    ostream &os;
    unsigned indent, indent_step, maximal_width;
    string spaces;
    void indent_increment()
    {
        for (indent += indent_step; spaces.length() < indent; spaces += "           ")
            ;
    }
    void indent_decrement()
    {
        if (indent >= indent_step)
            indent -= indent_step;
    }
    void indent_output() { os.write(spaces.c_str(), indent); }
    void newline_output()
    {
        os << '\n';
        indent_output();
    }
    void comma_output(bool &flag, bool one)
    {
        if (flag) {
            if (one)
                os << ", ";
            else
                os << ",", newline_output();
        }
        flag = true;
    }
    void open(char ch, bool one)
    {
        if (one)
            os << " " << ch << " ";
        else
            newline_output(), indent_increment(), os << ch, newline_output();
    }
    void close(char ch, bool one)
    {
        if (one)
            os << " " << ch;
        else
            indent_decrement(), newline_output(), os << ch;
    }

    unsigned reserve_width(unsigned step)
    {
        unsigned pos = width.size();
        width.resize(pos + step);
        return pos;
    }
    void push_length(unsigned value) { length.push_back(value); }

public:
    output(ostream &stream, int indent = 2, int width = 80);
    ~output();
    void output_record(record *r);
};
} // namespace iodata

#endif
