/**
 * PHFM.cpp
 * Psi History File Merger
 * Sep-2004, Feb-2007 by Michał Jazłowiecki
 *
 * Compile using modern C++ compiler with Standard C++ Library as described in
 * "C++ Programming Language" 3rd Edition by Bjarne Stroustrup and
 * ISO/IEC 14882:2003 "Programming Language: C++" document.
 */

#ifndef __cplusplus
#error Compile this file using modern C++ compiler.
#endif // #ifndef __cplusplus

#define VERSION "1.4"


#include <algorithm>  // find
#include <functional> // pair
#include <fstream>    // ifstream, ofstream
#include <iostream>   // cout, endl, flush, getline, istream, ostream
#include <map>        // map
#include <string>     // string
#include <vector>     // vector


using std::cout;
using std::endl;
using std::flush;
using std::getline;
using std::ifstream;
using std::istream;
using std::map;
using std::ofstream;
using std::ostream;
using std::pair;
using std::string;
using std::vector;


class History
{
	public:
		typedef vector<string> HistoryPage;
		typedef map<string, HistoryPage> HistoryEntries;
		typedef HistoryEntries::size_type size_type;
		typedef pair<string, HistoryPage> HistoryEntry;

	private:
		HistoryEntries history;
		size_type count;

#ifdef __BORLANDC__
#pragma warn -inl-
#endif // #ifdef __BORLANDC__
		friend ostream & operator <<(ostream & to, const HistoryPage & hp)
		{
			for (HistoryPage::const_iterator it = hp.begin(); it != hp.end(); ++it)
				to << *it << "\n";
			return to;
		}

	public:
		History(): history(), count(0)
		{
		}
		~History()
		{
			count = 0;
			history.clear();
		}

		friend void operator >>(istream & from, History & h)
		{
			while (from && !from.eof())
			{
				// Read history entry from file
				string line;
				getline(from, line);
				// Make sure this is not an empty line
				if (!line.length())
					break;
				++h.count;
/*// michalj; February, 01 2007: it seems that some type == 0 && flags == "N---" are normal messages that get stripped
				// See whether this is not an event history line
				string type = line.substr(21, 1);
				string::size_type flagsPipe = line.find('|', 23);
				string flags = line.substr(flagsPipe + 1, 4);
				if (type == "0" && flags.at(1) == '-')
					continue;
//*/
				// Extract key
				string key = line.substr(1, 19); // timestamp only
				// Look up the history for the key
				HistoryEntries::iterator hit = h.history.find(key);
				if (hit != h.history.end())
				{
					// The key is already here --- look up the queue
					HistoryPage::iterator pit = find(hit->second.begin(), hit->second.end(), line);
					if (pit == hit->second.end())
					{
						// The line was not found --- append it
						hit->second.push_back(line);
					}
				}
				else
				{
					// The key was not found --- save the line
					HistoryPage page;
					page.push_back(line);
					h.history.insert(HistoryEntry(key, page));
				}
			}
		}
		friend void operator <<(ostream & to, const History & h)
		{
			for (HistoryEntries::const_iterator it = h.history.begin(); it != h.history.end(); ++it)
				to << it->second;
			to << flush;
		}
#ifdef __BORLANDC__
#pragma warn -inl.
#endif // #ifdef __BORLANDC__

		void clear()
		{
			history.clear();
		}
		bool empty() const
		{
			return history.empty();
		}
		size_type size() const
		{
			size_type entries = 0;
			for (HistoryEntries::const_iterator it = history.begin(); it != history.end(); ++it)
				entries += it->second.size();
			return entries;
		}

		size_type read() const
		{
			return count;
		}
};

typedef vector<string> Files;


int main(int argc, char ** argv)
{
	cout << "Psi History File Merger" 
#ifdef VERSION
	     << ", version " << VERSION
#endif // #ifdef VERSION
	     << "\nMerges and removes redundant entries from multiple Psi history files." << endl;

	// Get programs basename
	string basename(argv[0]);
	string::size_type pos = basename.rfind("\x5C");
	if (pos != string::npos)
		basename.erase(0, pos + 1);
	pos = basename.rfind("/");
	if (pos != string::npos)
		basename.erase(0, pos + 1);
	pos = basename.rfind(".");
	if (pos != string::npos && pos != 0)
		basename.erase(pos, basename.length() - pos);

	// Check argument count
	if (argc <= 2)
	{
		cout << "*** Expected 2 or more arguments, not " << (argc - 1) << "!\n    Command line:\n    " << basename << " infile1 [infile2 ... infilen] outfile\n        Merges infile1 with infile2, ..., infilen and writes it to outfile.\n    " << basename << " --indirs indir1 [indir2 ... indirn] outdir filename\n        Merges indir1/filename with indir2/filename, ..., indirn/filename\n        and writes it to outdir/filename.\n*** Quitting." << endl;
		return -1;
	}

	// Switch between modes
	Files inFiles;
	int fileCount;
	string outFile;
	if (string(argv[1]) == "--indirs")
	{
		cout << "[MODE: directory list]" << endl;
		fileCount = argc - 4;
		string fileName = "/" + string(argv[fileCount + 3]);
		for (int i = 0; i < fileCount; ++i)
		{
			inFiles.push_back(argv[i + 2] + fileName);
		}
		outFile = argv[fileCount + 2] + fileName;
	}
	else
	{
		cout << "[MODE: file list]" << endl;
		fileCount = argc - 2;
		for (int i = 1; i <= fileCount; ++i)
			inFiles.push_back(argv[i]);
		outFile = argv[fileCount + 1];
	}
	cout << "    This program will try to merge " << fileCount << " Psi history files." << endl;

	History history;
	History::size_type entries = 0, read = 0;
	int i = 1;
	cout << "[INPUT FILE(S)]" << endl;
	for (Files::iterator file = inFiles.begin(); file != inFiles.end(); ++file, ++i)
	{
		cout << "    [" << i << "/" << inFiles.size() << "] " << *file << "\n    Opening... " << flush;
		ifstream in(file->c_str(), std::ios::in);
		if (!in)
		{
			cout << ":(\n*** File does not exist or is not readable.\n*** Skipping." << endl;
			--fileCount;
			continue;
		}
		else
			cout << ":)\n    Reading... " << flush;
		in >> history;
		cout << (history.read() - read) << flush;
		cout << " (" << (read = history.read()) << " total) read, " << (history.size() - entries) << flush;
		cout << " (" << (entries = history.size()) << " total) in memory." << endl;
		in.close();
	}
	cout << "[OUTPUT FILE]" << endl;
	if (fileCount)
	{
		cout << "    Filename: " << outFile << "\n    Opening... " << flush;
		ofstream out(outFile.c_str(), std::ios::out | std::ios::trunc | std::ios::binary);
		if (!out)
		{
			cout << ":(\n*** File is read only.\n*** Aborting further merge process.\n*** Quitting." << endl;
			return -2;
		}
		else
			cout << ":)\n    Writing... " << flush;
		out << history;
		cout << ":)\n    " << fileCount << " Psi history file(s) merged. " << (read - entries) << " redundant entries removed." << endl;
		out.close();
	}
	else
	{
		cout << "*** No input files read. Output file not created/written.\n*** Quitting." << endl;
		return -3;
	}

	return 0;
}