/*
 *  Copyright (C) 2005 Alo Sarv <madcat_@users.sourceforge.net>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#define BOOST_SPIRIT_DEBUG
#define BOOST_SPIRIT_DEBUG_PRINT_SOME 40

#include <boost/spirit.hpp>
#include <boost/spirit/dynamic/for.hpp>
#include <boost/spirit/phoenix.hpp>
#include <boost/spirit/actor/clear_actor.hpp>
#include <boost/lambda/lambda.hpp>
#include <stdint.h>
#include <fstream>
#include <hnbase/utils.h>
#include <boost/algorithm/string/trim.hpp>

using namespace boost::spirit;
using namespace phoenix;
using namespace boost::lambda;

class TorrentInfo {
public:
	TorrentInfo(const std::string &data);
private:
	struct TorrentFile {
	public:
		uint64_t length;
		std::string name;
		void clear() {
			name.clear();
			length = 0;
		}
	};

	std::string announceUrl;
	int64_t creationDate;
	uint64_t length;
	std::string name;
	uint32_t chunkSize;
	std::string hashes;
	std::vector<TorrentFile> files;
	std::string comment;
	std::string createdBy;

	friend std::ostream& operator<<(
		std::ostream &o, const TorrentInfo::TorrentFile &f
	) {
		return o << "   " << f.length << " | " << f.name << std::endl;
	}
};

TorrentInfo::TorrentInfo(const std::string &data)
: creationDate(), length(), chunkSize() {
	#define BSTR(val) uint_p[assign_a(tmp)] >> ':'                  \
		>> repeat_p(boost::ref(tmp))[anychar_p[push_back_a(val)]]
	#define BINT(val) ch_p('i') >> int_p[assign_a(val)] >> 'e'

	rule<> dummyList, dummyDict, unknown;
	int tmp = 0, dummy = 0;
	std::string dummy2;
	TorrentFile tmpFile;
	rule<> fileDict =
		'd' >> *(  ("6:length" >> BINT(tmpFile.length))
			|  ("4:path" >> ch_p('l') >> *BSTR(tmpFile.name)[
				push_back_a(tmpFile.name, '/')
				] >> 'e'
			   )
			|  unknown
			)
		>> ch_p('e')[push_back_a(files, tmpFile)][clear_a(tmpFile)]
	;
	rule<> infod =
		'd' >> *(  ("6:length"        >> BINT(length))
			|  ("12:piece length" >> BINT(chunkSize))
			|  ("4:name"          >> BSTR(name))
			|  ("6:pieces"        >> BSTR(hashes))
			|  ("5:files"         >> ch_p('l') >> *fileDict >> 'e')
			|  unknown
			)
		>> 'e'
	;
	dummyList =
		'l' >> *( BSTR(dummy2)
			| BINT(dummy)
			| dummyDict
			| dummyList
			)
		>> 'e'
	;
	dummyDict =
		'd' >> *( BSTR(dummy2) >>
				( BSTR(dummy2)
				| BINT(dummy)
				| dummyDict
				| dummyList
				)
			)
		>> 'e'
	;
	unknown =
	    BSTR(dummy2)
	    >> ( BINT(dummy) | BSTR(dummy2) | dummyDict | dummyList )
	;

	rule<> btorrent =
		'd' >> *(  ("8:announce"       >> BSTR(announceUrl))
			|  ("13:creation date" >> BINT(creationDate))
			|  ("4:info"           >> infod)
			|  ("7:comment"        >> BSTR(comment))
			|  ("10:created by"    >> BSTR(createdBy))
			|  unknown
			)
		>> 'e' >> end_p
	;

	BOOST_SPIRIT_DEBUG_RULE(btorrent);
	BOOST_SPIRIT_DEBUG_RULE(infod);
	BOOST_SPIRIT_DEBUG_RULE(unknown);
	BOOST_SPIRIT_DEBUG_RULE(dummyList);
	BOOST_SPIRIT_DEBUG_RULE(dummyDict);
	BOOST_SPIRIT_DEBUG_RULE(fileDict);

	parse_info<> info = parse(
		data.c_str(), data.c_str() + data.size(), btorrent
	);
	if (info.full) {
		std::cerr << "Parse succeeded." << std::endl;
	} else {
		std::cerr << "Parsing stopped at: " << std::endl;
		std::cerr << "--------------" << std::endl;
		std::ostringstream tmp; tmp << info.stop;
		std::cerr << Utils::hexDump(tmp.str()) << std::endl;
		std::cerr << "--------------" << std::endl;
	}

	// post-processing - calculate total length (if not known yet), and
	// trim tailing slashes from filenames (inherent from parser)
	bool calcLength = !length;
	for (uint32_t i = 0; i < files.size(); ++i) {
		boost::algorithm::trim_if(files[i].name, __1 == '/');
		if (calcLength) {
			length += files[i].length;
		}
	}

	std::cerr << "Announce URL:  " << announceUrl            << std::endl;
	std::cerr << "Creation date: " << creationDate           << std::endl;
	std::cerr << "Length:        " << length                 << std::endl;
	std::cerr << "Name:          " << name                   << std::endl;
	std::cerr << "Chunk size:    " << chunkSize              << std::endl;
	std::cerr << "Got hashes:    " << hashes.size() / 20.0   << std::endl;
	std::cerr << "Got " << files.size() << " files: " << std::endl;
	for (uint32_t i = 0; i < files.size(); ++i) {
		std::cerr << i << ": " << files[i].length << ":";
		std::cerr << files[i].name << std::endl;
	}
	std::cerr << "Comment: \n----------\n" << comment << "\n----------\n";
	std::cerr << "Created by:    " << createdBy              << std::endl;
//	std::cerr << "Hashdata:      " << Utils::hexDump(hashes) << std::endl;
}

int main(int argc, char *argv[]) {
	assert(argc == 2);
	std::string data;
	std::ifstream f(argv[1], std::ios::binary);
	char buf[10240];
	while (f) {
		f.read(buf, 10240);
		data.append(std::string(buf, f.gcount()));
	}
//	std::cerr << Utils::hexDump(data) << std::endl;
	TorrentInfo ti(data);
	return 0;
}