/*
 * (C) Copyright 1996- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include "eckit/eckit.h"

#include "eckit/io/DataHandle.h"
#include "eckit/persist/Exporter.h"

//-----------------------------------------------------------------------------

void *operator new(size_t, void* addr, eckit::Evolve&)
{
    return addr;
}

//-----------------------------------------------------------------------------

namespace eckit {

//-----------------------------------------------------------------------------

void _export(eckit::Exporter& out, unsigned char what) {
    out.writeUnsigned(what);
}

void _export(eckit::Exporter& out, char what) {
    out.writeSigned(what);
}

void _export(eckit::Exporter& out, unsigned int what) {
    out.writeUnsigned(what);
}
void _export(eckit::Exporter& out, int what) {
    out.writeSigned(what);
}

void _export(eckit::Exporter& out, unsigned long what) {
    out.writeUnsigned(what);
}

void _export(eckit::Exporter& out, long what) {
    out.writeSigned(what);
}

void _export(eckit::Exporter& out, unsigned short what) {
    out.writeUnsigned(what);
}

void _export(eckit::Exporter& out, short what) {
    out.writeSigned(what);
}

void _export(eckit::Exporter& out, unsigned long long what) {
    out.writeUnsigned(what);
}

void _export(eckit::Exporter& out, long long what) {
    out.writeSigned(what);
}

void _export(eckit::Exporter& out, double what) {
    out.writeDouble(what);
}

//-----------------------------------------------------------------------------

const int MAX_STRING_LEN = 10240;

enum {

    TAG_EOF          = 'X',

    TAG_START_CLASS  = 'C',
    TAG_END_CLASS    = 'c',
    TAG_START_OBJECT = 'O',
    TAG_END_OBJECT   = 'o',
    TAG_START_MEMBER = 'M',
    TAG_END_MEMBER   = 'm',

    TAG_START_SUBOBJECT = 'L',
    TAG_END_SUBOBJECT   = 'l',

    TAG_UNSIGNED     = 'u',
    TAG_SIGNED       = 's',
    TAG_STRING       = 'S',
    TAG_DOUBLE       = 'D',

    TAG_START_SCHEMAS   = '{',
    TAG_END_SCHEMAS   = '}',

    TAG_START_DATABASE  = '[',
    TAG_END_DATABASE    = ']'

};

template<class T> struct Swap {

    static const int half = sizeof(T) >> 1;
    static const int last = sizeof(T) - 1;
    T operator()(T v)
    {
        unsigned char *p = (unsigned char*)&v;
        for(int i = 0; i < half ; i++) std::swap(p[i],p[last-i]);
        return v;
    }
};

// xlc needs this,
// others will complain about double initialization
#if defined( __xlC__ )
template<class T> const int Swap<T>::half = sizeof(T) >> 1;
template<class T> const int Swap<T>::last = sizeof(T) - 1;
#endif

class Endian {
    public:

#ifdef ECKIT_LITTLE_ENDIAN
        template<class T> static T transform(T x)  { return Swap<T>()(x); }
#else
        template<class T> static T transform(T x)  { return x; }
#endif

};

Exporter::Exporter(DataHandle& handle):
    handle_(handle),
    type_(0),
    location_(0),
    objectId_(0),
    objectCount_(0),
    subCount_(0),
    inObject_(false)
{
}

Exporter::~Exporter()
{
}

void Exporter::writeTag(char tag)
{
    ASSERT(sizeof(tag) == 1);
    ASSERT(handle_.write(&tag,1) == 1);
}

char Exporter::readTag()
{
    char tag;
    ASSERT(sizeof(tag) == 1);
    ASSERT(handle_.read(&tag,1) == 1);
    return tag;
}


void Exporter::writeString(const std::string &s)
{
    size_t len = s.length();
    char buffer[MAX_STRING_LEN];

    if(len) {
        ASSERT(sizeof(s[0]) == 1);
        ASSERT(len <= sizeof(buffer));
    }

    writeTag(TAG_STRING);
    writeUnsigned(len);
    for(size_t i = 0;  i < len; i++)
        buffer[i] = s[i];

    ASSERT( (size_t) handle_.write(buffer,len) == len);
}

void Exporter::writeString(const char* s)
{
    size_t len = strlen(s);

    writeTag(TAG_STRING);
    writeUnsigned(len);
	ASSERT(sizeof(s[0]) == 1);

    ASSERT( (size_t) handle_.write(s,len) == len);
}

std::string Exporter::_readString()
{
    try {
        std::string s;

        size_t len = readUnsigned();
        for( size_t i = 0;  i < len; i++) {
            char c;
            ASSERT(handle_.read(&c,1) == 1);
            s += c;
        }
        return s;
    }
    catch(...)
    {
        std::cout << "Error reading std::string " << *this << std::endl;
        throw;
    }
}

std::string Exporter::readString()
{
    ASSERT(readTag() == TAG_STRING);
    return _readString();
}

void Exporter::writeDouble(double d) {
    writeTag(TAG_DOUBLE);
    ASSERT(sizeof(double) == 8);
    d = Endian::transform(d);
    ASSERT(handle_.write(&d,sizeof(d)) == sizeof(d));
}

double Exporter::_readDouble() {
    double d;
    ASSERT(sizeof(double) == 8);
    ASSERT(handle_.read(&d,sizeof(d)) == sizeof(d));
    return Endian::transform(d);
}

double Exporter::readDouble() {
    ASSERT(readTag() == TAG_DOUBLE);
    return _readDouble();
}

void Exporter::writeSigned(long long d) {
    writeTag(TAG_SIGNED);
    ASSERT(sizeof(long long) == 8);
    d = Endian::transform(d);
    ASSERT(handle_.write(&d,sizeof(d)) == sizeof(d));
}

long long Exporter::_readSigned() {
    long long d;
    ASSERT(sizeof(long long) == 8);
    ASSERT(handle_.read(&d,sizeof(d)) == sizeof(d));
    return Endian::transform(d);
}

long long Exporter::readSigned() {
    ASSERT(readTag() == TAG_SIGNED);
    return _readSigned();
}

void Exporter::writeUnsigned(unsigned long long d) {
    writeTag(TAG_UNSIGNED);
    ASSERT(sizeof(unsigned long long) == 8);
    d = Endian::transform(d);
    ASSERT(handle_.write(&d,sizeof(d)) == sizeof(d));
}

unsigned long long Exporter::_readUnsigned() {
    unsigned long long d;
    ASSERT(sizeof(unsigned long long) == 8);
    ASSERT(handle_.read(&d,sizeof(d)) == sizeof(d));
    return Endian::transform(d);
}

unsigned long long Exporter::readUnsigned() {
    ASSERT(readTag() == TAG_UNSIGNED);
    return _readUnsigned();
}

void _startClass(eckit::Exporter& out, const std::string& name) {
    out.writeTag(TAG_START_CLASS);
    out.writeString(name);
}

void _endClass(eckit::Exporter& out, const std::string& name) {
    out.writeTag(TAG_END_CLASS);
    //out.writeString(name);
}

void _startClass(eckit::Exporter& out, const char* name) {
    out.writeTag(TAG_START_CLASS);
    out.writeString(name);
}

void _endClass(eckit::Exporter& out, const char* name) {
    out.writeTag(TAG_END_CLASS);
    //out.writeString(name);
}

void _startMember(eckit::Exporter& out, const char* name) {
    out.writeTag(TAG_START_MEMBER);
    out.writeString(name);
}

void _endMember(eckit::Exporter& out, const char* name) {
    out.writeTag(TAG_END_MEMBER);
    //out.writeString(name);
}

void Exporter::startObject(unsigned long long type, unsigned long long location, unsigned long long id, size_t count) {
    writeTag(TAG_START_OBJECT);
    writeUnsigned(type);
    writeUnsigned(location);
    writeUnsigned(id);
    writeUnsigned(count);
    objectCount_++;
    ASSERT(!inObject_);
    inObject_ = true;
    subCount_ = 0;
}

void _startObject(eckit::Exporter& e, unsigned long long type, unsigned long long location, unsigned long long id, size_t count)
{
    e.startObject(type, location, id, count);
}

void Exporter::endObject(unsigned long long type, unsigned long long location, unsigned long long id, size_t count) {
    writeTag(TAG_END_OBJECT);
    ASSERT(inObject_);
    inObject_ = false;
    ASSERT(subCount_);


    /*
       writeUnsigned(type);
       writeUnsigned(location);
       writeUnsigned(id);
       writeUnsigned(count);
       */
}


