/*
 * © Copyright 1996-2012 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 <stdio.h>
#include <stdlib.h>

#include "ObsKey.h"

#define file_offset off_t

const long kBufferSize = 1024 * 1024 * 4;

extern "C" {
extern fortint _readbufr(FILE*, char*, fortint*);
}

struct Entry {

    long long offset_;
    long long length_;
    long n_;

    Entry(long long o = 0, long long l = 0, long n = 0) :
        offset_(o),
        length_(l),
        n_(n) {}
};

typedef map<ObsKey, Entry, less<ObsKey> > ObsMap;
typedef map<ObsKey, int, less<ObsKey> > ObsMapPosition;

static bool allowdup = false;
static bool order    = false;
static bool check    = true;
static bool subset   = false;

char buffer[kBufferSize];
void mapFile(FILE* f, ObsMap& m, const string& name) {
    fortint err;
    fortint len = kBufferSize;
    long count  = 0;
    ObsMapPosition mpos;
    vector<ObsKey> theKeys;

    while ((err = _readbufr(f, buffer, &len)) == 0) {
        try {
            ObsKey key(buffer, len, check);
            file_offset pos = ::ftell(f) - len;
            ;
            // cout << "Read BUFR message " << count << " of " << len << " bytes, "
            //      << "with key length of " << key.length() << " at position " << pos << endl;

            if (m.find(key) == m.end()) {
                Entry entry(pos, key.length(), count);
                m[key]    = entry;
                mpos[key] = count;
                theKeys.push_back(key);
            }
            else if (!allowdup) {
                int prev = mpos[key];

                cerr << "Error: duplicates found in " << name << " for message ";
                cerr << count << " with message " << prev << ", exit" << endl;
                cerr << "Key for message " << count << " is: " << key << endl;
                cerr << "Key for message " << prev << " is: " << theKeys[prev] << endl;
                exit(1);
            }

            count++;
            len = kBufferSize;
        }
        catch (BadBufr& err) {
            cerr << "Error: " << err.text()
                 << " file " << name << " message:" << count << endl;
            exit(1);
        }
    }

    if (err != -1 && err != 0) {
        cerr << "Error " << err << " reading file " << name << endl;
        exit(1);
    }
}

inline void readBufr(FILE* f, ObsMap::iterator i, char* buffer, const string& name) {
    long n;

    ::fseek(f, (*i).second.offset_, SEEK_SET);
    n = ::fread(buffer, 1, (*i).second.length_, f);
    if (n != (*i).second.length_) {
        cerr << "Error: reading file " << name << " msg: " << (*i).second.n_ << " length: " << (*i).second.length_ << " offset: " << (*i).second.offset_ << endl;
        exit(1);
    }
}

long isAnOption(const string& s) {
    if (s == "-allowdup" || s == "-a")
        return 1;
    if (s == "-order" || s == "-o")
        return 2;
    if (s == "-cool" || s == "-c")
        return 3;
    if (s == "-subset" || s == "-s")
        return 4;

    return 0;
}

void usage(const string& s) {
    cerr << endl;
    cerr << "Usage: " << s << " [-[a]llowdup] [-[o]rder] [-[c]ool] [-[s]ubset] file1 file2" << endl;
    cerr << endl;
    cerr << "       "
         << "-allowdup: don't fail if duplicates" << endl;
    cerr << "       "
         << "-order   : fail if not same order" << endl;
    cerr << "       "
         << "-cool    : don't fail if problem in BUFR key" << endl;
    cerr << "       "
         << "-subset  : don't fail if file1 is contained in file2" << endl;
    cerr << endl;
    exit(0);
}

void analyseParameters(int count, char* argv[]) {
    const long optionCount      = 3;
    const long compulsaryParams = 2;

    if (count < 1 + compulsaryParams)
        usage(argv[0]);
    if (count > 1 + compulsaryParams + optionCount)
        usage(argv[0]);

    long optionThere = count - compulsaryParams - 1;
    if (optionThere == 0) {
        allowdup = false;
        order    = false;
    }
    else {
        for (long i = 0; i < optionThere; i++) {
            long which;
            if ((which = isAnOption(argv[i + 1])) == 0)
                usage(argv[0]);
            switch (which) {
                case 0:
                    usage(argv[0]);
                    break;
                case 1:
                    allowdup = true;
                    break;
                case 2:
                    order = true;
                    break;
                case 3:
                    check = false;
                    break;
                case 4:
                    subset = true;
                    break;
            }
        }
    }
}

char buffer1[kBufferSize];
char buffer2[kBufferSize];
char b1[kBufferSize];
char b2[kBufferSize];

int main(int argc, char* argv[]) {

    analyseParameters(argc, argv);

    string file1 = argv[argc - 2];
    string file2 = argv[argc - 1];


    FILE* f1 = fopen(file1.c_str(), "r");
    if (f1 == 0) {
        cerr << "Error: cannot open file " << file1 << endl;
        exit(1);
    }
    ::setbuf(f1, buffer1);
    FILE* f2 = fopen(file2.c_str(), "r");
    if (f2 == 0) {
        cerr << "Error cannot open file " << file2;
        exit(1);
    }
    ::setbuf(f2, buffer2);

    ObsMap m1;
    ObsMap m2;
    mapFile(f1, m1, file1);
    mapFile(f2, m2, file2);

    cerr << file1 << ": " << m1.size() << " messages" << endl;
    cerr << file2 << ": " << m2.size() << " messages" << endl;

    if (subset) {
        for (ObsMap::iterator g1 = m1.begin(); g1 != m1.end(); ++g1)
            if (m2.find((*g1).first) == m2.end()) {
                cout << file1 << " is NOT CONTAINED in " << file2 << endl;
                cout << "message nb. " << ((*g1).second.n_ + 1) << " at offset " << (*g1).second.offset_ << " from file " << file1 << " has not been found in " << file2 << endl;
                exit(1);
            }
        cout << "ALL BUFR messages in " << file1 << " are in " << file2 << endl;
        exit(0);
    }

    if (m1.size() != m2.size()) {
        cerr << "Error: different number of bufr messages in files, failure." << endl;
        exit(1);
    }
    if (m1.size() == 0) {
        cerr << "Error: file " << file1 << " is empty" << endl;
        exit(1);
    }
    if (m2.size() == 0) {
        cerr << "Error: file " << file2 << " is empty" << endl;
        exit(1);
    }

    long count = 0;
    // got to compare bit by bit now
    ObsMap::iterator i1 = m1.begin();
    ObsMap::iterator i2 = m2.begin();
    // both maps are the same size
    while (i1 != m1.end()) {
        if (order && ((*i1).second.n_ != (*i2).second.n_)) {

            cerr << "Error: order problem " << (*i1).second.n_ << " and " << (*i2).second.n_ << endl;
            exit(1);
        }
        if ((*i1).second.length_ > kBufferSize || (*i1).second.length_ > kBufferSize) {
            cerr << "Error: buffer of " << kBufferSize << " bytes is too small" << endl;
            exit(1);
        }
        readBufr(f1, i1, b1, file1);
        readBufr(f2, i2, b2, file2);

        if (::memcmp(b1, b2, (*i1).second.length_) != 0) {
            cerr << "Error: files are not identical," << endl
                 << "                       message " << (*i1).second.n_ << " for file " << file1 << " size: " << (*i1).second.length_ << endl
                 << "                       message " << (*i2).second.n_ << " for file " << file2 << " size: " << (*i2).second.length_ << endl;

            exit(1);
        }

        ++i1;
        ++i2;
        count++;
    }

    fclose(f1);
    fclose(f2);

    cout << "BUFR files are identical" << endl;
    return 0;
}
