// This file is part of LatNet Builder.
//
// Copyright (C) 2012-2021  The LatNet Builder author's, supervised by Pierre L'Ecuyer, Universite de Montreal.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "latbuilder/Parser/EmbeddingType.h"
#include "latbuilder/Parser/Lattice.h"
#include "latbuilder/Parser/CommandLine.h"
#include "latbuilder/TextStream.h"
#include "latbuilder/Types.h"

#include "netbuilder/DigitalNet.h"
#include "netbuilder/Types.h"

#include "netbuilder/Parser/OutputStyleParser.h"

#include <fstream>
#include <chrono>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string/join.hpp>

namespace LatBuilder{
using TextStream::operator<<;

static unsigned int merit_digits_displayed = 0;

template <LatticeType LR, EmbeddingType ET>
void onLatticeSelected(const Task::Search<LR, ET>& s)
   {
     Dimension currentDim = s.bestLattice().dimension();
     Dimension totalDim = s.dimension();
      unsigned int old_precision = (unsigned int) std::cout.precision();
      if (merit_digits_displayed){
        std::cout.precision(merit_digits_displayed);
      }
      std::string lattice;
      if (s.minObserver().totalCount() == 1){
        lattice = " lattice";
      }
      else{
        lattice = " lattices";
      }

       std::cout << "End coordinate: " << currentDim << "/" << totalDim << " - "
       << s.minObserver().totalCount() << lattice  << " explored (" << s.minObserver().acceptedCount() << " accepted)"
       << " - partial merit value: " << s.bestMeritValue() << std::endl;

      if (merit_digits_displayed){
        std::cout.precision(old_precision);
      }
      if (currentDim < totalDim){
        std::cout << "Begin coordinate " << currentDim+1 << "/" << totalDim << std::endl;
      }
   }

boost::program_options::options_description
makeOptionsDescription()
{
   namespace po = boost::program_options;
   po::options_description desc("allowed options");

   desc.add_options ()
   ("help,h", "produce help message")
   ("version", "show version")
   ("set-type,t", po::value<std::string>(),
    "(required) point set type; possible values:\n"
    "  lattice\n"
    "  net\n")
   ("construction,c", po::value<std::string>(),
   "lattice construction; possible values:\n"
   "  ordinary\n"
   "  polynomial\n")
   ("dimension,d", po::value<std::string>(),
    "(required) point set dimension\n")
   ("size-parameter,s", po::value<std::string>(),
    "(required) modulus of the lattice; possible values:\n"
   "  <modulus>\n"
   "  <base>^<max-level>\n"
   "  input format :\n"
   "  ordinary lattice rules: integer (decimal representation)\n"
   "  polynomial lattice rules: polynomial (list of coefficients: 1011 stands for 1 + X^2 + X^3), or\n"
   "                            2^<max_level> (default irreducible modulus, not available for multilevel)\n"
   )
   ("exploration-method,e", po::value<std::string>(),
    "(required) exploration method; possible values:\n"
    "  evaluation:<a1>,...,<as>\n"
    "  exhaustive\n"
    "  random:<r>\n"
    "  Korobov\n"
    "  random-Korobov:<r>\n"
    "  full-CBC\n"
    "  random-CBC:<r>\n"
    "  fast-CBC\n"
    "  extend:<num-points>:<a1>,...,<as>\n"
    "where <r> is the number of samples, "
    "and <a1>,...,<as> are the components of the generating vector\n")
   ("figure-of-merit,f", po::value<std::string>(),
    "(required) type of figure of merit; format: [CU:]<merit>\n"
    "  where the optional \"CU:\" prefix switches on the coordinate-uniform evaluation algorithm,\n"
    "  and where <merit> is one of:\n"
    "    spectral  (only for ordinary lattice rules, no CU)\n"
    "    P<alpha>  (for both constructions, CU for l_2 norm)\n"
    "    R<alpha>  (only for ordinary lattice rules, CU for l_2 norm)\n"
    "    R         (for both constructions, CU for l_2 norm)\n"
    "    IA<alpha> (for interlaced polynomial lattice rules, CU for l_1 norm)\n"
    "    IB (for interlaced polynomial lattice rules, CU for l_1 norm)\n")
    ("interlacing-factor,i", po::value<std::string>()->default_value("1"),
    "(default: 1) interlacing factor (only for polynomial lattice rules). If larger than one, the constructed"
    "point set is an interlaced polynomial lattice rule. In this case, the figure of merit must be"
    "specific to interlaced polynomial lattice rules.\n")
   ("norm-type,q", po::value<std::string>(),
    "norm type used to combine the value of the projection-dependent figure of merit for all projections; possible values:"
    "    <q>: a real number corresponding the l_<q> norm\n"
    "    inf: corresponding to the `max' norm\n")
   ("weights,w", po::value<std::vector<std::string>>()->multitoken(),
    "(required) whitespace-separated list of weights specifications (the actual weights are the sum of these); possible values:\n"
    "  product:<default>:<coord1-weight>[,...]\n"
    "  order-dependent:<default>:<order-1-weight>,...,<order-s-weight>\n"
    "  POD:<default>:<order-1-weight>,...,<order-s-weight>:<default>:<coord1-weight>[,...]\n"
    "  projection-dependent:<proj-1>:<weight-1>[:<proj-2>:<weight-2>[:...]]\n"
    "    where <proj-n> is a comma-separated list of coordinates\n"
    "  file:\"<file>\"\n"
    "    line format in <file>:\n"
    "      <i1>,<i2>,...: <weight>\n"
    "      order <x>: <weight>\n"
    "      default: <weight>\n"
    "    if <file> is `-' data is read from standard input\n")
   ("weights-power,p", po::value<Real>(),
    "(default: same value as for the --norm-type argument) real number specifying that the weights passed as input will be assumed to be already elevated at that power (a value of `inf' is mapped to 1)\n")
   ("multilevel,M", po::value<std::string>()->default_value("false"),
    "multilevel point set; possible values:\n"
   "  false (default)\n"
   "  true\n")
   ("combiner,C", po::value<std::string>(),
    "(required for multilevel) combiner for multilevel merit values; possible values:\n"
    "  sum\n"
    "  max\n"
    "  level:{<level>|max}\n")
   ("filters,F", po::value<std::vector<std::string>>()->multitoken(),
    "whitespace-separated list of filters for merit values; possible values:\n"
    "  norm[:<levels>] for a default normalization (if available)"
    "  norm:{P<alpha>-SL10|P<alpha>-DPW08|P<alpha>|IA<alpha>|IB}[:<levels>]\n"
    "  low-pass:<threshold>\n"
    "where in the case of multilevel lattices, the optional parameter <levels> specifies the selected levels; possible values:\n"
    "  select[:<min-level>[:<max-level>]] (default)\n")
   ("repeat,r", po::value<unsigned int>()->default_value(1),
    "(optional) number of times the exploration must be executed\n"
   "(can be useful to obtain different results from random exploration)\n")
   ("verbose,v", po::value<int>()->default_value(0),
   "specify the verbosity of the program;\n"
   "ranges between 0 (default) and 3\n")
    ("output-folder,o", po::value<std::string>(),
    "(optional) path to the folder for the outputs of LatNeBuilder. The contents of the folder may be overwritten. If the folder does not exist, it is created. If no path is provided, no output folder is created.")
    ("output-style,O", po::value<std::string>()->default_value(""),
    "(optional) TBD")
   ("merit-digits-displayed", po::value<unsigned int>()->default_value(0),
    "(optional) number of significant figures to use when displaying merit values\n");

   return desc;
}

boost::program_options::variables_map
parse(int argc, const char* argv[])
{
   namespace po = boost::program_options;

   auto desc = makeOptionsDescription();

   po::variables_map opt;
   po::store(po::parse_command_line(argc, argv, desc), opt);
   po::notify(opt);

   if (opt.count("help")) {
      std::cout << desc << std::endl;
      std::exit (0);
   }

   for (const auto x : {"construction", "size-parameter", "exploration-method", "dimension", "figure-of-merit", "norm-type"}) {
      if (opt.count(x) != 1)
         throw std::runtime_error("--" + std::string(x) + " must be specified exactly once (try --help)");
   }

  if (opt.count("combiner") < 1 && opt["multilevel"].as<std::string>() == "true"){
    throw std::runtime_error("--combiner must be specified for multilevel set type (try --help)");
  }

   if (opt.count("weights") < 1)
      throw std::runtime_error("--weights must be specified (try --help)");

   return opt;
}

template <EmbeddingType ET>
std::string helper(const SizeParam<LatticeType::ORDINARY, ET>& param);

template<>
std::string helper(const SizeParam<LatticeType::ORDINARY, EmbeddingType::MULTILEVEL>& param)
{
  return std::to_string(param.base()) + "  // Base\n" + std::to_string(param.maxLevel()) + "  // Maximum level\n";
}

template<>
std::string helper(const SizeParam<LatticeType::ORDINARY, EmbeddingType::UNILEVEL>& param)
{
  return "0  // Base\n0  // Maximum level\n";
}

template <EmbeddingType ET>
std::string helper2(const SizeParam<LatticeType::ORDINARY, ET>& param);

template<>
std::string helper2(const SizeParam<LatticeType::ORDINARY, EmbeddingType::MULTILEVEL>& param)
{
  return ", embedded from " + std::to_string(param.base()) + " to " + std::to_string(param.maxLevel()) + "\n";
}

template<>
std::string helper2(const SizeParam<LatticeType::ORDINARY, EmbeddingType::UNILEVEL>& param)
{
  return "\n";
}

template <EmbeddingType ET>
void executeOrdinary(const Parser::CommandLine<LatticeType::ORDINARY, ET>& cmd, int verbose, unsigned int repeat, std::string outputFolder)
{
   const LatticeType LR = LatticeType::ORDINARY ;
   using namespace std::chrono;

   auto search = cmd.parse();

   const std::string separator = "====================\n";

   std::cout << separator << "    Input" << std::endl << separator << *search << std::endl;
    if (outputFolder != ""){
      std::ofstream outFile;
      std::string fileName = outputFolder + "/input.txt";
      outFile.open(fileName);
      outFile << "Input Command Line: " << cmd.originalCommandLine << std::endl << std::endl;
      outFile << *search;
      outFile.close();
    }

   if (verbose > 0) {
      search->onLatticeSelected().connect(onLatticeSelected<LR,ET>);
      search->setObserverVerbosity(verbose-1);
      search->setVerbose(verbose-2);
   }

   for (unsigned int i = 0; i < repeat; i++) {
        if (repeat > 1){
          std::cout << separator << "      Run " << i+1 << std::endl << separator;
        }
        else{
          std::cout << separator << "Running the task..." << std::endl << separator;
        }

      auto t0 = high_resolution_clock::now();
      search->execute();
      auto t1 = high_resolution_clock::now();

      unsigned int old_precision = (unsigned int) std::cout.precision();
      if (merit_digits_displayed)
   std::cout.precision(merit_digits_displayed);
     const auto lat = search->bestLattice();

   auto dt = duration_cast<duration<double>>(t1 - t0);
         std::cout << std::endl;
         std::cout << separator << "      Result" << std::endl << separator;
        std::cout << lat;
        std::cout << "Merit: " << search->bestMeritValue() << std::endl;
        std::cout << std::endl;
         std::cout << "ELAPSED CPU TIME: " << dt.count() << " seconds" << std::endl << std::endl;

      if (outputFolder != ""){
        std::ofstream outFile;
        std::string fileName = outputFolder + "/output.txt";
        outFile.open(fileName);
        outFile << "# Input Command Line: " << cmd.originalCommandLine << std::endl;
        outFile << "# Merit: " << search->bestMeritValue() << std::endl;
        outFile << "# Parameters for a lattice rule";
        outFile << helper2<ET>(lat.sizeParam());
        outFile << lat.dimension() <<"    # s = "<< lat.dimension() << " dimensions\n";
        outFile << lat.sizeParam().numPoints() <<"    # modulus = n = "<< lat.sizeParam().numPoints() << " points\n";
        auto vec = lat.gen();
        outFile << "# Coordinates of generating vector, starting at j=1" << std::endl;
        for (unsigned int coord = 0; coord < vec.size(); coord++){
          if (coord < vec.size() - 1){
            outFile << vec[coord] << std::endl;
          }
          else{
            outFile << vec[coord];
          }
        }
        outFile.close();
      }

      if (merit_digits_displayed)
   std::cout.precision(old_precision);

      search->reset();
    }
}

template <EmbeddingType ET>
void executePolynomial(const Parser::CommandLine<LatticeType::POLYNOMIAL, ET>& cmd, int verbose, unsigned int repeat, std::string outputFolder, NetBuilder::OutputStyle outputStyle)
{
   const LatticeType LR = LatticeType::POLYNOMIAL ;
   using namespace std::chrono;

   auto search = cmd.parse();

   unsigned int interlacingFactor = 1;
    try{
      interlacingFactor = boost::lexical_cast<unsigned int>(cmd.interlacingFactor);
    }
    catch (boost::bad_lexical_cast&) {}

   const std::string separator = "====================\n";

   std::cout << separator << "    Input" << std::endl << separator << *search << std::endl;
    if (outputFolder != ""){
      std::ofstream outFile;
      std::string fileName = outputFolder + "/input.txt";
      outFile.open(fileName);
      outFile << "Input Command Line: " << cmd.originalCommandLine << std::endl << std::endl;
      outFile << *search;
      outFile.close();
    }

   if (verbose > 0) {
      search->onLatticeSelected().connect(onLatticeSelected<LR,ET>);
      search->setObserverVerbosity(verbose-1);
      search->setVerbose(verbose-2);
   }

   for (unsigned int i = 0; i < repeat; i++) {
        if (repeat > 1){
          std::cout << separator << "      Run " << i+1 << std::endl << separator;
        }
        else{
          std::cout << separator << "Running the task..." << std::endl << separator;
        }

        auto t0 = high_resolution_clock::now();
        search->execute();
        auto t1 = high_resolution_clock::now();

        unsigned int old_precision = (unsigned int) std::cout.precision();
        if (merit_digits_displayed){
          std::cout.precision(merit_digits_displayed);
        }
       const auto lat = search->bestLattice();

        auto dt = duration_cast<duration<double>>(t1 - t0);
           std::cout << std::endl;
           std::cout << separator << "    Result" << std::endl << separator;
           std::cout << lat;
           std::cout << "Merit: " << search->bestMeritValue() << std::endl;
           std::cout << std::endl;
           std::cout << "ELAPSED CPU TIME: " << dt.count() << " seconds" << std::endl << std::endl;

      if (outputFolder != ""){
          NetBuilder::DigitalNet<NetBuilder::NetConstruction::POLYNOMIAL> net((unsigned int) lat.gen().size(), lat.sizeParam().modulus(),lat.gen());

          if (outputStyle != NetBuilder::OutputStyle::TERMINAL){
            std::ofstream outFile;
            std::string fileName = outputFolder + "/output.txt";
            outFile.open(fileName);
            outFile << "# Input Command Line: " << cmd.originalCommandLine << std::endl;
            outFile << "# Merit: " << search->bestMeritValue() << std::endl;
            outFile << net.format(outputStyle, interlacingFactor) ;
            outFile.close();
          }
      }

        if (merit_digits_displayed){
          std::cout.precision(old_precision);
        }

        search->reset();
    }
}

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