void Exporter::startSubObject()
{
    writeTag(TAG_START_SUBOBJECT);
    subCount_++;
}

void _startSubObject(eckit::Exporter& e)
{
    e.startSubObject();
}

void Exporter::endSubObject()
{
    writeTag(TAG_END_SUBOBJECT);
}

void _endSubObject(eckit::Exporter& e)
{
    e.endSubObject();
}

void _endObject(eckit::Exporter& e, unsigned long long type, unsigned long long location, unsigned long long id, size_t count)
{
    e.endObject(type, location, id, count);
}

void Exporter::endObject() {
    ASSERT(readTag() == TAG_END_OBJECT);
    ASSERT(subCount_);
    for(std::map<std::string,Datatype>::iterator j = members_.begin(); j != members_.end(); ++j)
        if(!(*j).second.used())
            std::cout << "WARNING NOT USED [" << (*j).first << "]" << std::endl;

    members_.clear();
    stack_.clear();
}

bool Exporter::nextDatabase(std::string& name,unsigned long long& id, unsigned long long& count)
{
    char tag = readTag();
    if(tag == TAG_EOF) {
        return false;
    }

    if(tag != TAG_START_DATABASE) {
        std::cout << "tag " << int(tag) << std::endl;
        std::cout << "tag " << tag << std::endl;
    }

    ASSERT(tag == TAG_START_DATABASE);

    if(tag == TAG_START_DATABASE) {
        name  = readString();
        id    = readUnsigned();
        count = readUnsigned();
    }
    return true; // should only exit with (tag == TAG_START_DATABASE)
}

