/*
 * © 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 <stdlib.h>
#include <stdio.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;
}