   try {
        auto opt = parse(argc, argv);

        // bool quiet = opt.count("quiet");
        int verbose = opt["verbose"].as<int>();

        auto repeat = opt["repeat"].as<unsigned int>();

        std::string outputFolder = "";
        if (opt.count("output-folder") >= 1){
          outputFolder = opt["output-folder"].as<std::string>();
          std::cout << "Writing in output folder: " << outputFolder << std::endl;
          boost::filesystem::create_directories(outputFolder);
        }

        // global variable
        merit_digits_displayed = opt["merit-digits-displayed"].as<unsigned int>();

        std::string outputstyle = opt["output-style"].as<std::string>();

       LatBuilder::LatticeType lattice = Parser::LatticeParser::parse(opt["construction"].as<std::string>());

       std::vector<std::string> all_args;
       if (argc > 1) {
          all_args.assign(argv + 1, argv + argc);
        }

       if(lattice == LatticeType::ORDINARY){

            Parser::CommandLine<LatticeType::ORDINARY, EmbeddingType::MULTILEVEL> cmd;

            cmd.originalCommandLine = boost::algorithm::join(all_args, " ");
            cmd.construction  = opt["exploration-method"].as<std::string>();
            cmd.size          = opt["size-parameter"].as<std::string>();
            cmd.dimension     = opt["dimension"].as<std::string>();
            cmd.normType      = opt["norm-type"].as<std::string>();
            cmd.figure        = opt["figure-of-merit"].as<std::string>();
            cmd.weights       = opt["weights"].as<std::vector<std::string>>();

            if (opt.count("combiner") == 1){
              cmd.combiner      = opt["combiner"].as<std::string>();
            }
            else{
              cmd.combiner = "level:max";
            }

            if (opt["interlacing-factor"].as<std::string>() != "1")
            {
              throw std::runtime_error("Interlacing can only be used with polynomial lattice rules.");
            }
            else
            {
              cmd.interlacingFactor = "1";
            }

            cmd.weightsPowerScale = 1.0;
            if (opt.count("weights-power") >= 1) {
               // assume 1.0 if norm-type is `inf' or anything else
               cmd.weightsPowerScale = 1.0;
               try {
                  // start the value of norm-type as a default
                  if (cmd.normType != "inf")
                     cmd.weightsPowerScale = boost::lexical_cast<Real>(cmd.normType);
               }
               catch (boost::bad_lexical_cast&) {}
               // then scale down according to interpretation of input
               cmd.weightsPowerScale /= opt["weights-power"].as<Real>();
            }

            if (opt.count("filters") >= 1)
               cmd.filters = opt["filters"].as<std::vector<std::string>>();

            EmbeddingType latType = Parser::EmbeddingType::parse(opt["multilevel"].as<std::string>());

            if (latType == EmbeddingType::UNILEVEL){
               executeOrdinary<EmbeddingType::UNILEVEL> (cmd, verbose, repeat, outputFolder);

             }
            else{
               executeOrdinary<EmbeddingType::MULTILEVEL> (cmd, verbose, repeat, outputFolder);

             }
      }

      else if(lattice == LatticeType::POLYNOMIAL){

            Parser::CommandLine<LatticeType::POLYNOMIAL, EmbeddingType::MULTILEVEL> cmd;

            cmd.originalCommandLine = boost::algorithm::join(all_args, " ");
            cmd.construction  = opt["exploration-method"].as<std::string>();
            cmd.size          = opt["size-parameter"].as<std::string>();
            cmd.dimension     = opt["dimension"].as<std::string>();
            cmd.normType      = opt["norm-type"].as<std::string>();
            cmd.figure        = opt["figure-of-merit"].as<std::string>();
            cmd.weights       = opt["weights"].as<std::vector<std::string>>();
            cmd.interlacingFactor = opt["interlacing-factor"].as<std::string>();
            if (opt.count("combiner") == 1){
              cmd.combiner      = opt["combiner"].as<std::string>();
            }
            else{
              cmd.combiner = "level:max";
            }

            cmd.weightsPowerScale = 1.0;
            if (opt.count("weights-power") >= 1) {
               // assume 1.0 if norm-type is `inf' or anything else
               cmd.weightsPowerScale = 1.0;
               try {
                  // start the value of norm-type as a default
                  if (cmd.normType != "inf")
                     cmd.weightsPowerScale = boost::lexical_cast<Real>(cmd.normType);
               }
               catch (boost::bad_lexical_cast&) {}
               // then scale down according to interpretation of input
               cmd.weightsPowerScale /= opt["weights-power"].as<Real>();
            }

            if (opt.count("filters") >= 1)
               cmd.filters = opt["filters"].as<std::vector<std::string>>();

            EmbeddingType latType = Parser::EmbeddingType::parse(opt["multilevel"].as<std::string>());

            NetBuilder::OutputStyle outputStyle = NetBuilder::Parser::OutputStyleParser<NetBuilder::NetConstruction::POLYNOMIAL>::parse(outputstyle);

            if (latType == EmbeddingType::UNILEVEL){
              executePolynomial< EmbeddingType::UNILEVEL> (cmd, verbose, repeat, outputFolder, outputStyle);

             }
            else{
              executePolynomial<EmbeddingType::MULTILEVEL> (cmd, verbose, repeat, outputFolder, outputStyle);

             }
      }
   }
   catch (Parser::ParserError& e) {
      std::cerr << "COMMAND LINE ERROR: " << e.what() << std::endl;
      std::exit(1);
   }
   catch (std::exception& e) {
      std::cerr << "ERROR: " << e.what() << std::endl;
      std::exit(1);
   }

   return 0;
}

}


//***********************************************************************
// program to analyze bands and report some information on changing
//
//***********************************************************************
// version
// 1.0.1	17/04/2019	Rémi Saint-Amant	Some bug correction, add debug
// 1.0.0	31/01/2019	Rémi Saint-Amant	Creation

