/*
    biobambam2
    Copyright (C) 2009-2015 German Tischler
    Copyright (C) 2011-2015 Genome Research Limited

    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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <libmaus2/bambam/BamNumExtractor.hpp>
#include <libmaus2/bambam/BamBlockWriterBaseFactory.hpp>
#include <biobambam2/BamBamConfig.hpp>
#include <biobambam2/Licensing.hpp>

static int getDefaultLevel() {return libmaus2::lz::DeflateDefaults::getDefaultLevel();}

int bambisect(libmaus2::util::ArgInfo const & arginfo)
{
	int const level = arginfo.getValue<int>("level",getDefaultLevel());
	std::string const tmpfilenamebase = arginfo.getUnparsedValue("tmpfile",arginfo.getDefaultTmpFileName());
	std::string const tmpfile = tmpfilenamebase + ".bam.tmp";

	std::string const infn = arginfo.getUnparsedRestArg(0);
	std::string const command =  arginfo.getUnparsedRestArg(1);

	std::string const fullcommand = command + " " + tmpfile;

	libmaus2::bambam::BamNumExtractor BNE(infn);

	uint64_t const n = BNE.indexdec->getAlignmentCount();

	struct StackNode
	{
		int64_t parent;
		uint64_t visit;
		uint64_t subvisit;
		uint64_t from;
		uint64_t to;
		std::pair<int64_t,int64_t> childfail;

		StackNode()
		{

		}
		StackNode(
			int64_t const rparent,
			uint64_t const rvisit,
			uint64_t const rsubvisit,
			uint64_t const rfrom,
			uint64_t const rto,
			std::pair<int64_t,int64_t> const rchildfail
		)
		: parent(rparent), visit(rvisit), subvisit(rsubvisit), from(rfrom), to(rto), childfail(rchildfail) {}

		std::string toString() const
		{
			std::ostringstream ostr;
			ostr << "StackNode(parent=" << parent << ",visit=" << visit << ",subvisit=" << subvisit << ",from=" << from << ",to=" << to << ",childfail=" << childfail.first << "," << childfail.second << ")";
			return ostr.str();
		}
	};

	std::vector<StackNode> V;
	V.push_back(StackNode(-1,0,0,0,n,std::pair<int64_t,int64_t>(-1,-1)));

	while ( V.size() )
	{
		StackNode S = V.back();
		V.pop_back();

		std::cerr << "processing " << S.toString() << std::endl;

		uint64_t const id = V.size();
		uint64_t const diam = S.to - S.from;

		if ( ! S.visit )
		{
			std::cerr << "extracting " << S.from << "," << S.to << std::endl;

			BNE.extract(tmpfile,level,S.from,S.to);

			std::cerr << "running " << fullcommand << " for " << S.from << "," << S.to << std::endl;

			int const r = system(fullcommand.c_str());

			std::cerr << "got return code " << r << std::endl;

			// call failed?
			if ( r != 0 )
			{
				V.push_back(StackNode(S.parent,S.visit+1,0,S.from,S.to,S.childfail));

				int64_t parentid = S.parent;

				while ( parentid >= 0 )
				{
					std::cerr << "propagating failed "
						<< "from [" << S.from << "," << S.to << ") to "
						<< "[" << V.at(parentid).from << "," << V.at(parentid).to << ")" << std::endl;

					V.at(parentid).childfail = std::pair<int64_t,int64_t>(S.from,S.to);
					parentid = V[parentid].parent;
				}
			}
			else
			{
				// left empty
			}
		}
		else if ( S.childfail.first == -1 && ((diam >> S.visit) != 0) )
		{
			if ( S.subvisit == 0 )
			{
				V.push_back(StackNode(S.parent,S.visit,S.subvisit+1,S.from,S.to,S.childfail));
				V.push_back(StackNode(id,0 /* visit */,0 /* subvisit */,
					S.from,S.to-(diam >> S.visit),
					std::pair<int64_t,int64_t>(-1,-1))
				);
			}
			else if ( S.subvisit == 1 )
			{
				V.push_back(StackNode(S.parent,S.visit,S.subvisit+1,S.from,S.to,S.childfail));
				V.push_back(StackNode(id,0 /* visit */,0 /* subvisit */,S.from + (diam >> S.visit),S.to,std::pair<int64_t,int64_t>(-1,-1)));
			}
			else
			{
				V.push_back(StackNode(S.parent,S.visit+1,0 /* subvisit */,S.from,S.to,S.childfail));
			}
		}
		else if ( S.childfail.first == -1 && ((diam >> S.visit) == 0) )
		{
			std::cerr << "failed " << S.from << "," << S.to << std::endl;
		}
		else
		{
			assert ( S.childfail.first != -1 );

			#if 0
			if ( S.parent >= 0 )
			{
				std::cerr << "propagating failed [" << S.childfail.first << "," << S.childfail.second << ")"
					<< " from [" << S.from << "," << S.to << ") to "
					<< "[" << V.at(S.parent).from << "," << V.at(S.parent).to << ")" << std::endl;
				V.at(S.parent).childfail = S.childfail;
			}
			#endif
		}
	}

	return EXIT_SUCCESS;
}

int main(int argc, char * argv[])
{
	try
	{
		libmaus2::util::ArgInfo const arginfo(argc,argv);

		for ( uint64_t i = 0; i < arginfo.restargs.size(); ++i )
			if (
				arginfo.restargs[i] == "-v"
				||
				arginfo.restargs[i] == "--version"
			)
			{
				std::cerr << ::biobambam2::Licensing::license();
				return EXIT_SUCCESS;
			}
			else if (
				arginfo.restargs[i] == "-h"
				||
				arginfo.restargs[i] == "--help"
			)
			{
				std::cerr << ::biobambam2::Licensing::license();
				std::cerr << std::endl;
				std::cerr << "synopis: " << argv[0] << " <in.bam> <prog>" << std::endl;
				std::cerr << std::endl;
				std::cerr << "Key=Value pairs:" << std::endl;
				std::cerr << std::endl;

				std::vector< std::pair<std::string,std::string> > V;

				V.push_back ( std::pair<std::string,std::string> ( "level=<["+::biobambam2::Licensing::formatNumber(getDefaultLevel())+"]>", libmaus2::bambam::BamBlockWriterBaseFactory::getBamOutputLevelHelpText() ) );

				::biobambam2::Licensing::printMap(std::cerr,V);

				std::cerr << std::endl;
				return EXIT_SUCCESS;
			}

		return bambisect(arginfo);
	}
	catch(std::exception const & ex)
	{
		std::cerr << ex.what() << std::endl;
		return EXIT_FAILURE;
	}
}