size_t Exporter::nextObject() {
    char tag = readTag();

    if(tag == TAG_END_DATABASE) {
        unsigned long long objectCount = readUnsigned();
        std::cout << "objectCount " << objectCount << " " << objectCount_ << std::endl;
        ASSERT(objectCount == objectCount_);
        return 0;
    }

	if(tag != TAG_START_OBJECT) {
        std::cout << tag << std::endl;

	}

    ASSERT(tag == TAG_START_OBJECT);
    objectCount_++;
    subCount_ = 0;
    stack_.clear();

    try {

        type_     = readUnsigned();
        location_ = readUnsigned();
        objectId_ = readUnsigned();
        return     readUnsigned(); // count

    }
    catch(...) {
        std::cout << "ERROR reading start object " <<  *this << std::endl;
        throw;
    }

}

void _nextSubObject(eckit::Exporter& e)
{
    e.nextSubObject();
}

std::string Exporter::path() const {
    std::string s;
    for(std::vector<std::string>::const_iterator j = stack_.begin(); j != stack_.end(); ++j) {
        if(s.length()) s += ".";
        s += (*j);
    }
    return s;
}

void Exporter::nextSubObject() {
    ASSERT(readTag() == TAG_START_SUBOBJECT);
    subCount_ ++;

    for(std::map<std::string,Datatype>::iterator j = members_.begin(); j != members_.end(); ++j)
        if(!(*j).second.used())
            std::cout << "WARNING NOT USED [" << (*j).first << "]" << std::endl;

    members_.clear();

    std::string s;
    for(;;) {
        char tag = readTag();
        switch(tag) {

            case TAG_START_CLASS:
                s = readString();
                //cout << s << std::endl;
                //stack_.push_back(s.substr(0,s.find('<')));
				stack_.push_back(s);
                //stack_.push_back(s);
                break;

            case TAG_START_MEMBER:
                stack_.push_back(readString());
                break;

            case TAG_END_MEMBER:
                stack_.pop_back();
                break;

            case TAG_END_CLASS:
                stack_.pop_back();
                break;

            case TAG_UNSIGNED:
                {
                    std::string p = path();
                    Datatype& x = members_[p];
                    ASSERT(x.empty());
                    x = Datatype(_readUnsigned());
                    //cout << "read [" << p << "] = " << x << std::endl;
                }
                break;

            case TAG_SIGNED:
                {
                    std::string p = path();
                    Datatype& x = members_[p];
                    ASSERT(x.empty());
                    x = Datatype(_readSigned());
                    //cout << "read [" << p << "] = " << x << std::endl;
                }
                break;

            case TAG_DOUBLE:
                {
                    std::string p = path();
                    Datatype& x = members_[p];
                    ASSERT(x.empty());
                    x = Datatype(_readDouble());
                    //cout << "read [" << p << "] = " << x << std::endl;
                }
                break;

            case TAG_END_SUBOBJECT:
                return;

            default:
                std::cout << tag << std::endl;
                ASSERT(1 == 0);
                break;
        }
    }
}

double Exporter::getDoubleMember(const std::string& name)
{
    std::map<std::string,Datatype>::iterator j = members_.find(name);
    if(j != members_.end())
    {
        //cout << "consume [" << name << "] = " << (*j).second << std::endl;
        return (*j).second;
    }
    std::cout << name << " not found" << std::endl;
    return 0;
}

long long Exporter::getSignedMember(const std::string& name)
{
    std::map<std::string,Datatype>::iterator j = members_.find(name);
    if(j != members_.end())
    {
        //cout << "consume [" << name << "] = " << (*j).second << std::endl;
        return (*j).second;
    }
    std::cout << name << " not found" << std::endl;
    return 0;
}