//-te -790800 7649400 -711300 7709400 -multi -co "tiled=YES" -co "BLOCKXSIZE=512" -co "BLOCKYSIZE=512" -co "compress=LZW" -RGB Natural -of VRT --config GDAL_CACHEMAX 4096 -stats -overview {2,4,8,16} -overwrite "C:\Lansat(Fire)\Fire\fire.vrt"  "C:\Lansat(Fire)\Input\Landsat_2010-2015.vrt" "C:\Lansat(Fire)\Output\BC.vrt"
//"U:\GIS\#documents\TestCodes\FireSeverity\Fires\Fires.vrt" "U:\GIS\#documents\TestCodes\FireSeverity\Input\Landsat_2010-2015.vrt" "U:\GIS\#documents\TestCodes\FireSeverity\Output\test.vrt"

#include "stdafx.h"
//#include "VisualLeakDetector\include\vld.h"

#include <float.h>
#include <math.h>
#include <array>
#include <utility>
#include <iostream>
#include <bitset>
#include <boost/dynamic_bitset.hpp>

#include "FireSeverity.h"
#include "Basic/UtilTime.h"
#include "Basic/UtilMath.h"
#include "Basic/OpenMP.h"
#include "Geomatic/GDALBasic.h"
#include "Geomatic/LandsatDataset.h"

#pragma warning(disable: 4275 4251)
#include "gdal_priv.h"

#include "windows.h"
#include "psapi.h"

using namespace std;
using namespace WBSF;
using namespace WBSF::Landsat;

static const char* version = "1.0.1";

std::string CFireSeverity::GetDescription()
{
	return  std::string("FireSeverity version ") + version + " (" + _T(__DATE__) + ")\n";
}

CFireSeverityOption::CFireSeverityOption()
{
	m_outputType = GDT_Int16;
	m_scenesSize = Landsat::SCENES_SIZE;
	m_scenesLoaded = { NOT_INIT ,NOT_INIT };
	m_scenesTreated = { NOT_INIT ,NOT_INIT };
	m_buffer = 8;
	m_bDebug = false;

	m_appDescription = "This software compute fire severity (delta NBR) from Landsat images series";

	static const COptionDef OPTIONS[] =
	{
			{ "-Buffer", 1, "nbPixels", false, "Set buffer size around fires to do NBR correction. 8 by default. 0 to don't use correction." },
			{ "-Debug",0,"",false,"Output debug information."},
			{ "Fires", 0, "", false, "fires zones (0=no fire, 1=fire) for each years. Bands name must finish with _year." },
			{ "srcfile", 0, "", false, "Input LANDSAT scenes image file path." },
			{ "dstfile", 0, "", false, "Output LANDSAT scenes image file path." }
	};

	for (int i = 0; i < sizeof(OPTIONS) / sizeof(COptionDef); i++)
		AddOption(OPTIONS[i]);

	//AddOption("-Period");
	RemoveOption("-ot");
	static const CIOFileInfoDef IO_FILE_INFO[] =
	{
		{ "Fire", "f", "", "", "", "File of fires zones (0=no fire, 1=fire) for each years. Bands name must finish with _year." },
		{ "LANDSAT", "src1file", "NbScenes", "ScenesSize(9)", "B1: Landsat band 1|B2: Landsat band 2|B3: Landsat band 3|B4: Landsat band 4|B5: Landsat band 5|B6: Landsat band 6|B7: Landsat band 7|QA: Image quality|JD: Date(Julian day 1970)|... for each scene" },
		{ "Output", "dstfile", "Nb scenes processed", "", "DeltaNBR|zScore|FireSeverity" },
	};

	for (int i = 0; i < sizeof(IO_FILE_INFO) / sizeof(CIOFileInfoDef); i++)
		AddIOFileInfo(IO_FILE_INFO[i]);

}

ERMsg CFireSeverityOption::ProcessOption(int& i, int argc, char* argv[])
{
	ERMsg msg;

	if (IsEqual(argv[i], "-Buffer"))
	{
		m_buffer = atoi(argv[++i]);
		if (m_buffer > 100)
			msg.ajoute("Invalid buffer. Buffer must be smaller than 100.");
	}
	else if (IsEqual(argv[i], "-Debug"))
	{
		m_bDebug = true;
	}
	else
	{
		//Look to see if it's a know base option
		msg = CBaseOptions::ProcessOption(i, argc, argv);
	}

	return msg;
}

ERMsg CFireSeverityOption::ParseOption(int argc, char* argv[])
{
	ERMsg msg = CBaseOptions::ParseOption(argc, argv);

	ASSERT(NB_FILE_PATH == 3);
	if (msg && m_filesPath.size() != NB_FILE_PATH)
	{
		msg.ajoute("ERROR: Invalid argument line. 3 files are needed: the fire series, the LANDSAT images series and the destination image.");
		msg.ajoute("Argument found: ");
		for (size_t i = 0; i < m_filesPath.size(); i++)
			msg.ajoute("   " + to_string(i + 1) + "- " + m_filesPath[i]);
	}

	return msg;
}

//***********************************************************************

ERMsg CFireSeverity::OpenAll(CLandsatDataset& landsatDS, CGDALDatasetEx& maskDS, CGDALDatasetEx& fireDS, CGDALDatasetEx& outputDS, CGDALDatasetEx& debugDS)
{
	ERMsg msg;

	if (!m_options.m_bQuiet)
	{
		cout << endl << "Open input image..." << endl;
	}

	msg = landsatDS.OpenInputImage(m_options.m_filesPath[CFireSeverityOption::LANDSAT_FILE_PATH], m_options);

	if (msg && landsatDS.GetNbScenes() < 3)
		msg.ajoute("Input Landsat series need at least 3 scenes");

	if (msg)
		landsatDS.UpdateOption(m_options);

	if (msg)
	{
		const std::vector<CTPeriod>& scenesPeriod = landsatDS.GetScenePeriod();
		CTPeriod p = m_options.m_period;//period to load
		set<size_t> toLoad;

		for (size_t s = 0; s < landsatDS.GetNbScenes(); s++)
		{
			if (p.IsIntersect(scenesPeriod[s]))
				toLoad.insert(s);
		}

		size_t nb_input_scenes = 0;
		if (!toLoad.empty())
		{
			m_options.m_scenesLoaded[0] = *toLoad.begin();
			m_options.m_scenesLoaded[1] = *toLoad.rbegin();
		}

		if (m_options.m_periodTreated.IsInit())
		{
			const std::vector<CTPeriod>& scenesPeriod = landsatDS.GetScenePeriod();
			CTPeriod p = m_options.m_periodTreated;

			set<size_t> selected;

			for (size_t s = 0; s < scenesPeriod.size(); s++)
			{
				if (s > 1 && (scenesPeriod[s].End() < scenesPeriod[s - 1].Begin()))
					msg.ajoute("Scenes of the input Landsat images (" + GetFileName(m_options.m_filesPath[CFireSeverityOption::LANDSAT_FILE_PATH]) + ") are not chronologically ordered.");

				if (p.IsIntersect(scenesPeriod[s]))
					selected.insert(s);
			}

			if (!selected.empty())
			{
				m_options.m_scenesTreated[0] = *selected.begin();
				m_options.m_scenesTreated[1] = *selected.rbegin();
			}
			else
			{
				msg.ajoute("No input scenes intersect -PeriodTrait (" + m_options.m_periodTreated.GetFormatedString("%1 %2", "%F") + ").");
			}
		}

		if (m_options.m_scenesTreated[0] == NOT_INIT)
			m_options.m_scenesTreated[0] = 0;

		if (m_options.m_scenesTreated[1] == NOT_INIT)
			m_options.m_scenesTreated[1] = landsatDS.GetNbScenes() - 1;

		if (m_options.m_scenesTreated[0] >= landsatDS.GetNbScenes() || m_options.m_scenesTreated[1] >= landsatDS.GetNbScenes())
			msg.ajoute("Scenes {" + to_string(m_options.m_scenesTreated[0] + 1) + ", " + to_string(m_options.m_scenesTreated[1] + 1) + "} must be in range {1, " + to_string(landsatDS.GetNbScenes()) + "}");

		if (m_options.m_scenesTreated[0] > m_options.m_scenesTreated[1])
			msg.ajoute("First scene (" + to_string(m_options.m_scenesTreated[0] + 1) + ") must be smaller or equal to the last scene (" + to_string(m_options.m_scenesTreated[1] + 1) + ")");

	}

	if (!msg)
		return msg;

	//update period from scene
	size_t nbSceneLoaded = m_options.m_scenesLoaded[1] - m_options.m_scenesLoaded[0] + 1;
	size_t nbScenedProcess = m_options.m_scenesTreated[1] - m_options.m_scenesTreated[0] + 1;

	CTPeriod loadedPeriod;
	CTPeriod processPeriod;
	const std::vector<CTPeriod>& p = landsatDS.GetScenePeriod();

	ASSERT(m_options.m_scenesTreated[0] < p.size());
	ASSERT(m_options.m_scenesTreated[1] < p.size());

	for (size_t i = 0; i < p.size(); i++)
	{
		if (i >= m_options.m_scenesLoaded[0] && i <= m_options.m_scenesLoaded[1])
			loadedPeriod += p[i];

		if (i >= m_options.m_scenesTreated[0] && i <= m_options.m_scenesTreated[1])
			processPeriod += p[i];
	}

	if (!m_options.m_bQuiet)
	{
		CGeoExtents extents = landsatDS.GetExtents();
		CProjectionPtr pPrj = landsatDS.GetPrj();
		string prjName = pPrj ? pPrj->GetName() : "Unknown";

		if (m_options.m_period.IsInit())
			cout << "    User's Input loading period  = " << m_options.m_period.GetFormatedString() << endl;

		if (m_options.m_periodTreated.IsInit())
			cout << "    User's Input treating period = " << m_options.m_periodTreated.GetFormatedString() << endl;

		cout << "    Size           = " << landsatDS->GetRasterXSize() << " cols x " << landsatDS->GetRasterYSize() << " rows x " << landsatDS.GetRasterCount() << " bands" << endl;
		cout << "    Extents        = X:{" << ToString(extents.m_xMin) << ", " << ToString(extents.m_xMax) << "}  Y:{" << ToString(extents.m_yMin) << ", " << ToString(extents.m_yMax) << "}" << endl;
		cout << "    Projection     = " << prjName << endl;
		cout << "    NbBands        = " << landsatDS.GetRasterCount() << endl;
		cout << "    Scene size     = " << landsatDS.GetSceneSize() << endl;
		cout << "    Entire period  = " << landsatDS.GetPeriod().GetFormatedString() << " (nb scenes = " << landsatDS.GetNbScenes() << ")" << endl;
		cout << "    Loaded period  = " << loadedPeriod.GetFormatedString() << " (nb scenes = " << nbSceneLoaded << ")" << endl;
		//cout << "    Treated period = " << processPeriod.GetFormatedString() << " (nb scenes = " << nbScenedProcess << ")" << endl;
	}

	if (!m_options.m_period.IsIntersect(processPeriod))
		msg.ajoute("Input period and process period does not intersect. Verify period or scenes options");

	if (msg && !m_options.m_maskName.empty())
	{
		if (!m_options.m_bQuiet)
			cout << "Open mask image..." << endl;

		msg += maskDS.OpenInputImage(m_options.m_maskName);
	}

	if (msg)
		msg = fireDS.OpenInputImage(m_options.m_filesPath[CFireSeverityOption::FIRE_FILE_PATH], m_options);

	if (msg && m_options.m_bCreateImage)
	{
		if (!m_options.m_bQuiet)
			cout << "Open output images " << endl;
		//cout << "Open output images " << " x(" << m_options.m_extents.m_xSize << " C x " << m_options.m_extents.m_ySize << " R x " << m_options.m_nbBands << " bands) with " << m_options.m_CPU << " threads..." << endl;

		string filePath = m_options.m_filesPath[CFireSeverityOption::OUTPUT_FILE_PATH];
		CFireSeverityOption options = m_options;
		options.m_nbBands = NB_OUTPUTS * fireDS.GetRasterCount();

		for (size_t zz = 0; zz < fireDS.GetRasterCount(); zz++)
		{
			string title = GetFileTitle(fireDS.GetInternalName(zz));
			if (title.length() > 4)
			{
				string sYear = title.substr(title.size() - 4);
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_dNBR.tif|";
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_zScore1.tif|";
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_zScore2.tif|";
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_FireS.tif|";
			}
		}

		msg += outputDS.CreateImage(filePath, options);
	}

	if (msg && m_options.m_bDebug)
	{
		if (!m_options.m_bQuiet )
			cout << "Open debug images " << endl;
		//cout << "Open output images " << " x(" << m_options.m_extents.m_xSize << " C x " << m_options.m_extents.m_ySize << " R x " << m_options.m_nbBands << " bands) with " << m_options.m_CPU << " threads..." << endl;

		string filePath = m_options.m_filesPath[CFireSeverityOption::OUTPUT_FILE_PATH];
		SetFileTitle(filePath, GetFileTitle(filePath) + "_debug");
		CFireSeverityOption options = m_options;
		options.m_nbBands = NB_DEBUGS * fireDS.GetRasterCount();

		for (size_t zz = 0; zz < fireDS.GetRasterCount(); zz++)
		{
			string title = GetFileTitle(fireDS.GetInternalName(zz));
			if (title.length() > 4)
			{
				string sYear = title.substr(title.size() - 4);
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_nbMissing.tif|";
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_dNBR0.tif|";
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_offset.tif|";
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_T1_B3.tif|";
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_T1_B4.tif|";
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_T1_B5.tif|";
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_T1_B7.tif|";
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_T3_B3.tif|";
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_T3_B4.tif|";
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_T3_B5.tif|";
				options.m_VRTBandsName += GetFileTitle(filePath) + "_" + sYear + "_T3_B7.tif|";
			}
		}

		msg += debugDS.CreateImage(filePath, options);
	}

	return msg;
}

ERMsg CFireSeverity::Execute()
{
	ERMsg msg;

	if (!m_options.m_bQuiet)
	{
		cout << "Output: " << m_options.m_filesPath[CFireSeverityOption::OUTPUT_FILE_PATH] << endl;
		cout << "From:   " << m_options.m_filesPath[CFireSeverityOption::LANDSAT_FILE_PATH] << endl;
		cout << "Fires:  " << m_options.m_filesPath[CFireSeverityOption::FIRE_FILE_PATH] << endl;

		if (!m_options.m_maskName.empty())
			cout << "Mask:   " << m_options.m_maskName << endl;
	}

	GDALAllRegister();

	CLandsatDataset inputDS;
	CGDALDatasetEx maskDS;
	CGDALDatasetEx outputDS;
	CGDALDatasetEx fireDS;
	CGDALDatasetEx debugDS;

	msg = OpenAll(inputDS, maskDS, fireDS, outputDS, debugDS);

	FireBitset fires;
	vector <bool> bHaveFire;
	if (msg)
	{
		msg += LoadFires(inputDS, fireDS, fires, bHaveFire);
	}

	if (msg)
	{
		PROCESS_MEMORY_COUNTERS memCounter;
		if (!m_options.m_bQuiet)
		{
			GetProcessMemoryInfo(GetCurrentProcess(), &memCounter, sizeof(memCounter));
			cout << "Memory used: " << memCounter.WorkingSetSize / (1024 * 1024) << " Mo" << endl;
		}

		//size_t nbScenedProcess = m_options.m_scenesTreated[1] - m_options.m_scenesTreated[0] + 1;
		//size_t nbScenedLoaded = m_options.m_scenesLoaded[1] - m_options.m_scenesLoaded[0] + 1;

		CBandsHolderMT bandHolder(1, m_options.m_memoryLimit, m_options.m_IOCPU, m_options.m_BLOCK_THREADS);

		if (maskDS.IsOpen())
			bandHolder.SetMask(maskDS.GetSingleBandHolder(), m_options.m_maskDataUsed);

		msg += bandHolder.Load(inputDS, m_options.m_bQuiet, m_options.m_extents, m_options.m_period);

		if (!msg)
			return msg;

		CGeoExtents extents = bandHolder.GetExtents();
		vector<pair<int, int>> XYindex = extents.GetBlockList();
		size_t nbPixels = extents.m_xSize*extents.m_ySize;

		BufferStat bufferStat(fires.size());
		if (m_options.m_buffer > 0)
		{
			if (!m_options.m_bQuiet)
				cout << "Compute buffer statistics with " << m_options.m_BLOCK_THREADS << " block threads (" << m_options.BLOCK_CPU() << " threads/block)" << endl;

			m_options.ResetBar((size_t)fires.size()*nbPixels);

			//pass 1 : find dates
			omp_set_nested(1);
#pragma omp parallel for schedule(static, 1) num_threads(m_options.m_BLOCK_THREADS) if (m_options.m_bMulti)
			for (__int64 b = 0; b < (__int64)XYindex.size(); b++)
			{
				size_t thread = omp_get_thread_num();
				size_t xBlock = XYindex[b].first;
				size_t yBlock = XYindex[b].second;

				if (bHaveFire[b])//there is some fire in this block?
				{
					ReadBlock(xBlock, yBlock, bandHolder[thread]);
					ComputeBufferStat(xBlock, yBlock, bandHolder[thread], fires, bufferStat);
				}
				else
				{
					CGeoSize blockSize = extents.GetBlockSize((int)xBlock, (int)yBlock);
#pragma omp atomic
					m_options.m_xx += fires.size()* blockSize.m_x*blockSize.m_y;
					m_options.UpdateBar();
				}
			}//for all blocks
		}

		//pass 2 : compute dNBR and fire severity
		if (!m_options.m_bQuiet)
			cout << "Compute fire severity with " << m_options.m_BLOCK_THREADS << " block threads (" << m_options.BLOCK_CPU() << " threads/block)" << endl;

		m_options.ResetBar((size_t)fires.size()*nbPixels);

		omp_set_nested(1);
#pragma omp parallel for schedule(static, 1) num_threads(m_options.m_BLOCK_THREADS) if (m_options.m_bMulti)
		for (__int64 b = 0; b < (__int64)XYindex.size(); b++)
		{
			size_t thread = omp_get_thread_num();
			size_t xBlock = XYindex[b].first;
			size_t yBlock = XYindex[b].second;

			if (bHaveFire[b])//there is some fire in this block?
			{
				OutputData output;
				DebugData debug;
				ReadBlock(xBlock, yBlock, bandHolder[thread]);
				ProcessBlock(xBlock, yBlock, bandHolder[thread], fires, bufferStat, output, debug);
				WriteBlock(xBlock, yBlock, output, debug, outputDS, debugDS);
			}
			else
			{
				CGeoSize blockSize = extents.GetBlockSize((int)xBlock, (int)yBlock);
#pragma omp atomic
				m_options.m_xx += fires.size()* blockSize.m_x*blockSize.m_y;
				m_options.UpdateBar();
			}
		}//for all blocks

		//close inputs and outputs
		CloseAll(inputDS, maskDS, fireDS, outputDS, debugDS);

		GetProcessMemoryInfo(GetCurrentProcess(), &memCounter, sizeof(memCounter));
		cout << "Memory used: " << memCounter.WorkingSetSize / (1024 * 1024) << " Mo" << endl;
	}

	return msg;
}

void CFireSeverity::ReadBlock(size_t xBlock, size_t yBlock, CBandsHolder& bandHolder)
{
#pragma omp critical(ReadBlockIO)
	{
		m_options.m_timerRead.Start();
		bandHolder.LoadBlock((int)xBlock, (int)yBlock);
		m_options.m_timerRead.Stop();
	}
}