unsigned long long Exporter::getUnsignedMember(const std::string& name)
{
    std::map<std::string,Datatype>::iterator j = members_.find(name);
    if(j != members_.end())
    {
        //cout << "consume [" << name << "] = " << (*j).second << std::endl;
        return (*j).second;
    }
    std::cout << name << " not found" << std::endl;
    return 0;
}

Evolve::Evolve(eckit::Exporter& e):
    e_(e),
    parent_(0)
{
}

Evolve::Evolve(Evolve* e, char const* klass, char const* name):
    e_(e->e_),
    path_(e->path()),
    parent_(e)
{
    if(path_.length()) path_ += ".";
    path_ += klass;

    if(name) {
        path_ += ".";
        path_ += name;
    }
}


Evolve Evolve::operator()(char const* klass, char const* name) {
    return Evolve(this,klass,name);
}

Evolve::operator unsigned char()
{
    return e_.getUnsignedMember(path_);
}

Evolve::operator unsigned int()
{
    return e_.getUnsignedMember(path_);
}

Evolve::operator int()
{
    return e_.getSignedMember(path_);
}

Evolve::operator char()
{
    return e_.getSignedMember(path_);
}

Evolve::operator unsigned short()
{
    return e_.getUnsignedMember(path_);
}

Evolve::operator unsigned long()
{
    return e_.getUnsignedMember(path_);
}

Evolve::operator long long()
{
    return e_.getSignedMember(path_);
}

Evolve::operator unsigned long long()
{
    return e_.getUnsignedMember(path_);
}

Evolve::operator double()
{
    return e_.getDoubleMember(path_);
}

Evolve::operator long()
{
    return e_.getSignedMember(path_);
}

Exporter::Datatype::Datatype():
    type_(0),
    used_(false),
    double_(0),
    signed_(0),
    unsigned_(0)
{
}

Exporter::Datatype::Datatype(double d):
    type_(TAG_DOUBLE),
    used_(false),
    double_(d),
    signed_(0),
    unsigned_(0)
{
}

Exporter::Datatype::Datatype(long long d):
    type_(TAG_SIGNED),
    used_(false),
    double_(0),
    signed_(d),
    unsigned_(0)
{
}

Exporter::Datatype::Datatype(unsigned long long d):
    type_(TAG_UNSIGNED),
    used_(false),
    double_(0),
    signed_(0),
    unsigned_(d)
{
}

Exporter::Datatype::operator long long() {
    ASSERT(type_ == TAG_SIGNED);
    ASSERT(!used_);
    used_ = true;
    return signed_;
}

Exporter::Datatype::operator unsigned long long() {
    ASSERT(type_ == TAG_UNSIGNED);
    ASSERT(!used_);
    used_ = true;
    return unsigned_;
}

Exporter::Datatype::operator double() {
    ASSERT(type_ == TAG_DOUBLE);
    ASSERT(!used_);
    used_ = true;
    return double_;
}


void Exporter::Datatype::print(std::ostream& out) const
{
    switch(type_) {
        case TAG_SIGNED:   out << "S(" << signed_; break;
        case TAG_UNSIGNED: out << "U(" << unsigned_; break;
        case TAG_DOUBLE:   out << "D(" << double_; break;
        default: out << "X("; break;
    }

    out <<"," << (used_?"used":"new") << ")";
}

#define X(a) out << " " << #a << "=" << a;

void Exporter::print(std::ostream& out) const
{
    out << "Exporter[";

    X(objectCount_);
    X(subCount_);
    X(type_);
    X(location_);
    X(objectId_);
    X(inObject_);
    X(path());

    out << "]";
}

void Exporter::openForWrite()
{
    handle().openForWrite(0);
}

void Exporter::close()
{
    writeTag(TAG_EOF);
    handle().close();
}

void Exporter::startSchemas()
{
    writeTag(TAG_START_SCHEMAS);
}

void Exporter::endSchemas()
{
    writeTag(TAG_END_SCHEMAS);
}

void Exporter::startDatabase(const std::string& path, unsigned long id, unsigned long  long count)
{
    PathName home("~");
    std::string p = path;

    if(p.find(home) == 0) {
         p = std::string("~/") + p.substr(std::string(home).length());
    }

    writeTag(TAG_START_DATABASE);
    writeString(p);
    writeUnsigned(id);
    writeUnsigned(count);

}

void Exporter::endDatabase(const std::string&, unsigned long id)
{
    writeTag(TAG_END_DATABASE);
    writeUnsigned(objectCount_);
    objectCount_ = 0;
}

//-----------------------------------------------------------------------------

} // namespace eckit