ERMsg CFireSeverity::LoadFires(CLandsatDataset& lansatDS, CGDALDatasetEx& fireDS, FireBitset& fires, std::vector <bool>& bHaveFire)
{
	ERMsg msg;

	if (msg)
	{
		CBandsHolderMT bandHolder = CBandsHolderMT(1, m_options.m_memoryLimit, m_options.m_IOCPU, 2);
		msg += bandHolder.Load(fireDS, m_options.m_bQuiet, m_options.m_extents, m_options.m_period);

		if (msg)
		{
			CGeoExtents extents = bandHolder.GetExtents();

			fires.resize(bandHolder.GetRasterCount());
			for (size_t i = 0; i < fires.size(); i++)
			{
				int year = GetYear(fireDS.GetInternalName(i));
				if (year != -999)
				{
					fires[i].first[T_JD] = CBaseOptions::GetTRefIndex(CBaseOptions::JDAY1970, CTRef(year, JANUARY, DAY_01));
					fires[i].first[T_Z] = FindLayerIndex(lansatDS, year);
					if (fires[i].first[T_Z] < lansatDS.GetNbScenes())
					{
						fires[i].second[T_FIRE].resize((size_t)extents.m_xSize*extents.m_ySize);
						if (m_options.m_buffer > 0)
							fires[i].second[T_BUFFER].resize((size_t)extents.m_xSize*extents.m_ySize);
					}
					else
					{
						msg.ajoute("Unable to find year (" + to_string(year) + ") in the input image");
					}
				}
				else
				{
					msg.ajoute("Error: file name of fire file must end with 4 digits year (YYYY)");
				}
			}

			if (msg)
			{
				vector<pair<int, int>> XYindex = extents.GetBlockList();
				bHaveFire.resize(XYindex.size(), false);
				if (!m_options.m_bQuiet)
					cout << "Load fires with " << m_options.m_CPU << " threads" << endl;

				size_t nbPixels = extents.m_xSize*extents.m_ySize;
				m_options.ResetBar((size_t)fires.size()*nbPixels * 2);

				CTimer timerRead(true);
				//pass 1 : load fire pixel
				omp_set_nested(1);
#pragma omp parallel for schedule(static, 1) num_threads(2) if (m_options.m_bMulti)
				for (__int64 b = 0; b < (__int64)XYindex.size(); b++)
				{

					size_t thread = omp_get_thread_num();
					size_t xBlock = XYindex[b].first;
					size_t yBlock = XYindex[b].second;
					CGeoRectIndex index = extents.GetBlockRect((int)xBlock, (int)yBlock);
					CGeoSize blockSize = extents.GetBlockSize((int)xBlock, (int)yBlock);

#pragma omp critical(LOAD_FIRES)
					{
						bandHolder[thread].LoadBlock((int)xBlock, (int)yBlock);
					}

#pragma omp critical(READ_FIRES)
					{
						if (!bandHolder[thread].IsEmpty())
						{
							bHaveFire[b] = true;
							CRasterWindow fire_window = bandHolder[thread].GetWindow();
							ASSERT(fire_window.GetNbScenes() >= fires.size());
#pragma omp parallel for num_threads(m_options.m_CPU) if (m_options.m_bMulti)
							for (__int64 zz = 0; zz < (__int64)fires.size(); zz++)
							{
								for (size_t y = 0; y < blockSize.m_y; y++)
								{
									for (size_t x = 0; x < blockSize.m_x; x++)
									{
										size_t xy2 = ((size_t)index.m_y + y)* extents.m_xSize + index.m_x + x;
										if (fire_window[zz]->IsValid((int)x, (int)y))
										{
											bool bSet = fire_window[zz]->at((int)x, (int)y) > 0;
											fires[zz].second[T_FIRE].set(xy2, bSet);
										}
									}//x
								}//y
							}//zz
						}//block not empty

#pragma omp atomic
						m_options.m_xx += fires.size()*blockSize.m_x*blockSize.m_y;
						m_options.UpdateBar();
					}//critical
				}//

				//set buffer if any
				if (m_options.m_buffer > 0)
				{
#pragma omp parallel for num_threads(m_options.m_CPU) if (m_options.m_bMulti)
					for (__int64 zz = 0; zz < (__int64)fires.size(); zz++)
					{
						for (size_t y = 0; y < extents.m_ySize; y++)
						{
							for (size_t x = 0; x < extents.m_xSize; x++)
							{
								size_t xy = y * extents.m_xSize + x;
								if (fires[zz].second[T_FIRE].test(xy))
								{
									for (size_t yy = 0; (yy < 2 * m_options.m_buffer + 1); yy++)
									{
										for (size_t xx = 0; (xx < 2 * m_options.m_buffer + 1); xx++)
										{
											size_t yyy = y + yy - m_options.m_buffer;
											size_t xxx = x + xx - m_options.m_buffer;
											if (yyy < (size_t)extents.m_ySize && xxx < (size_t)extents.m_xSize)
											{
												size_t xy2 = yyy * extents.m_xSize + xxx;
												if (!fires[zz].second[T_FIRE].test(xy2))
													fires[zz].second[T_BUFFER].set(xy2, true);
											}
										}//xx
									}//yy
								}//if fire
							}//x
#pragma omp atomic
							m_options.m_xx += extents.m_xSize;
							m_options.UpdateBar();

						}//y
					}//zz
				}
				else
				{
					m_options.m_xx += fires.size()*nbPixels;
					m_options.UpdateBar();
				}

				timerRead.Stop();

				if (!m_options.m_bQuiet)
				{
					cout << endl << "Time to load fires = " << SecondToDHMS(timerRead.Elapsed()) << endl;
					cout << "Fire pixels " << endl;
					for (__int64 zz = 0; zz < (__int64)fires.size(); zz++)
						cout << zz + 1 << ":" << fires[zz].second[T_FIRE].count() << " (" << fires[zz].second[T_BUFFER].count() << ")" << endl;
				}

			}//if msg
		}//if msg
	}//if msg

	return msg;
}

size_t CFireSeverity::FindLayerIndex(CLandsatDataset& lansatDS, int year)
{
	size_t index = NOT_INIT;
	const std::vector<CTPeriod>& periods = lansatDS.GetScenePeriod();
	for (size_t z = 0; z < periods.size() && index == NOT_INIT; z++)
	{
		if (periods[z].GetFirstYear() == year)
			index = z;
	}

	return index;
}

int CFireSeverity::GetYear(const std::string& name)
{
	int year = -999;

	string title = GetFileTitle(name);
	if (title.length() > 4)
	{
		string sYear = title.substr(title.length() - 4);
		year = stoi(sYear);
	}

	return year;
}

size_t CFireSeverity::GetPrevious(size_t z, size_t x, size_t y, const CLandsatWindow& window)
{
	size_t previous = NOT_INIT;
	for (size_t zz = z - 1; zz < window.GetNbScenes() && previous == NOT_INIT; zz--)
		if (window.GetPixel(zz, (int)x, (int)y).IsValid())
			previous = zz;

	return previous;
}

size_t CFireSeverity::GetNext(size_t z, size_t x, size_t y, const CLandsatWindow& window)
{
	size_t next = NOT_INIT;
	for (size_t zz = z + 1; zz < window.GetNbScenes() && next == NOT_INIT; zz++)
		if (window.GetPixel(zz, (int)x, (int)y).IsValid())
			next = zz;

	return next;
}

void CFireSeverity::ComputeBufferStat(size_t xBlock, size_t yBlock, const CBandsHolder& bandHolder, const FireBitset& fires, BufferStat& bufferStat)
{
	size_t nbScenesLoaded = m_options.m_scenesLoaded[1] - m_options.m_scenesLoaded[0] + 1;
	size_t nbScenesProcess = m_options.m_scenesTreated[1] - m_options.m_scenesTreated[0] + 1;
	size_t nbScenes = bandHolder.GetNbScenes();
	size_t sceneSize = bandHolder.GetSceneSize();
	CGeoExtents extents = bandHolder.GetExtents();
	CGeoRectIndex index = extents.GetBlockRect((int)xBlock, (int)yBlock);
	CGeoSize blockSize = extents.GetBlockSize((int)xBlock, (int)yBlock);

	ASSERT(!bandHolder.IsEmpty());

	CLandsatWindow window = bandHolder.GetWindow();

	//compute offset when buffer
#pragma omp parallel for num_threads( m_options.BLOCK_CPU()) if (m_options.m_bMulti)
	for (__int64 zz = 0; zz < (__int64)fires.size(); zz++)
	{
		size_t z = fires[zz].first[T_Z];
		__int16 JD_base = __int16(fires[zz].first[T_JD]);
		//find dates
		for (size_t y = 0; y < blockSize.m_y; y++)
		{
			for (size_t x = 0; x < blockSize.m_x; x++)
			{
				size_t xy = y * blockSize.m_x + x;
				size_t xy2 = ((size_t)index.m_y + y)* extents.m_xSize + index.m_x + x;

				if (fires[zz].second[T_BUFFER].test(xy2))
				{
					size_t z0 = GetPrevious(z, x, y, window);
					size_t z2 = GetNext(z, x, y, window);
					if (z0 != NOT_INIT && z2 != NOT_INIT)
					{
						array <CLandsatPixel, 2> p;
						p[0] = window.GetPixel(z0, (int)x, (int)y);
						p[1] = window.GetPixel(z2, (int)x, (int)y);

						//buffer is variable that are share over many block at the same time
						//this variable must be protect when writing
#pragma omp critical(BUFFER_STAT)
						{
							pair<__int16, __int16> date = make_pair(p[0][I_JD], p[1][I_JD]);
							double dNBR = GetDeltaNBR(JD_base, p);
							bufferStat[zz][date] += dNBR;
						}
					}//if buffer
				}//is init
			}//x
		}//y

#pragma omp atomic
		m_options.m_xx += (size_t)blockSize.m_x*blockSize.m_y;

		m_options.UpdateBar();

	}//zz

}

//Get input image reference
void CFireSeverity::ProcessBlock(size_t xBlock, size_t yBlock, const CBandsHolder& bandHolder, const FireBitset& fires, const BufferStat& bufferStat, OutputData& output, DebugData& debug)
{
	size_t nbScenesLoaded = m_options.m_scenesLoaded[1] - m_options.m_scenesLoaded[0] + 1;
	size_t nbScenesProcess = m_options.m_scenesTreated[1] - m_options.m_scenesTreated[0] + 1;
	size_t nbScenes = bandHolder.GetNbScenes();
	size_t sceneSize = bandHolder.GetSceneSize();
	CGeoExtents extents = bandHolder.GetExtents();
	CGeoRectIndex index = extents.GetBlockRect((int)xBlock, (int)yBlock);
	CGeoSize blockSize = extents.GetBlockSize((int)xBlock, (int)yBlock);

	if (bandHolder.IsEmpty())
	{
#pragma omp atomic
		m_options.m_xx += (nbScenesProcess + nbScenesLoaded)* blockSize.m_x*blockSize.m_y;
		m_options.UpdateBar();

		return;
	}

	//allocate process memory and load data
	CLandsatWindow window = bandHolder.GetWindow();

	__int16 noData = (__int16)::GetDefaultNoData(GDT_Int16);
	output.resize(fires.size());
	for (size_t i = 0; i < output.size(); i++)
	{
		for (size_t j = 0; j < output[i].size(); j++)
			output[i][j].resize(blockSize.m_x*blockSize.m_y, noData);
	}

	if (m_options.m_bDebug)
	{

		__int16 noData = (__int16)::GetDefaultNoData(GDT_Int16);
		debug.resize(fires.size());
		for (size_t i = 0; i < debug.size(); i++)
		{
			for (size_t j = 0; j < debug[i].size(); j++)
				debug[i][j].resize(blockSize.m_x*blockSize.m_y, noData);
		}
	}

	//compute dNBR and apply offset when buffer
#pragma omp parallel for num_threads( m_options.BLOCK_CPU()) if (m_options.m_bMulti)
	for (__int64 zz = 0; zz < (__int64)fires.size(); zz++)
	{
		size_t z = fires[zz].first[T_Z];
		__int16 JD_base = __int16(fires[zz].first[T_JD]);

		//find dates
		for (size_t y = 0; y < blockSize.m_y; y++)
		{
			for (size_t x = 0; x < blockSize.m_x; x++)
			{
				size_t xy = y * blockSize.m_x + x;
				size_t xy2 = ((size_t)index.m_y + y)* extents.m_xSize + index.m_x + x;

				if (fires[zz].second[T_FIRE].test(xy2))
				{
					size_t z0 = GetPrevious(z, x, y, window);
					size_t z2 = GetNext(z, x, y, window);
					if (z0 != NOT_INIT && z2 != NOT_INIT)
					{
						array <CLandsatPixel, 2> p;
						p[0] = window.GetPixel(z0, (int)x, (int)y);
						p[1] = window.GetPixel(z2, (int)x, (int)y);

						pair<__int16, __int16> date = make_pair(p[0][I_JD], p[1][I_JD]);
						auto it = bufferStat[zz].find(date);
						__int16 offset = 0;
						if (it != bufferStat[zz].end())
							offset = __int16(Round(it->second[MEAN]));

						__int16 dNBR0 = GetDeltaNBR(JD_base, p);
						array<__int16, 2> Zscore = GetZscore(p);
						output[zz][O_DNBR][xy] = dNBR0 - offset;
						output[zz][O_ZSCORE1][xy] = Zscore[0];
						output[zz][O_ZSCORE2][xy] = Zscore[1];
						output[zz][O_FIRE_SEV][xy] = GetFireSeverity(dNBR0 - offset);

						if (!debug.empty())
						{
							debug[zz][D_NB_MISSING][xy] = GetNbMissing(JD_base, p);
							debug[zz][D_DNBR0][xy] = dNBR0;
							debug[zz][D_OFFSET][xy] = offset;
							debug[zz][D_T1_B3][xy] = p[0][I_B3];
							debug[zz][D_T1_B4][xy] = p[0][I_B4];
							debug[zz][D_T1_B5][xy] = p[0][I_B5];
							debug[zz][D_T1_B7][xy] = p[0][I_B7];
							debug[zz][D_T3_B3][xy] = p[1][I_B3];
							debug[zz][D_T3_B4][xy] = p[1][I_B4];
							debug[zz][D_T3_B5][xy] = p[1][I_B5];
							debug[zz][D_T3_B7][xy] = p[1][I_B7];
						}

					}
				}
			}//x
		}//y

#pragma omp atomic
		m_options.m_xx += (size_t)blockSize.m_x*blockSize.m_y;

		m_options.UpdateBar();

	}//zz
}

__int16 CFireSeverity::GetFireSeverity(__int16 dNBR)
{
	static const double F[2][2] =
	{
		{ 0.22, 0.311 },//Ron
		{ 0.09, 0.019 }//Jo
	};

	double fs = (double)Round(100.0 * ((double)dNBR / (F[1][0] * dNBR + F[1][1])));
	return __int16(max(-32767.0, min(32767.0, fs)));
}

array<__int16, 2> CFireSeverity::GetZscore(const array <CLandsatPixel, 2>& p)
{
	static const double F[2][4][2] =
	{
		{ {278.4841,121.1584},{1839.0676,365.3447},{920.9541,332.3372},{435.8769,195.7674 } },//pre-salvage
		{ {676.3447,676.3447},{1456.5572,253.4981},{2316.8939,2316.8939},{1855.5038,349.1989 } }//salvage
	};

	static const size_t B[4] = { I_B3, I_B4, I_B5, I_B7 };

	double Zsore[2] = { 0 };
	for (int f = 0; f < 2; f++)
	{
		for (int i = 0; i < 4; i++)
		{
			Zsore[f] += 10*Square((p[1][B[i]] - F[f][i][0]) / F[f][i][1]) / 4.0;
		}
	}

	//array<__int16, 2>
	return { {__int16(Zsore[0]), __int16(Zsore[1])} };
	//return (Zsore[0]<15&& Zsore[1]<10)?1:0;
}

__int16 CFireSeverity::GetNbMissing(__int16 JD_base, array <CLandsatPixel, 2> p)
{
	int nbDays = p[1][I_JD] - JD_base;
	ASSERT(nbDays >= 365); // at least the next year

	__int16 nbMissing = __int16(max(0, nbDays-365)/365);

	return nbMissing;
}

__int16 CFireSeverity::GetDeltaNBR(__int16 JD_base, array <CLandsatPixel, 2> p)
{
	//CTRef test1 = CBaseOptions::GetTRef(CBaseOptions::JDAY1970, JD_base);
	//CTRef test2 = CBaseOptions::GetTRef(CBaseOptions::JDAY1970, p[0][I_JD]);
	//CTRef test3 = CBaseOptions::GetTRef(CBaseOptions::JDAY1970, p[1][I_JD]);
	//int nbDays = p[1][I_JD] - JD_base;
	//ASSERT(nbDays >= 365); // at least the next year

	static const double F[2][4][2] =
	{
		//B3 B4 B5 B7
		{ {0.66, 138.87}, { 0.65,163.13 }, { 0.66,453.22 }, { 0.70,434.00 }},//T3 is missing
		{{0.60,156.54},{0.57,195.84},{0.60,506.01},{0.71,447.77}}//T3 and T4 is missing
	};

	__int16 nbMissing = GetNbMissing(JD_base, p);

	//make correction if missing year
	//if (nbDays > 2 * 365)
	if (nbMissing>0)
	{
		static const size_t B[4] = { B3, B4, B5, B7 };
		//size_t f = (nbDays < 3 * 365) ? 0 : 1;
		size_t f = nbMissing == 1 ? 0 : 1;

		for (int i = 0; i < 4; i++)
		{
			p[1][B[i]] = __int16(p[1][B[i]] * F[f][i][0] + F[f][i][1]);
		}
	}

	return __int16(p[0][I_NBR] - p[1][I_NBR]);
}

void CFireSeverity::WriteBlock(size_t xBlock, size_t yBlock, OutputData& output, DebugData& debug, CGDALDatasetEx& outputDS, CGDALDatasetEx& debugDS)
{

#pragma omp critical(WriteBlockIO)
	{

		m_options.m_timerWrite.Start();

		CGeoExtents extents = outputDS.GetExtents();
		CGeoSize blockSize = extents.GetBlockSize((int)xBlock, (int)yBlock);
		CGeoRectIndex outputRect = extents.GetBlockRect((int)xBlock, (int)yBlock);

		ASSERT(outputRect.Width() == blockSize.m_x);
		ASSERT(outputRect.Height() == blockSize.m_y);

		if (m_options.m_bCreateImage)
		{
			__int16 noData = (__int16)::GetDefaultNoData(GDT_Int16);
			for (size_t z = 0; z < outputDS.GetRasterCount() / NB_OUTPUTS; z++)
			{
				for (size_t i = 0; i < NB_OUTPUTS; i++)
				{
					size_t b = z * NB_OUTPUTS + i;
					ASSERT(output.empty() || output[z][i].size() == outputRect.Width()*outputRect.Height());

					GDALRasterBand *pBand = outputDS.GetRasterBand(b);
					if (!output.empty())
						pBand->RasterIO(GF_Write, outputRect.m_x, outputRect.m_y, outputRect.Width(), outputRect.Height(), &(output[z][i][0]), outputRect.Width(), outputRect.Height(), GDT_Int16, 0, 0);
					else
						pBand->RasterIO(GF_Write, outputRect.m_x, outputRect.m_y, outputRect.Width(), outputRect.Height(), &(noData), 1, 1, GDT_Int16, 0, 0);
				}
			}
		}

		if (m_options.m_bDebug)
		{
			__int16 noData = (__int16)::GetDefaultNoData(GDT_Int16);
			for (size_t z = 0; z < debugDS.GetRasterCount() / NB_DEBUGS; z++)
			{
				for (size_t i = 0; i < NB_DEBUGS; i++)
				{
					size_t b = z * NB_DEBUGS + i;
					ASSERT(debug.empty() || debug[z][i].size() == outputRect.Width()*outputRect.Height());

					GDALRasterBand *pBand = debugDS.GetRasterBand(b);
					if (!debug.empty())
						pBand->RasterIO(GF_Write, outputRect.m_x, outputRect.m_y, outputRect.Width(), outputRect.Height(), &(debug[z][i][0]), outputRect.Width(), outputRect.Height(), GDT_Int16, 0, 0);
					else
						pBand->RasterIO(GF_Write, outputRect.m_x, outputRect.m_y, outputRect.Width(), outputRect.Height(), &(noData), 1, 1, GDT_Int16, 0, 0);
				}
			}
		}

		m_options.m_timerWrite.Stop();
	}

}

void CFireSeverity::CloseAll(CGDALDatasetEx& landsatDS, CGDALDatasetEx& maskDS, CGDALDatasetEx& fireDS, CGDALDatasetEx& outputDS, CGDALDatasetEx& debugDS)
{
	if (!m_options.m_bQuiet)
		_tprintf("\nClose all files...\n");

	landsatDS.Close();
	maskDS.Close();
	fireDS.Close();

	m_options.m_timerWrite.Start();
	outputDS.Close(m_options);
	debugDS.Close(m_options);

	m_options.m_timerWrite.Stop();
	m_options.PrintTime();
}

void CFireSeverity::LoadData(const CBandsHolder& bandHolder, LansatData& data)
{
	CGeoExtents extents = bandHolder.GetExtents();
	CLandsatWindow window = bandHolder.GetWindow();
	size_t nbScenesLoaded = m_options.m_scenesLoaded[1] - m_options.m_scenesLoaded[0] + 1;

	CGeoSize blockSize = window.GetGeoSize();
	data.resize(blockSize.m_x*blockSize.m_y);

	for (size_t y = 0; y < blockSize.m_y; y++)
		for (size_t x = 0; x < blockSize.m_x; x++)
			data[y*blockSize.m_x + x].resize(nbScenesLoaded);

#pragma omp parallel for num_threads(m_options.BLOCK_CPU()) if (m_options.m_bMulti)
	for (int zz = 0; zz < (int)nbScenesLoaded; zz++)
	{
		size_t z = m_options.m_scenesLoaded[0] + zz;
		for (size_t y = 0; y < blockSize.m_y; y++)
		{
			for (size_t x = 0; x < blockSize.m_x; x++)
			{
				data[y*blockSize.m_x + x][zz] = window.GetPixel(z, (int)x, (int)y);
			}
		}

		//#pragma omp atomic
			//	m_options.m_xx += (size_t)blockSize.m_x*blockSize.m_y;
				//m_options.UpdateBar();

	}
}

string CFireSeverity::GetScenesDateFormat(const std::vector<CTPeriod>& p)
{
	string str;

	set<size_t> stat;
	for (size_t z = 0; z < p.size(); z++)
		stat.insert(p[z].size());

	if (*stat.rbegin() == 1)
		str = "%F";  //Individual input
	else //if (stat[HIGHEST] <= 366)
		str = "%Y";	//annual input
	//else
		//str = "%1-%2", "%Y");	//multi annual input

	return str;
}


// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2006-2018 Tiago de Paula Peixoto <tiago@skewed.de>
//
// 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 "graph_filtering.hh"
#include "graph_python_interface.hh"

#include <boost/python.hpp>
#include <boost/graph/dijkstra_shortest_paths_no_color_map.hpp>

#include "graph.hh"
#include "graph_selectors.hh"
#include "graph_util.hh"

#include "coroutine.hh"
#include "graph_python_interface.hh"

using namespace std;
using namespace boost;
using namespace graph_tool;

class DJKVisitorWrapper
{
public:
    DJKVisitorWrapper(GraphInterface& gi, python::object vis)
        : _gi(gi), _vis(vis) {}

    template <class Vertex, class Graph>
    void initialize_vertex(Vertex u, Graph& g)
    {
        auto gp = retrieve_graph_view<Graph>(_gi, g);
        _vis.attr("initialize_vertex")(PythonVertex<Graph>(gp, u));
    }

    template <class Vertex, class Graph>
    void discover_vertex(Vertex u, Graph& g)
    {
        auto gp = retrieve_graph_view<Graph>(_gi, g);
        _vis.attr("discover_vertex")(PythonVertex<Graph>(gp, u));
    }

    template <class Vertex, class Graph>
    void examine_vertex(Vertex u, Graph& g)
    {
        auto gp = retrieve_graph_view<Graph>(_gi, g);
        _vis.attr("examine_vertex")(PythonVertex<Graph>(gp, u));
    }

    template <class Edge, class Graph>
    void examine_edge(Edge e, Graph& g)
    {
        auto gp = retrieve_graph_view<Graph>(_gi, g);
        _vis.attr("examine_edge")(PythonEdge<Graph>(gp, e));
    }

    template <class Edge, class Graph>
    void edge_relaxed(Edge e, Graph& g)
    {
        auto gp = retrieve_graph_view<Graph>(_gi, g);
        _vis.attr("edge_relaxed")(PythonEdge<Graph>(gp, e));
    }

    template <class Edge, class Graph>
    void edge_not_relaxed(Edge e, Graph& g)
    {
        auto gp = retrieve_graph_view<Graph>(_gi, g);
        _vis.attr("edge_not_relaxed")(PythonEdge<Graph>(gp, e));
    }

    template <class Vertex, class Graph>
    void finish_vertex(Vertex u, Graph& g)
    {
        auto gp = retrieve_graph_view<Graph>(_gi, g);
        _vis.attr("finish_vertex")(PythonVertex<Graph>(gp, u));
    }

private:
    GraphInterface& _gi;
    boost::python::object _vis;
};

class DJKCmp
{
public:
    DJKCmp() {}
    DJKCmp(python::object cmp): _cmp(cmp) {}

    template <class Value1, class Value2>
    bool operator()(const Value1& v1, const Value2& v2) const
    {
        return python::extract<bool>(_cmp(v1, v2));
    }

private:
    python::object _cmp;
};

class DJKCmb
{
public:
    DJKCmb() {}
    DJKCmb(python::object cmb): _cmb(cmb) {}

    template <class Value1, class Value2 >
    Value1 operator()(const Value1& v1, const Value2& v2) const
    {
        return python::extract<Value1>(_cmb(v1, v2));
    }

private:
    python::object _cmb;
};

struct do_djk_search
{
    template <class Graph, class DistanceMap, class PredMap, class Visitor>
    void operator()(const Graph& g, size_t s, DistanceMap dist,
                    PredMap pred_map, boost::any aweight,
                    Visitor vis, const DJKCmp& cmp, const DJKCmb& cmb,
                    pair<python::object, python::object> range) const
    {
        typedef typename property_traits<DistanceMap>::value_type dtype_t;
        dtype_t z = python::extract<dtype_t>(range.first);
        dtype_t i = python::extract<dtype_t>(range.second);
        typedef typename graph_traits<Graph>::edge_descriptor edge_t;
        DynamicPropertyMapWrap<dtype_t, edge_t> weight(aweight,
                                                       edge_properties());

        if (vertex(s, g) == graph_traits<Graph>::null_vertex())
        {
            for (auto u : vertices_range(g))
            {
                vis.initialize_vertex(u, g);
                put(dist, u, i);
                put(pred_map, u, u);
            }
            for (auto u : vertices_range(g))
            {
                if (dist[u] != i)
                    continue;
                dist[u] = z;
                dijkstra_shortest_paths_no_color_map_no_init
                    (g, u, pred_map, dist, weight, get(vertex_index_t(), g),
                     cmp, cmb, i, z, vis);
            }
        }
        else
        {
            dijkstra_shortest_paths_no_color_map
                (g, vertex(s, g), visitor(vis).weight_map(weight).
                 predecessor_map(pred_map).
                 distance_map(dist).distance_compare(cmp).
                 distance_combine(cmb).distance_inf(i).distance_zero(z));
        }
    }
};

struct do_djk_search_fast
{
    template <class Graph, class DistanceMap, class WeightMap, class Visitor>
    void operator()(const Graph& g, size_t s, DistanceMap dist,
                    WeightMap weight, Visitor vis,
                    pair<python::object, python::object> range) const
    {
        typedef typename property_traits<DistanceMap>::value_type dtype_t;
        dtype_t z = python::extract<dtype_t>(range.first);
        dtype_t i = python::extract<dtype_t>(range.second);

        if (vertex(s, g) == graph_traits<Graph>::null_vertex())
        {
            for (auto u : vertices_range(g))
            {
                vis.initialize_vertex(u, g);
                put(dist, u, i);
            }
            for (auto u : vertices_range(g))
            {
                if (dist[u] != i)
                    continue;
                dist[u] = z;
                dijkstra_shortest_paths_no_color_map_no_init
                    (g, u, dummy_property_map(), dist, weight,
                     get(vertex_index_t(), g), std::less<dtype_t>(),
                     boost::closed_plus<dtype_t>(), i, z, vis);
            }
        }
        else
        {
            dijkstra_shortest_paths_no_color_map
                (g, vertex(s, g), visitor(vis).weight_map(weight).
                 distance_map(dist).distance_inf(i).distance_zero(z));
        }
    }
};

void dijkstra_search(GraphInterface& g, size_t source, boost::any dist_map,
                     boost::any pred_map, boost::any weight, python::object vis,
                     python::object cmp, python::object cmb,
                     python::object zero, python::object inf)
{
    typedef typename property_map_type::
        apply<int64_t, GraphInterface::vertex_index_map_t>::type pred_t;
    pred_t pred = any_cast<pred_t>(pred_map);
    run_action<graph_tool::all_graph_views, mpl::true_>()
        (g, std::bind(do_djk_search(), std::placeholders::_1, source,
                      std::placeholders::_2, pred, weight,
                      DJKVisitorWrapper(g, vis), DJKCmp(cmp), DJKCmb(cmb),
                      make_pair(zero, inf)),
         writable_vertex_properties())(dist_map);
}

#ifdef HAVE_BOOST_COROUTINE

class DJKGeneratorVisitor : public dijkstra_visitor<>
{
public:
    DJKGeneratorVisitor(GraphInterface& gi,
                        coro_t::push_type& yield)
        : _gi(gi), _yield(yield) {}

    template <class Edge, class Graph>
    void edge_relaxed(const Edge& e, Graph& g)
    {
        auto gp = retrieve_graph_view<Graph>(_gi, g);
        _yield(boost::python::object(PythonEdge<Graph>(gp, e)));
    }

private:
    GraphInterface& _gi;
    coro_t::push_type& _yield;
};

#endif // HAVE_BOOST_COROUTINE

boost::python::object dijkstra_search_generator(GraphInterface& g,
                                                size_t source,
                                                boost::any dist_map,
                                                boost::any weight,
                                                python::object cmp,
                                                python::object cmb,
                                                python::object zero,
                                                python::object inf)
{
#ifdef HAVE_BOOST_COROUTINE
    auto dispatch = [&](auto& yield)
        {
            DJKGeneratorVisitor vis(g, yield);
            run_action<graph_tool::all_graph_views, mpl::true_>()
            (g, std::bind(do_djk_search(), std::placeholders::_1, source,
                          std::placeholders::_2, dummy_property_map(), weight,
                          vis, DJKCmp(cmp), DJKCmb(cmb),
                          make_pair(zero, inf)),
             writable_vertex_properties())(dist_map);
        };
    return boost::python::object(CoroGenerator(dispatch));
#else
    throw GraphException("This functionality is not available because boost::coroutine was not found at compile-time");
#endif
}

boost::python::object dijkstra_search_generator_fast(GraphInterface& g,
                                                     size_t source,
                                                     boost::any dist_map,
                                                     boost::any weight,
                                                     python::object zero, python::object inf)
{
#ifdef HAVE_BOOST_COROUTINE
    auto dispatch = [&](auto& yield)
        {
            DJKGeneratorVisitor vis(g, yield);
            run_action<graph_tool::all_graph_views, mpl::true_>()
            (g, std::bind(do_djk_search_fast(), std::placeholders::_1, source,
                          std::placeholders::_2, std::placeholders::_3,
                          vis, make_pair(zero, inf)),
             writable_vertex_scalar_properties(),
             edge_scalar_properties())(dist_map, weight);
        };
    return boost::python::object(CoroGenerator(dispatch));
#else
    throw GraphException("This functionality is not available because boost::coroutine was not found at compile-time");
#endif
}

class DJKArrayVisitor: public dijkstra_visitor<>
{
public:
    DJKArrayVisitor(std::vector<std::array<size_t, 2>>& edges)
        : _edges(edges) {}

    template <class Edge, class Graph>
    void edge_relaxed(const Edge& e, Graph& g)
    {
        _edges.push_back({{source(e, g), target(e,g)}});
    }

private:
    std::vector<std::array<size_t, 2>>& _edges;
};

boost::python::object dijkstra_search_array(GraphInterface& g,
                                            size_t source,
                                            boost::any dist_map,
                                            boost::any weight,
                                            python::object cmp,
                                            python::object cmb,
                                            python::object zero,
                                            python::object inf)
{
    std::vector<std::array<size_t, 2>> edges;
    DJKArrayVisitor vis(edges);
    run_action<graph_tool::all_graph_views, mpl::true_>()
        (g, std::bind(do_djk_search(), std::placeholders::_1, source,
                      std::placeholders::_2, dummy_property_map(), weight,
                      vis, DJKCmp(cmp), DJKCmb(cmb),
                      make_pair(zero, inf)),
         writable_vertex_properties())(dist_map);
    return wrap_vector_owned<size_t,2>(edges);
}

boost::python::object dijkstra_search_array_fast(GraphInterface& g,
                                                 size_t source,
                                                 boost::any dist_map,
                                                 boost::any weight,
                                                 python::object zero,
                                                 python::object inf)
{
    std::vector<std::array<size_t, 2>> edges;
    DJKArrayVisitor vis(edges);
    run_action<graph_tool::all_graph_views, mpl::true_>()
        (g, std::bind(do_djk_search_fast(), std::placeholders::_1, source,
                      std::placeholders::_2, std::placeholders::_3,
                      vis, make_pair(zero, inf)),
         writable_vertex_scalar_properties(),
         edge_scalar_properties())(dist_map, weight);
    return wrap_vector_owned<size_t,2>(edges);
}

void export_dijkstra()
{
    using namespace boost::python;
    def("dijkstra_search", &dijkstra_search);
    def("dijkstra_generator", &dijkstra_search_generator);
    def("dijkstra_generator_fast", &dijkstra_search_generator_fast);
    def("dijkstra_array", &dijkstra_search_array);
    def("dijkstra_array_fast", &dijkstra_search_array_fast);
}


/***
第四章	标准库的典型内容
第一节 std::declval

（2.2）返回左值引用还是返回右值引用
decltype(DeclValRight<A>)())的返回类型 = class _nmsp1::A &
decltype(DeclValRight<A&>)())的返回类型 = class _nmsp1::A &
decltype(DeclValRight<A&&>)())的返回类型 = class _nmsp1::A &

decltype(DeclValRight<A>)())的返回类型 = class _nmsp1::A &&
decltype(DeclValRight<A&>)())的返回类型 = class _nmsp1::A &
decltype(DeclValRight<A&&>)())的返回类型 = class _nmsp1::A &&

（2.3）调用引用限定符修饰的成员函数范例

（3）推导函数返回值范例
T_F：是int (*)(int,int)类型，也就是函数指针类型
decltype(std::DeclValLeft<T_F>()   (std::DeclValLeft<U_Args>()...))：是int类型，也就是Add函数的返回类型
a)decltype(std::DeclValLeft<T_F>() )：是 int (* && )(int,int),函数指针的右值引用类型，其实就简单理解成函数指针类型
b)decltype(std::DeclValLeft<U_Args>()...)这种写法：推导出来的是两个int &&
***/

#include <iostream>
//#include <boost/type_index.hpp>
using namespace std;

namespace _nmsp2
{
	//函数类型一般是由函数的返回值和参数类型决定，与函数名没有关系，所以Add代表的函数类型是: int(int,int);
	int Add(int a, int b)
	{
		return a + b;
	}

	//std::declval<>中的U_Args写成U_Args&&或者U_Args&
	//decltype(std::declval<T_F>() (std::declval<U_Args>()...))写法的作用：根据函数类型以及函数参数类型，推导函数返回值类型。
	//decltype(T_F(U_Args...)) TestFnRtnImpl(T_F func, U_Args... args) -报错：
	//decltype后面圆括号中出现的一般都是变量、对象、表达式、函数名、函数指针名等等。
	template<typename T_F, typename... U_Args>
	decltype(std::declval<T_F>() (std::declval<U_Args>()...)) TestFnRtnImpl(T_F func, U_Args... args)
	{
		auto rtnvalue = func(args...);
		return rtnvalue;
	}

	//decltype(func(args...))：int类型，也就是Add的返回类型。
	//decltype(func)：int(*)(int,int)类型，函数指针类型
	template<typename T_F, typename... U_Args>
	auto TestFnRtnImpl2(T_F func, U_Args... args)->decltype(func(args...))
	{
		auto rtnvalue = func(args...);
		return rtnvalue;
	}

}

int main()
{
	/*_nmsp2测试*/
	//函数指针类型fp_var = int(*)(int,int)
	//函数指针的右值引用rr_fp_var = int(* &&)(int,int),现在rr_fp_var就可以代表fp_var
	int (*fp_var)(int x, int y);
	int (* &&rr_fp_var)(int x, int y) = std::move(fp_var);
	fp_var = _nmsp2::Add;
	cout << fp_var(1, 2) << endl;    //输出值为3
	cout << rr_fp_var(1, 2) << endl; //输出值为3

	auto result1 = _nmsp2::TestFnRtnImpl(_nmsp2::Add, 5, 5);                   //T_F被推断出 int(*)(int,int)
	cout << result1 << endl;

	auto result2 = _nmsp2::TestFnRtnImpl<int(int,int)>(_nmsp2::Add, 5, 7);  //T_F被推断出 int(*)(int,int)
	cout << result2 << endl;

	auto result3 = _nmsp2::TestFnRtnImpl2(_nmsp2::Add, 15, 15);
	cout << result3 << endl;

	return 0;
}


#include <NTL/vec_ZZ_p.h>

NTL_START_IMPL

static
void BasicBlockConstruct(ZZ_p* x, long n, long d)
{
   long m, j;

   long i = 0;

   NTL_SCOPE(guard) { BlockDestroy(x, i); };

   while (i < n) {
      m = ZZ_BlockConstructAlloc(x[i]._ZZ_p__rep, d, n-i);
      for (j = 1; j < m; j++)
         ZZ_BlockConstructSet(x[i]._ZZ_p__rep, x[i+j]._ZZ_p__rep, j);
      i += m;
   }

   guard.relax();
}

void BlockConstruct(ZZ_p* x, long n)
{
   if (n <= 0) return;

   if (!ZZ_pInfo)
      LogicError("ZZ_p constructor called while modulus undefined");

   long d = ZZ_p::ModulusSize();

   BasicBlockConstruct(x, n, d);
}

void BlockConstructFromVec(ZZ_p* x, long n, const ZZ_p* y)
{
   if (n <= 0) return;

   long d = y->_ZZ_p__rep.MaxAlloc() - 1;
   BasicBlockConstruct(x, n, d);

   NTL_SCOPE(guard) { BlockDestroy(x, n); };

   long i;
   for (i = 0; i < n; i++) x[i] = y[i];

   guard.relax();
}

void BlockConstructFromObj(ZZ_p* x, long n, const ZZ_p& y)
{
   if (n <= 0) return;

   if (!ZZ_pInfo)
      LogicError("ZZ_p constructor called while modulus undefined");

   long d = ZZ_p::ModulusSize();

   BasicBlockConstruct(x, n, d);

   NTL_SCOPE(guard) { BlockDestroy(x, n); };

   long i;
   for (i = 0; i < n; i++) x[i] = y;

   guard.relax();
}

void BlockDestroy(ZZ_p* x, long n)
{
   if (n <= 0) return;

   long i = 0;
   long m;

   while (i < n) {
      m = ZZ_BlockDestroy(x[i]._ZZ_p__rep);
      i += m;
   }
}

void conv(vec_ZZ_p& x, const vec_ZZ& a)
{
   long i, n;

   n = a.length();
   x.SetLength(n);

   ZZ_p* xp = x.elts();
   const ZZ* ap = a.elts();

   for (i = 0; i < n; i++)
      conv(xp[i], ap[i]);
}

void conv(vec_ZZ& x, const vec_ZZ_p& a)
{
   long n = a.length();
   x.SetLength(n);
   long i;
   for (i = 0; i < n; i++)
      x[i] = rep(a[i]);
}

void InnerProduct(ZZ_p& x, const vec_ZZ_p& a, const vec_ZZ_p& b)
{
   long n = min(a.length(), b.length());
   long i;
   NTL_ZZRegister(accum);
   NTL_ZZRegister(t);

   clear(accum);
   for (i = 0; i < n; i++) {
      mul(t, rep(a[i]), rep(b[i]));
      add(accum, accum, t);
   }

   conv(x, accum);
}

void InnerProduct(ZZ_p& x, const vec_ZZ_p& a, const vec_ZZ_p& b,
                  long offset)
{
   if (offset < 0) LogicError("InnerProduct: negative offset");
   if (NTL_OVERFLOW(offset, 1, 0))
      ResourceError("InnerProduct: offset too big");

   long n = min(a.length(), b.length()+offset);
   long i;
   NTL_ZZRegister(accum);
   NTL_ZZRegister(t);

   clear(accum);
   for (i = offset; i < n; i++) {
      mul(t, rep(a[i]), rep(b[i-offset]));
      add(accum, accum, t);
   }

   conv(x, accum);
}

void mul(vec_ZZ_p& x, const vec_ZZ_p& a, const ZZ_p& b_in)
{
   NTL_ZZ_pRegister(b);
   b = b_in;
   long n = a.length();
   x.SetLength(n);
   long i;
   for (i = 0; i < n; i++)
      mul(x[i], a[i], b);
}

void mul(vec_ZZ_p& x, const vec_ZZ_p& a, long b_in)
{
   NTL_ZZ_pRegister(b);
   b = b_in;
   long n = a.length();
   x.SetLength(n);
   long i;
   for (i = 0; i < n; i++)
      mul(x[i], a[i], b);
}

void add(vec_ZZ_p& x, const vec_ZZ_p& a, const vec_ZZ_p& b)
{
   long n = a.length();
   if (b.length() != n) LogicError("vector add: dimension mismatch");

   x.SetLength(n);
   long i;
   for (i = 0; i < n; i++)
      add(x[i], a[i], b[i]);
}

void sub(vec_ZZ_p& x, const vec_ZZ_p& a, const vec_ZZ_p& b)
{
   long n = a.length();
   if (b.length() != n) LogicError("vector sub: dimension mismatch");
   x.SetLength(n);
   long i;
   for (i = 0; i < n; i++)
      sub(x[i], a[i], b[i]);
}

void clear(vec_ZZ_p& x)
{
   long n = x.length();
   long i;
   for (i = 0; i < n; i++)
      clear(x[i]);
}

void negate(vec_ZZ_p& x, const vec_ZZ_p& a)
{
   long n = a.length();
   x.SetLength(n);
   long i;
   for (i = 0; i < n; i++)
      negate(x[i], a[i]);
}

long IsZero(const vec_ZZ_p& a)
{
   long n = a.length();
   long i;

   for (i = 0; i < n; i++)
      if (!IsZero(a[i]))
         return 0;

   return 1;
}

vec_ZZ_p operator+(const vec_ZZ_p& a, const vec_ZZ_p& b)
{
   vec_ZZ_p res;
   add(res, a, b);
   NTL_OPT_RETURN(vec_ZZ_p, res);
}

vec_ZZ_p operator-(const vec_ZZ_p& a, const vec_ZZ_p& b)
{
   vec_ZZ_p res;
   sub(res, a, b);
   NTL_OPT_RETURN(vec_ZZ_p, res);
}

vec_ZZ_p operator-(const vec_ZZ_p& a)
{
   vec_ZZ_p res;
   negate(res, a);
   NTL_OPT_RETURN(vec_ZZ_p, res);
}

ZZ_p operator*(const vec_ZZ_p& a, const vec_ZZ_p& b)
{
   ZZ_p res;
   InnerProduct(res, a, b);
   NTL_OPT_RETURN(ZZ_p, res);
}

void VectorCopy(vec_ZZ_p& x, const vec_ZZ_p& a, long n)
{
   if (n < 0) LogicError("VectorCopy: negative length");
   if (NTL_OVERFLOW(n, 1, 0)) ResourceError("overflow in VectorCopy");

   long m = min(n, a.length());

   x.SetLength(n);

   long i;

   for (i = 0; i < m; i++)
      x[i] = a[i];

   for (i = m; i < n; i++)
      clear(x[i]);
}

NTL_END_IMPL