#include "dynet/nodes.h"
#include "dynet/dynet.h"
#include "dynet/training.h"
#include "dynet/timing.h"
#include "dynet/rnn.h"
#include "dynet/gru.h"
#include "dynet/lstm.h"
#include "dynet/fast-lstm.h"
#include "dynet/dict.h"
#include "dynet/expr.h"

#include <sys/stat.h>
#include <sys/types.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <type_traits>
#include <time.h>

#include <boost/serialization/vector.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/program_options.hpp>

#include "s2s/encdec.hpp"
#include "s2s/decode.hpp"
#include "s2s/define.hpp"
#include "s2s/comp.hpp"
#include "s2s/metrics.hpp"
#include "s2s/options.hpp"

namespace s2s {

    float train_one_batch(const bool is_train, const batch& one_batch, const s2s_options &opts, const float align_w, encoder_decoder* encdec, dynet::Trainer* trainer){
        auto chrono_start = std::chrono::system_clock::now();
        unsigned int batch_size = one_batch.src.at(0).at(0).size();
        dynet::ComputationGraph cg;
        std::vector<dynet::expr::Expression> errs_att;
        std::vector<dynet::expr::Expression> errs_out;
        float loss_att = 0.0;
        float loss_out = 0.0;
        std::vector<dynet::expr::Expression> i_enc = encdec->encoder(one_batch, cg);
        std::vector<dynet::expr::Expression> i_feed = encdec->init_feed(one_batch, cg);
        for (unsigned int t = 0; t < one_batch.trg.size() - 1; ++t) {
            dynet::expr::Expression i_att_t = encdec->decoder_attention(cg, one_batch.trg[t], i_feed[t], i_enc[0]);
            if(opts.guided_alignment == true){
                for(unsigned int i = 0; i < one_batch.align.at(t).size(); i++){
                    assert(0 <= one_batch.align.at(t+1).at(i) < one_batch.src.size());
                }
                dynet::expr::Expression i_err = pickneglogsoftmax(i_att_t, one_batch.align.at(t+1));
                errs_att.push_back(i_err);
            }
            std::vector<dynet::expr::Expression> i_out_t = encdec->decoder_output(cg, i_att_t, i_enc[1]);
            i_feed.push_back(i_out_t[1]);
            dynet::expr::Expression i_err = pickneglogsoftmax(i_out_t[0], one_batch.trg[t+1]);
            errs_out.push_back(i_err);
        }
        dynet::expr::Expression i_nerr_out = sum_batches(sum(errs_out)) / (float)(batch_size);
        loss_out = as_scalar(cg.forward(i_nerr_out));
        dynet::expr::Expression i_nerr_all;
        if(opts.guided_alignment == true){
            dynet::expr::Expression i_nerr_att = sum_batches(sum(errs_att)) / (float)(batch_size);
            loss_att = as_scalar(cg.incremental_forward(i_nerr_att));
            i_nerr_all = i_nerr_out + align_w * i_nerr_att;
        }else{
            i_nerr_all = i_nerr_out;
        }
        float loss_all = as_scalar(cg.incremental_forward(i_nerr_all));
        if(is_train == true){
            cg.backward(i_nerr_all);
            //cg.print_graphviz();
            trainer->update();
        }
        auto chrono_end = std::chrono::system_clock::now();
        auto time_used = (double)std::chrono::duration_cast<std::chrono::milliseconds>(chrono_end - chrono_start).count() / (double)1000;
        std::cerr << "batch_size: " << batch_size;
        std::cerr << ",\toutput loss: " << loss_out;
        std::cerr << ",\tattention loss: " << loss_att;
        std::cerr << ",\tsource length: " << one_batch.src.size();
        std::cerr << ",\ttarget length: " << one_batch.trg.size();
        std::cerr << ",\ttime: " << time_used << " [s]" << std::endl;
        std::cerr << "[epoch=" << trainer->epoch << " eta=" << trainer->eta << " align_w=" << align_w << " clips=" << trainer->clips_since_status << " updates=" << trainer->updates_since_status << "] " << std::endl;
        return loss_out;
    }

    void train(const s2s_options &opts){
        s2s::dicts dicts;
        s2s::parallel_corpus para_corp_train;
        s2s::parallel_corpus para_corp_dev;
        dicts.set(opts);
        para_corp_train.load_src(opts.srcfile, dicts);
        para_corp_train.load_trg(opts.trgfile, dicts);
        para_corp_train.load_check();
        para_corp_dev.load_src(opts.srcvalfile, dicts);
        para_corp_dev.load_trg(opts.trgvalfile, dicts);
        para_corp_dev.load_check();
        if(opts.guided_alignment == true){
            para_corp_train.load_align(opts.alignfile);
            para_corp_train.load_check_with_align();
            para_corp_dev.load_align(opts.alignvalfile);
            para_corp_dev.load_check_with_align();
        }
        // for debug
        dicts.save(opts);
        // for debug
        dynet::Model model;
        encoder_decoder* encdec = new encoder_decoder(model, &opts);
        encdec->enable_dropout();
        dynet::Trainer* trainer = nullptr;
        if(opts.optim == "sgd"){
            trainer = new dynet::SimpleSGDTrainer(model);
        }else if(opts.optim == "momentum_sgd"){
            trainer = new dynet::MomentumSGDTrainer(model);
        }else if(opts.optim == "adagrad"){
            trainer = new dynet::AdagradTrainer(model);
        }else if(opts.optim == "adadelta"){
            trainer = new dynet::AdadeltaTrainer(model);
        }else if(opts.optim == "adam"){
            trainer = new dynet::AdamTrainer(model);
        }else{
            std::cerr << "Trainer does not exist !"<< std::endl;
            assert(false);
        }
        float learning_rate = opts.learning_rate;
        trainer->eta0 = learning_rate;
        trainer->eta = learning_rate;
        trainer->eta_decay = 0.f;
        trainer->clipping_enabled = opts.clipping_enabled;
        trainer->clip_threshold = opts.clip_threshold;
        unsigned int epoch = 0;
        float align_w = opts.guided_alignment_weight;
        float prev_loss = FLT_MAX;
        float current_loss = 0.0;
        while(epoch < opts.epochs){
            // train
            para_corp_train.sort_para_sent(opts.sort_sent_type_train, opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train);
            para_corp_train.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
            para_corp_train.shuffle_batch(opts.shuffle_batch_type_train);
            batch one_batch;
            while(para_corp_train.next_batch_para(one_batch, dicts)){
                one_batch.drop_word(dicts, opts);
                // train one batch
                train_one_batch(true, one_batch, opts, align_w, encdec, trainer);
            }
            std::cerr << std::endl;

            // dev
            std::cerr << "dev" << std::endl;
            encdec->disable_dropout();
            para_corp_dev.sort_para_sent(opts.sort_sent_type_pred, opts.max_batch_pred, opts.src_tok_lim_pred, opts.trg_tok_lim_pred);
            para_corp_dev.set_para_batch_order(opts.max_batch_pred, opts.src_tok_lim_pred, opts.trg_tok_lim_pred, opts.batch_type_pred);
            current_loss = 0.0;
            while(para_corp_dev.next_batch_para(one_batch, dicts)){
                // train one batch
                current_loss += train_one_batch(false, one_batch, opts, align_w, encdec, trainer) * (float)(one_batch.src.at(0).at(0).size());
            }
            std::cerr << "current loss: " << current_loss << ", previous_loss: " << prev_loss << std::endl;
            std::cerr << "dev_decode" << std::endl;
            para_corp_train.reset_index();
            para_corp_dev.reset_index();
            trainer->update_epoch();
            trainer->status();
            std::vector<std::string> str_sents(para_corp_dev.src.size());
            while(para_corp_dev.next_batch_para(one_batch, dicts)){
                std::vector<std::vector<unsigned int> > osent;
                if(opts.decoder_type == "greedy"){
                    s2s::greedy_decode(one_batch, osent, encdec, dicts, opts);
                }else if(opts.decoder_type == "greedy_vinyals"){
                    s2s::greedy_decode_vinyals(one_batch, osent, encdec, dicts, opts);
                }else{
                    std::cerr << "Decoder does not exist !"<< std::endl;
                    assert(false);
                }
                std::vector<std::string> str_batch_sents = s2s::print_sents(osent, dicts);
                for(unsigned int i=0; i < str_batch_sents.size(); i++){
                    // debug
                    std::cerr << one_batch.sent_id.at(i) << std::endl;
                    str_sents[one_batch.sent_id.at(i)] = str_batch_sents.at(i);
                }
            }
            std::string print_body = "";
            for(const std::string str_sent : str_sents){
                print_body += str_sent;
                print_body += "\n";
            }
            ofstream dev_sents(opts.rootdir + "/dev_" + to_string(epoch) + ".txt");
            dev_sents << print_body;
            dev_sents.close();
            para_corp_dev.reset_index();
            encdec->enable_dropout();
            // save Model
            ofstream model_out(opts.rootdir + "/" + opts.save_file + "_" + to_string(epoch) + ".model");
            boost::archive::text_oarchive model_oa(model_out);
            model_oa << model << *encdec;
            model_out.close();
            // preparation for next epoch
            epoch++;
            if(opts.lr_auto_decay == true){
                if(current_loss > prev_loss){
                    learning_rate *= opts.lr_decay;
                }
            }else{
                if(epoch >= opts.sgd_start_epoch){
                    if(epoch > opts.sgd_start_decay){
                        if((epoch - opts.sgd_start_decay) % opts.sgd_start_decay_for_each == 0){
                            learning_rate *= opts.sgd_start_lr_decay;
                        }
                    }else if(epoch == opts.sgd_start_epoch){
                        delete(trainer);
                        trainer = new dynet::SimpleSGDTrainer(model);
                        learning_rate = opts.sgd_start_learning_rate;
                        trainer->eta0 = learning_rate;
                        trainer->eta_decay = 0.f;
                        trainer->clipping_enabled = opts.clipping_enabled;
                        trainer->clip_threshold = opts.clip_threshold;
                        trainer->epoch = epoch;
                    }
                }else{
                    if(epoch >= opts.start_epoch){
                        if(epoch > opts.start_epoch){
                            if((epoch - opts.start_epoch) % opts.decay_for_each == 0){
                                learning_rate *= opts.lr_decay;
                            }
                        }else if(epoch == opts.start_epoch){
                            learning_rate *= opts.lr_decay;
                        }
                    }
                }
            }
            trainer->eta = learning_rate;
            if(opts.guided_alignment == true){
                if(epoch > opts.guided_alignment_start_epoch){
                    if((epoch - opts.guided_alignment_start_epoch) % opts.guided_alignment_decay_for_each == 0){
                        align_w *= opts.guided_alignment_decay;
                    }
                }else if(epoch == opts.guided_alignment_start_epoch){
                    align_w *= opts.guided_alignment_decay;
                }
            }
            prev_loss = current_loss;
        }
    }

    void predict(const s2s_options &opts){
        s2s::dicts dicts;
        dicts.load(opts);
        // load model
        dynet::Model model;
        encoder_decoder* encdec = new encoder_decoder(model, &opts);
        //encdec->disable_dropout();
        ifstream model_in(opts.modelfile);
        boost::archive::text_iarchive model_ia(model_in);
        model_ia >> model >> *encdec;
        model_in.close();
        // predict
        s2s::monoling_corpus mono_corp;
        mono_corp.load_src(opts.srcfile, dicts);
        batch one_batch;
        encdec->disable_dropout();
        mono_corp.sort_mono_sent(opts.sort_sent_type_pred);
        mono_corp.set_mono_batch_order(opts.max_batch_pred, opts.src_tok_lim_pred, opts.batch_type_pred);
        std::vector<std::string> str_sents(mono_corp.src.size());
        while(mono_corp.next_batch_mono(one_batch, dicts)){
            std::vector<std::vector<unsigned int> > osent;
            if(opts.decoder_type == "greedy"){
                s2s::greedy_decode(one_batch, osent, encdec, dicts, opts);
            }else if(opts.decoder_type == "greedy_vinyals"){
                s2s::greedy_decode_vinyals(one_batch, osent, encdec, dicts, opts);
            }else{
                std::cerr << "Decoder does not exist !"<< std::endl;
                assert(false);
            }
            std::vector<std::string> str_batch_sents = s2s::print_sents(osent, dicts);
            for(unsigned int i=0; i < str_batch_sents.size(); i++){
                str_sents[one_batch.sent_id.at(i)] = str_batch_sents.at(i);
            }
        }
        std::string print_body = "";
        for(const std::string str_sent : str_sents){
            print_body += str_sent;
            print_body += "\n";
        }
        ofstream pred_sents(opts.trgfile);
        pred_sents << print_body;
        pred_sents.close();
    }

};

int main(int argc, char** argv) {
    namespace po = boost::program_options;
    po::options_description bpo("h");
    s2s::s2s_options opts;
    s2s::set_s2s_options(&bpo, &opts);
    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, bpo), vm);
    po::notify(vm);
    if(vm.at("mode").as<std::string>() == "train"){
        s2s::add_s2s_options_train(&vm, &opts);
        s2s::check_s2s_options_train(&vm, opts);
        std::string file_name = opts.rootdir + "/options.txt";
        struct stat st;
        if(stat(opts.rootdir.c_str(), &st) != 0){
            mkdir(opts.rootdir.c_str(), 0775);
        }
        ofstream out(file_name);
        boost::archive::text_oarchive oa(out);
        oa << opts;
        out.close();
        dynet::initialize(argc, argv);
        s2s::train(opts);
    }else if(vm.at("mode").as<std::string>() == "predict"){
        ifstream in(opts.rootdir + "/options.txt");
        boost::archive::text_iarchive ia(in);
        ia >> opts;
        in.close();
        s2s::check_s2s_options_predict(&vm, opts);
        dynet::initialize(argc, argv);
        s2s::predict(opts);
    }else if(vm.at("mode").as<std::string>() == "test"){

    }else{
        std::cerr << "Mode does not exist !"<< std::endl;
        assert(false);
    }
}


#include "globalmap.hpp"

#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>

#include <OgreImage.h>
#include <OgreTextureManager.h>
#include <OgreColourValue.h>
#include <OgreHardwareVertexBuffer.h>
#include <OgreRoot.h>
#include <OgreHardwarePixelBuffer.h>

#include <components/loadinglistener/loadinglistener.hpp>

#include <components/esm/globalmap.hpp>

#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"

#include "../mwworld/esmstore.hpp"

namespace MWRender
{

    GlobalMap::GlobalMap(const std::string &cacheDir)
        : mCacheDir(cacheDir)
        , mMinX(0), mMaxX(0)
        , mMinY(0), mMaxY(0)
        , mWidth(0)
        , mHeight(0)
    {
        mCellSize = Settings::Manager::getInt("global map cell size", "Map");
    }

    GlobalMap::~GlobalMap()
    {
        Ogre::TextureManager::getSingleton().remove(mOverlayTexture->getName());
    }

    void GlobalMap::render (Loading::Listener* loadingListener)
    {
        Ogre::TexturePtr tex;

        const MWWorld::ESMStore &esmStore =
            MWBase::Environment::get().getWorld()->getStore();

        // get the size of the world
        MWWorld::Store<ESM::Cell>::iterator it = esmStore.get<ESM::Cell>().extBegin();
        for (; it != esmStore.get<ESM::Cell>().extEnd(); ++it)
        {
            if (it->getGridX() < mMinX)
                mMinX = it->getGridX();
            if (it->getGridX() > mMaxX)
                mMaxX = it->getGridX();
            if (it->getGridY() < mMinY)
                mMinY = it->getGridY();
            if (it->getGridY() > mMaxY)
                mMaxY = it->getGridY();
        }

        mWidth = mCellSize*(mMaxX-mMinX+1);
        mHeight = mCellSize*(mMaxY-mMinY+1);

        loadingListener->loadingOn();
        loadingListener->setLabel("Creating map");
        loadingListener->setProgressRange((mMaxX-mMinX+1) * (mMaxY-mMinY+1));
        loadingListener->setProgress(0);

        const Ogre::ColourValue waterShallowColour(0.15, 0.2, 0.19);
        const Ogre::ColourValue waterDeepColour(0.1, 0.14, 0.13);
        const Ogre::ColourValue groundColour(0.254, 0.19, 0.13);
        const Ogre::ColourValue mountainColour(0.05, 0.05, 0.05);
        const Ogre::ColourValue hillColour(0.16, 0.12, 0.08);

        //if (!boost::filesystem::exists(mCacheDir + "/GlobalMap.png"))
        if (1)
        {
            std::vector<Ogre::uchar> data (mWidth * mHeight * 3);

            for (int x = mMinX; x <= mMaxX; ++x)
            {
                for (int y = mMinY; y <= mMaxY; ++y)
                {
                    ESM::Land* land = esmStore.get<ESM::Land>().search (x,y);

                    if (land)
                    {
                        int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX;
                        if (!land->isDataLoaded(mask))
                            land->loadData(mask);
                    }

                    for (int cellY=0; cellY<mCellSize; ++cellY)
                    {
                        for (int cellX=0; cellX<mCellSize; ++cellX)
                        {
                            int vertexX = float(cellX)/float(mCellSize) * ESM::Land::LAND_SIZE;
                            int vertexY = float(cellY)/float(mCellSize) * ESM::Land::LAND_SIZE;

                            int texelX = (x-mMinX) * mCellSize + cellX;
                            int texelY = (mHeight-1) - ((y-mMinY) * mCellSize + cellY);

                            unsigned char r,g,b;

                            if (land)
                            {
                                const float landHeight = land->mLandData->mHeights[vertexY * ESM::Land::LAND_SIZE + vertexX];

                                if (landHeight >= 0)
                                {
                                    const float hillHeight = 2500.f;
                                    if (landHeight >= hillHeight)
                                    {
                                        const float mountainHeight = 15000.f;
                                        float factor = std::min(1.f, float(landHeight-hillHeight)/mountainHeight);
                                        r = (hillColour.r * (1-factor) + mountainColour.r * factor) * 255;
                                        g = (hillColour.g * (1-factor) + mountainColour.g * factor) * 255;
                                        b = (hillColour.b * (1-factor) + mountainColour.b * factor) * 255;
                                    }
                                    else
                                    {
                                        float factor = std::min(1.f, float(landHeight)/hillHeight);
                                        r = (groundColour.r * (1-factor) + hillColour.r * factor) * 255;
                                        g = (groundColour.g * (1-factor) + hillColour.g * factor) * 255;
                                        b = (groundColour.b * (1-factor) + hillColour.b * factor) * 255;
                                    }
                                }
                                else
                                {
                                    if (landHeight >= -100)
                                    {
                                        float factor = std::min(1.f, -1*landHeight/100.f);
                                        r = (((waterShallowColour+groundColour)/2).r * (1-factor) + waterShallowColour.r * factor) * 255;
                                        g = (((waterShallowColour+groundColour)/2).g * (1-factor) + waterShallowColour.g * factor) * 255;
                                        b = (((waterShallowColour+groundColour)/2).b * (1-factor) + waterShallowColour.b * factor) * 255;
                                    }
                                    else
                                    {
                                        float factor = std::min(1.f, -1*(landHeight-100)/1000.f);
                                        r = (waterShallowColour.r * (1-factor) + waterDeepColour.r * factor) * 255;
                                        g = (waterShallowColour.g * (1-factor) + waterDeepColour.g * factor) * 255;
                                        b = (waterShallowColour.b * (1-factor) + waterDeepColour.b * factor) * 255;
                                    }
                                }

                            }
                            else
                            {
                                r = waterDeepColour.r * 255;
                                g = waterDeepColour.g * 255;
                                b = waterDeepColour.b * 255;
                            }

                            data[texelY * mWidth * 3 + texelX * 3] = r;
                            data[texelY * mWidth * 3 + texelX * 3+1] = g;
                            data[texelY * mWidth * 3 + texelX * 3+2] = b;
                        }
                    }
                    loadingListener->increaseProgress(1);
                }
            }

            Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));

            tex = Ogre::TextureManager::getSingleton ().createManual ("GlobalMap.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
                Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_B8G8R8, Ogre::TU_STATIC);
            tex->loadRawData(stream, mWidth, mHeight, Ogre::PF_B8G8R8);
        }
        else
            tex = Ogre::TextureManager::getSingleton ().getByName ("GlobalMap.png");

        tex->load();

        mOverlayTexture = Ogre::TextureManager::getSingleton().createManual("GlobalMapOverlay", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
            Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_A8B8G8R8, Ogre::TU_DYNAMIC, this);

        clear();

        loadingListener->loadingOff();
    }

    void GlobalMap::worldPosToImageSpace(float x, float z, float& imageX, float& imageY)
    {
        imageX = float(x / 8192.f - mMinX) / (mMaxX - mMinX + 1);

        imageY = 1.f-float(z / 8192.f - mMinY) / (mMaxY - mMinY + 1);
    }

    void GlobalMap::cellTopLeftCornerToImageSpace(int x, int y, float& imageX, float& imageY)
    {
        imageX = float(x - mMinX) / (mMaxX - mMinX + 1);

        // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is
        imageY = 1.f-float(y - mMinY + 1) / (mMaxY - mMinY + 1);
    }

    void GlobalMap::exploreCell(int cellX, int cellY)
    {
        float originX = (cellX - mMinX) * mCellSize;
        // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is
        float originY = mHeight - (cellY+1 - mMinY) * mCellSize;

        if (cellX > mMaxX || cellX < mMinX || cellY > mMaxY || cellY < mMinY)
            return;

        Ogre::TexturePtr localMapTexture = Ogre::TextureManager::getSingleton().getByName("Cell_"
            + boost::lexical_cast<std::string>(cellX) + "_" + boost::lexical_cast<std::string>(cellY));

        if (!localMapTexture.isNull())
        {
            mOverlayTexture->load();
            mOverlayTexture->getBuffer()->blit(localMapTexture->getBuffer(), Ogre::Image::Box(0,0,512,512),
                         Ogre::Image::Box(originX,originY,originX+mCellSize,originY+mCellSize));

            Ogre::Image backup;
            std::vector<Ogre::uchar> data;
            data.resize(mCellSize*mCellSize*4, 0);
            backup.loadDynamicImage(&data[0], mCellSize, mCellSize, Ogre::PF_A8B8G8R8);

            localMapTexture->getBuffer()->blitToMemory(Ogre::Image::Box(0,0,512,512), backup.getPixelBox());

            for (int x=0; x<mCellSize; ++x)
                for (int y=0; y<mCellSize; ++y)
                {
                    assert (originX+x < mOverlayImage.getWidth());
                    assert (originY+y < mOverlayImage.getHeight());
                    assert (x < int(backup.getWidth()));
                    assert (y < int(backup.getHeight()));
                    mOverlayImage.setColourAt(backup.getColourAt(x, y, 0), originX+x, originY+y, 0);
                }
        }
    }

    void GlobalMap::clear()
    {
        Ogre::uchar* buffer =  OGRE_ALLOC_T(Ogre::uchar, mWidth * mHeight * 4, Ogre::MEMCATEGORY_GENERAL);
        memset(buffer, 0, mWidth * mHeight * 4);

        mOverlayImage.loadDynamicImage(&buffer[0], mWidth, mHeight, 1, Ogre::PF_A8B8G8R8, true); // pass ownership of buffer to image

        mOverlayTexture->load();
    }

    void GlobalMap::loadResource(Ogre::Resource *resource)
    {
        Ogre::Texture* tex = dynamic_cast<Ogre::Texture*>(resource);
        Ogre::ConstImagePtrList list;
        list.push_back(&mOverlayImage);
        tex->_loadImages(list);
    }

    void GlobalMap::write(ESM::GlobalMap& map)
    {
        map.mBounds.mMinX = mMinX;
        map.mBounds.mMaxX = mMaxX;
        map.mBounds.mMinY = mMinY;
        map.mBounds.mMaxY = mMaxY;

        Ogre::DataStreamPtr encoded = mOverlayImage.encode("png");
        map.mImageData.resize(encoded->size());
        encoded->read(&map.mImageData[0], encoded->size());
    }

    void GlobalMap::read(ESM::GlobalMap& map)
    {
        const ESM::GlobalMap::Bounds& bounds = map.mBounds;

        if (bounds.mMaxX-bounds.mMinX <= 0)
            return;
        if (bounds.mMaxY-bounds.mMinY <= 0)
            return;

        if (bounds.mMinX > bounds.mMaxX
                || bounds.mMinY > bounds.mMaxY)
            throw std::runtime_error("invalid map bounds");

        Ogre::Image image;
        Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&map.mImageData[0], map.mImageData.size()));
        image.load(stream, "png");

        int xLength = (bounds.mMaxX-bounds.mMinX+1);
        int yLength = (bounds.mMaxY-bounds.mMinY+1);

        // Size of one cell in image space
        int cellImageSizeSrc = image.getWidth() / xLength;
        if (int(image.getHeight() / yLength) != cellImageSizeSrc)
            throw std::runtime_error("cell size must be quadratic");

        // If cell bounds of the currently loaded content and the loaded savegame do not match,
        // we need to resize source/dest boxes to accommodate
        // This means nonexisting cells will be dropped silently
        int cellImageSizeDst = mCellSize;

        // Completely off-screen? -> no need to blit anything
        if (bounds.mMaxX < mMinX
                || bounds.mMaxY < mMinY
                || bounds.mMinX > mMaxX
                || bounds.mMinY > mMaxY)
            return;

        int leftDiff = (mMinX - bounds.mMinX);
        int topDiff = (bounds.mMaxY - mMaxY);
        int rightDiff = (bounds.mMaxX - mMaxX);
        int bottomDiff =  (mMinY - bounds.mMinY);
        Ogre::Image::Box srcBox ( std::max(0, leftDiff * cellImageSizeSrc),
                                  std::max(0, topDiff * cellImageSizeSrc),
                                  std::min(image.getWidth(), image.getWidth() - rightDiff * cellImageSizeSrc),
                                  std::min(image.getHeight(), image.getHeight() - bottomDiff * cellImageSizeSrc));

        Ogre::Image::Box destBox ( std::max(0, -leftDiff * cellImageSizeDst),
                                   std::max(0, -topDiff * cellImageSizeDst),
                                   std::min(mOverlayTexture->getWidth(), mOverlayTexture->getWidth() + rightDiff * cellImageSizeDst),
                                   std::min(mOverlayTexture->getHeight(), mOverlayTexture->getHeight() + bottomDiff * cellImageSizeDst));

        // Looks like there is no interface for blitting from memory with src/dst boxes.
        // So we create a temporary texture for blitting.
        Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().createManual("@temp",
            Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, image.getWidth(),
                                                                                image.getHeight(), 0, Ogre::PF_A8B8G8R8);
        tex->loadImage(image);

        mOverlayTexture->load();
        mOverlayTexture->getBuffer()->blit(tex->getBuffer(), srcBox, destBox);

        if (srcBox.left == destBox.left && srcBox.right == destBox.right
                && srcBox.top == destBox.top && srcBox.bottom == destBox.bottom
                && int(image.getWidth()) == mWidth && int(image.getHeight()) == mHeight)
            mOverlayImage = image;
        else
            mOverlayTexture->convertToImage(mOverlayImage);

        Ogre::TextureManager::getSingleton().remove("@temp");
    }
}


#ifndef BOOST_GEOMETRY_PROJECTIONS_LCC_HPP
#define BOOST_GEOMETRY_PROJECTIONS_LCC_HPP

// Boost.Geometry - extensions-gis-projections (based on PROJ4)
// This file is automatically generated. DO NOT EDIT.

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017.
// Modifications copyright (c) 2017, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 4.9.1

// Original copyright notice:

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#include <boost/geometry/util/math.hpp>
#include <boost/math/special_functions/hypot.hpp>

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>
#include <boost/geometry/srs/projections/impl/pj_msfn.hpp>
#include <boost/geometry/srs/projections/impl/pj_phi2.hpp>
#include <boost/geometry/srs/projections/impl/pj_tsfn.hpp>

namespace boost { namespace geometry
{

namespace srs { namespace par4
{
    struct lcc {};

}} //namespace srs::par4

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace lcc
    {
            static const double EPS10 = 1.e-10;

            template <typename T>
            struct par_lcc
            {
                T   phi1;
                T   phi2;
                T   n;
                T   rho0;
                T   c;
                int ellips;
            };

            // template class, using CRTP to implement forward/inverse
            template <typename CalculationType, typename Parameters>
            struct base_lcc_ellipsoid : public base_t_fi<base_lcc_ellipsoid<CalculationType, Parameters>,
                     CalculationType, Parameters>
            {

                typedef CalculationType geographic_type;
                typedef CalculationType cartesian_type;

                par_lcc<CalculationType> m_proj_parm;

                inline base_lcc_ellipsoid(const Parameters& par)
                    : base_t_fi<base_lcc_ellipsoid<CalculationType, Parameters>,
                     CalculationType, Parameters>(*this, par) {}

                // FORWARD(e_forward)  ellipsoid & spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    static const CalculationType FORTPI = detail::FORTPI<CalculationType>();
                    static const CalculationType HALFPI = detail::HALFPI<CalculationType>();

                    CalculationType rho;
                    if (fabs(fabs(lp_lat) - HALFPI) < EPS10) {
                        if ((lp_lat * this->m_proj_parm.n) <= 0.)
                            BOOST_THROW_EXCEPTION( projection_exception(-20) );
                        rho = 0.;
                    } else
                        rho = this->m_proj_parm.c * (this->m_proj_parm.ellips ? pow(pj_tsfn(lp_lat, sin(lp_lat),
                            this->m_par.e), this->m_proj_parm.n) : pow(tan(FORTPI + .5 * lp_lat), -this->m_proj_parm.n));
                    xy_x = this->m_par.k0 * (rho * sin( lp_lon *= this->m_proj_parm.n ) );
                    xy_y = this->m_par.k0 * (this->m_proj_parm.rho0 - rho * cos(lp_lon) );
                }

                // INVERSE(e_inverse)  ellipsoid & spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(cartesian_type& xy_x, cartesian_type& xy_y, geographic_type& lp_lon, geographic_type& lp_lat) const
                {
                    static const CalculationType HALFPI = detail::HALFPI<CalculationType>();

                    CalculationType rho;
                    xy_x /= this->m_par.k0;
                    xy_y /= this->m_par.k0;
                    if( (rho = boost::math::hypot(xy_x, xy_y = this->m_proj_parm.rho0 - xy_y)) != 0.0) {
                        if (this->m_proj_parm.n < 0.) {
                            rho = -rho;
                            xy_x = -xy_x;
                            xy_y = -xy_y;
                        }
                        if (this->m_proj_parm.ellips) {
                            if ((lp_lat = pj_phi2(pow(rho / this->m_proj_parm.c, 1./this->m_proj_parm.n), this->m_par.e))
                                == HUGE_VAL)
                                BOOST_THROW_EXCEPTION( projection_exception(-20) );
                        } else
                            lp_lat = 2. * atan(pow(this->m_proj_parm.c / rho, 1./this->m_proj_parm.n)) - HALFPI;
                        lp_lon = atan2(xy_x, xy_y) / this->m_proj_parm.n;
                    } else {
                        lp_lon = 0.;
                        lp_lat = this->m_proj_parm.n > 0. ? HALFPI : -HALFPI;
                    }
                }

                // SPECIAL(fac)
                #ifdef SPECIAL_FACTORS_NOT_CONVERTED
                inline void fac(Geographic lp, Factors &fac) const
                {
                    static const CalculationType FORTPI = detail::FORTPI<CalculationType>();
                    static const CalculationType HALFPI = detail::HALFPI<CalculationType>();

                    CalculationType rho;
                    if (fabs(fabs(lp_lat) - HALFPI) < EPS10) {
                        if ((lp_lat * this->m_proj_parm.n) <= 0.) return;
                        rho = 0.;
                    } else
                        rho = this->m_proj_parm.c * (this->m_proj_parm.ellips ? pow(pj_tsfn(lp_lat, sin(lp_lat),
                            this->m_par.e), this->m_proj_parm.n) : pow(tan(FORTPI + .5 * lp_lat), -this->m_proj_parm.n));
                    this->m_fac.code |= IS_ANAL_HK + IS_ANAL_CONV;
                    this->m_fac.k = this->m_fac.h = this->m_par.k0 * this->m_proj_parm.n * rho /
                        pj_msfn(sin(lp_lat), cos(lp_lat), this->m_par.es);
                    this->m_fac.conv = - this->m_proj_parm.n * lp_lon;
                }
                #endif

                static inline std::string get_name()
                {
                    return "lcc_ellipsoid";
                }

            };

            // Lambert Conformal Conic
            template <typename Parameters, typename T>
            inline void setup_lcc(Parameters& par, par_lcc<T>& proj_parm)
            {
                static const T FORTPI = detail::FORTPI<T>();
                static const T HALFPI = detail::HALFPI<T>();

                T cosphi, sinphi;
                int secant;

                proj_parm.phi1 = pj_param(par.params, "rlat_1").f;
                if (pj_param(par.params, "tlat_2").i)
                    proj_parm.phi2 = pj_param(par.params, "rlat_2").f;
                else {
                    proj_parm.phi2 = proj_parm.phi1;
                    if (!pj_param(par.params, "tlat_0").i)
                        par.phi0 = proj_parm.phi1;
                }
                if (fabs(proj_parm.phi1 + proj_parm.phi2) < EPS10)
                    BOOST_THROW_EXCEPTION( projection_exception(-21) );
                proj_parm.n = sinphi = sin(proj_parm.phi1);
                cosphi = cos(proj_parm.phi1);
                secant = fabs(proj_parm.phi1 - proj_parm.phi2) >= EPS10;
                if( (proj_parm.ellips = (par.es != 0.)) ) {
                    double ml1, m1;

                    par.e = sqrt(par.es);
                    m1 = pj_msfn(sinphi, cosphi, par.es);
                    ml1 = pj_tsfn(proj_parm.phi1, sinphi, par.e);
                    if (secant) { /* secant cone */
                        proj_parm.n = log(m1 /
                           pj_msfn(sinphi = sin(proj_parm.phi2), cos(proj_parm.phi2), par.es));
                        proj_parm.n /= log(ml1 / pj_tsfn(proj_parm.phi2, sinphi, par.e));
                    }
                    proj_parm.c = (proj_parm.rho0 = m1 * pow(ml1, -proj_parm.n) / proj_parm.n);
                    proj_parm.rho0 *= (fabs(fabs(par.phi0) - HALFPI) < EPS10) ? 0. :
                        pow(pj_tsfn(par.phi0, sin(par.phi0), par.e), proj_parm.n);
                } else {
                    if (secant)
                        proj_parm.n = log(cosphi / cos(proj_parm.phi2)) /
                           log(tan(FORTPI + .5 * proj_parm.phi2) /
                           tan(FORTPI + .5 * proj_parm.phi1));
                    proj_parm.c = cosphi * pow(tan(FORTPI + .5 * proj_parm.phi1), proj_parm.n) / proj_parm.n;
                    proj_parm.rho0 = (fabs(fabs(par.phi0) - HALFPI) < EPS10) ? 0. :
                        proj_parm.c * pow(tan(FORTPI + .5 * par.phi0), -proj_parm.n);
                }
            }

    }} // namespace detail::lcc
    #endif // doxygen

    /*!
        \brief Lambert Conformal Conic projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
         - Ellipsoid
        \par Projection parameters
         - lat_1: Latitude of first standard parallel (degrees)
         - lat_2: Latitude of second standard parallel (degrees)
         - lat_0: Latitude of origin
        \par Example
        \image html ex_lcc.gif
    */
    template <typename CalculationType, typename Parameters>
    struct lcc_ellipsoid : public detail::lcc::base_lcc_ellipsoid<CalculationType, Parameters>
    {
        inline lcc_ellipsoid(const Parameters& par) : detail::lcc::base_lcc_ellipsoid<CalculationType, Parameters>(par)
        {
            detail::lcc::setup_lcc(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::lcc, lcc_ellipsoid, lcc_ellipsoid)

        // Factory entry(s)
        template <typename CalculationType, typename Parameters>
        class lcc_entry : public detail::factory_entry<CalculationType, Parameters>
        {
            public :
                virtual base_v<CalculationType, Parameters>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<lcc_ellipsoid<CalculationType, Parameters>, CalculationType, Parameters>(par);
                }
        };

        template <typename CalculationType, typename Parameters>
        inline void lcc_init(detail::base_factory<CalculationType, Parameters>& factory)
        {
            factory.add_to_factory("lcc", new lcc_entry<CalculationType, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_LCC_HPP


#ifndef BOOST_MPL_DISTANCE_HPP_INCLUDED
#define BOOST_MPL_DISTANCE_HPP_INCLUDED

// Copyright Aleksey Gurtovoy 2000-2004
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org/libs/mpl for documentation.

// $Id: distance.hpp 49267 2008-10-11 06:19:02Z agurtovoy $
// $Date: 2008-10-11 10:19:02 +0400 (Ñá, 11 îêò 2008) $
// $Revision: 49267 $

#include <boost/mpl/distance_fwd.hpp>
#include <boost/mpl/iter_fold.hpp>
#include <boost/mpl/iterator_range.hpp>
#include <boost/mpl/long.hpp>
#include <boost/mpl/next.hpp>
#include <boost/mpl/tag.hpp>
#include <boost/mpl/apply_wrap.hpp>
#include <boost/mpl/aux_/msvc_eti_base.hpp>
#include <boost/mpl/aux_/value_wknd.hpp>
#include <boost/mpl/aux_/na_spec.hpp>
#include <boost/mpl/aux_/config/forwarding.hpp>
#include <boost/mpl/aux_/config/static_constant.hpp>

namespace boost { namespace mpl {

// default implementation for forward/bidirectional iterators
template< typename Tag > struct distance_impl
{
    template< typename First, typename Last > struct apply
#if !defined(BOOST_MPL_CFG_NO_NESTED_FORWARDING)
        : aux::msvc_eti_base< typename iter_fold<
              iterator_range<First,Last>
            , mpl::long_<0>
            , next<>
            >::type >
    {
#else
    {
        typedef typename iter_fold<
              iterator_range<First,Last>
            , mpl::long_<0>
            , next<>
            >::type type;

        BOOST_STATIC_CONSTANT(long, value =
              (iter_fold<
                  iterator_range<First,Last>
                , mpl::long_<0>
                , next<>
                >::type::value)
            );
#endif
    };
};

template<
      typename BOOST_MPL_AUX_NA_PARAM(First)
    , typename BOOST_MPL_AUX_NA_PARAM(Last)
    >
struct distance
    : distance_impl< typename tag<First>::type >
        ::template apply<First, Last>
{
    BOOST_MPL_AUX_LAMBDA_SUPPORT(2, distance, (First, Last))
};

BOOST_MPL_AUX_NA_SPEC(2, distance)

}}

#endif // BOOST_MPL_DISTANCE_HPP_INCLUDED


#ifndef BOOST_GEOMETRY_PROJECTIONS_TCC_HPP
#define BOOST_GEOMETRY_PROJECTIONS_TCC_HPP

// Boost.Geometry - extensions-gis-projections (based on PROJ4)
// This file is automatically generated. DO NOT EDIT.

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 4.9.1

// Original copyright notice:

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#include <boost/geometry/extensions/gis/projections/impl/base_static.hpp>
#include <boost/geometry/extensions/gis/projections/impl/base_dynamic.hpp>
#include <boost/geometry/extensions/gis/projections/impl/projects.hpp>
#include <boost/geometry/extensions/gis/projections/impl/factory_entry.hpp>

namespace boost { namespace geometry { namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace tcc
    {

            static const double EPS10 = 1.e-10;

            struct par_tcc
            {
                double ap;
            };

            // template class, using CRTP to implement forward/inverse
            template <typename Geographic, typename Cartesian, typename Parameters>
            struct base_tcc_spheroid : public base_t_f<base_tcc_spheroid<Geographic, Cartesian, Parameters>,
                     Geographic, Cartesian, Parameters>
            {

                 typedef double geographic_type;
                 typedef double cartesian_type;

                par_tcc m_proj_parm;

                inline base_tcc_spheroid(const Parameters& par)
                    : base_t_f<base_tcc_spheroid<Geographic, Cartesian, Parameters>,
                     Geographic, Cartesian, Parameters>(*this, par) {}

                // FORWARD(s_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    double b, bt;

                    b = cos(lp_lat) * sin(lp_lon);
                    if ((bt = 1. - b * b) < EPS10) throw proj_exception();;
                    xy_x = b / sqrt(bt);
                    xy_y = atan2(tan(lp_lat) , cos(lp_lon));
                }

                static inline std::string get_name()
                {
                    return "tcc_spheroid";
                }

            };

            // Transverse Central Cylindrical
            template <typename Parameters>
            void setup_tcc(Parameters& par, par_tcc& proj_parm)
            {
                par.es = 0.;
            }

        }} // namespace detail::tcc
    #endif // doxygen

    /*!
        \brief Transverse Central Cylindrical projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Spheroid
         - no inverse
        \par Example
        \image html ex_tcc.gif
    */
    template <typename Geographic, typename Cartesian, typename Parameters = parameters>
    struct tcc_spheroid : public detail::tcc::base_tcc_spheroid<Geographic, Cartesian, Parameters>
    {
        inline tcc_spheroid(const Parameters& par) : detail::tcc::base_tcc_spheroid<Geographic, Cartesian, Parameters>(par)
        {
            detail::tcc::setup_tcc(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Factory entry(s)
        template <typename Geographic, typename Cartesian, typename Parameters>
        class tcc_entry : public detail::factory_entry<Geographic, Cartesian, Parameters>
        {
            public :
                virtual projection<Geographic, Cartesian>* create_new(const Parameters& par) const
                {
                    return new base_v_f<tcc_spheroid<Geographic, Cartesian, Parameters>, Geographic, Cartesian, Parameters>(par);
                }
        };

        template <typename Geographic, typename Cartesian, typename Parameters>
        inline void tcc_init(detail::base_factory<Geographic, Cartesian, Parameters>& factory)
        {
            factory.add_to_factory("tcc", new tcc_entry<Geographic, Cartesian, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

}}} // namespace boost::geometry::projections

#endif // BOOST_GEOMETRY_PROJECTIONS_TCC_HPP


#include <Eigen/Core>
#include <aslam/cameras/GridDetector.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <sm/logging.hpp>
#include <vector>

namespace aslam {
namespace cameras {

// serialization constructor (don't use!)
GridDetector::GridDetector() {}

GridDetector::GridDetector(boost::shared_ptr<CameraGeometryBase> geometry, GridCalibrationTargetBase::Ptr target,
                           const GridDetector::GridDetectorOptions& options)
    : _geometry(geometry), _target(target), _options(options) {
    SM_ASSERT_TRUE(Exception, _geometry.get() != NULL, "Unable to initialize with null camera geometry");
    SM_ASSERT_TRUE(Exception, _target.get() != NULL, "Unable to initialize with null calibration target");

    initializeDetector();
}

void GridDetector::initializeDetector() {
    if (_options.plotCornerReprojection) {
        cv::namedWindow("Corner reprojection", cv::WINDOW_NORMAL);
    }
}

GridDetector::~GridDetector() {}

void GridDetector::initCameraGeometry(boost::shared_ptr<CameraGeometryBase> geometry) {
    SM_ASSERT_TRUE(Exception, geometry.get() != NULL, "Unable to initialize with null camera geometry");
    _geometry = geometry;
}

bool GridDetector::initCameraGeometryFromObservation(const cv::Mat& image) {
    boost::shared_ptr<std::vector<cv::Mat>> images_ptr = boost::make_shared<std::vector<cv::Mat>>();
    images_ptr->push_back(image);

    return initCameraGeometryFromObservations(images_ptr);
}

bool GridDetector::initCameraGeometryFromObservations(boost::shared_ptr<std::vector<cv::Mat>> images_ptr) {
    std::vector<cv::Mat>& images = *images_ptr;

    SM_DEFINE_EXCEPTION(Exception, std::runtime_error);
    SM_ASSERT_TRUE(Exception, images.size() != 0, "Need min. one image");

    std::vector<GridCalibrationTargetObservation> observations;

    for (unsigned int i = 0; i < images.size(); i++) {
        GridCalibrationTargetObservation obs(_target);

        // detect calibration target
        bool success = findTargetNoTransformation(images[i], obs);

        // delete image copy (save memory)
        obs.clearImage();

        // append
        if (success) observations.push_back(obs);
    }

    // initialize the intrinsics
    if (observations.size() > 0) return _geometry->initializeIntrinsics(observations);

    return false;
}

bool GridDetector::findTarget(const cv::Mat& image, GridCalibrationTargetObservation& outObservation) const {
    return findTarget(image, aslam::Time(0, 0), outObservation);
}

bool GridDetector::findTargetNoTransformation(const cv::Mat& image, const aslam::Time& stamp,
                                              GridCalibrationTargetObservation& outObservation) const {
    bool success = false;

    // Extract the calibration target corner points
    Eigen::MatrixXd cornerPoints;
    std::vector<bool> validCorners;
    success = _target->computeObservation(image, cornerPoints, validCorners);

    // Set the image, target, and timestamp regardless of success.
    outObservation.setTarget(_target);
    outObservation.setImage(image);
    outObservation.setTime(stamp);

    // Set the observed corners in the observation
    for (int i = 0; i < cornerPoints.rows(); i++) {
        if (validCorners[i]) {
            outObservation.updateImagePoint(i, cornerPoints.row(i).transpose());
        }
    }

    return success;
}

bool GridDetector::findTarget(const cv::Mat& image, const aslam::Time& stamp,
                              GridCalibrationTargetObservation& outObservation) const {
    sm::kinematics::Transformation trafo;

    // find calibration target corners
    bool success = findTargetNoTransformation(image, stamp, outObservation);

    // calculate trafo cam-target
    if (success) {
        // also estimate the transformation:
        success = _geometry->estimateTransformation(outObservation, trafo);

        if (success)
            outObservation.set_T_t_c(trafo);
        else
            SM_DEBUG_STREAM("estimateTransformation() failed");
    }

    // remove corners with a reprojection error above a threshold
    //(remove detection outliers)
    if (_options.filterCornerOutliers && success) {
        // calculate reprojection errors
        std::vector<cv::Point2f> corners_reproj;
        std::vector<cv::Point2f> corners_detected;
        outObservation.getCornerReprojection(_geometry, corners_reproj);
        unsigned int numCorners = outObservation.getCornersImageFrame(corners_detected);

        // calculate error norm
        Eigen::MatrixXd reprojection_errors_norm = Eigen::MatrixXd::Zero(numCorners, 1);

        for (unsigned int i = 0; i < numCorners; i++) {
            cv::Point2f reprojection_err = corners_detected[i] - corners_reproj[i];

            reprojection_errors_norm(i, 0) =
                sqrt(reprojection_err.x * reprojection_err.x + reprojection_err.y * reprojection_err.y);
        }

        // calculate statistics
        double mean = reprojection_errors_norm.mean();
        double std = 0.0;
        for (unsigned int i = 0; i < numCorners; i++) {
            double temp = reprojection_errors_norm(i, 0) - mean;
            std += temp * temp;
        }
        std /= (double)numCorners;
        std = sqrt(std);

        // disable outlier corners
        std::vector<unsigned int> cornerIdx;
        outObservation.getCornersIdx(cornerIdx);

        unsigned int removeCount = 0;
        for (unsigned int i = 0; i < corners_detected.size(); i++) {
            if (reprojection_errors_norm(i, 0) > mean + _options.filterCornerSigmaThreshold * std &&
                reprojection_errors_norm(i, 0) > _options.filterCornerMinReprojError) {
                outObservation.removeImagePoint(cornerIdx[i]);
                removeCount++;
                SM_DEBUG_STREAM("removed target point with reprojection error of "
                                    << reprojection_errors_norm(i, 0) << " (mean: " << mean << ", std: " << std
                                    << ")\n";);
            }
        }

        if (removeCount > 0)
            SM_DEBUG_STREAM("removed " << removeCount << " of " << numCorners
                                       << " calibration target corner outliers\n";);
    }

    // show plot of reprojected corners
    if (_options.plotCornerReprojection) {
        cv::Mat imageCopy1 = image.clone();
        cv::cvtColor(imageCopy1, imageCopy1, CV_GRAY2RGB);

        if (success) {
            // calculate reprojection
            std::vector<cv::Point2f> reprojs;
            outObservation.getCornerReprojection(_geometry, reprojs);

            for (unsigned int i = 0; i < reprojs.size(); i++)
                cv::circle(imageCopy1, reprojs[i], 3, CV_RGB(255, 0, 0), 1);

        } else {
            cv::putText(imageCopy1, "Detection failed! (frame not used)", cv::Point(50, 50), CV_FONT_HERSHEY_SIMPLEX,
                        0.8, CV_RGB(255, 0, 0), 3, 8, false);
        }

        cv::imshow("Corner reprojection", imageCopy1);  // OpenCV call
        if (_options.imageStepping) {
            cv::waitKey(0);
        } else {
            cv::waitKey(1);
        }
    }

    return success;
}

/// \brief Find the target but don't estimate the transformation.
bool GridDetector::findTargetNoTransformation(const cv::Mat& image,
                                              GridCalibrationTargetObservation& outObservation) const {
    return findTargetNoTransformation(image, aslam::Time(0, 0), outObservation);
}

}  // namespace cameras
}  // namespace aslam

// export explicit instantions for all included archives
#include <boost/serialization/export.hpp>
#include <sm/boost/serialization.hpp>
BOOST_CLASS_EXPORT_IMPLEMENT(aslam::cameras::GridDetector);
BOOST_CLASS_EXPORT_IMPLEMENT(aslam::cameras::GridDetector::GridDetectorOptions);


#include <ros/ros.h>
#include <rosbag/bag.h>
#include <rosbag/view.h>

// ROS messages
#include <sensor_msgs/LaserScan.h>
#include <geometry_msgs/Pose.h>
#include <geometry_msgs/PoseArray.h>
#include <visualization_msgs/Marker.h>
#include <visualization_msgs/MarkerArray.h>

// Custom messages
#include <leg_tracker/laser_processor.h>
#include <leg_tracker/cluster_features.h>

#include <tf/transform_listener.h>
#include <tf/message_filter.h>
#include <message_filters/subscriber.h>

#include <boost/foreach.hpp>

#include <math.h>       /* atan2 */

#define PI 3.14159265

/**
* @brief Extract positive training scan clusters from a rosbag
*
* Reads in a rosbag, finds all scan clusters lying within user-specified bounding box (or alternatively min/max angles),
* marks those clusters as positive examples and saves the result to a new rosbag
* Used to quickly get positive examples for training the leg detector.
*/
class ExtractPositiveTrainingClusters
{
public:

  /**
  * @brief Constructor
  */
  ExtractPositiveTrainingClusters()
  {
    // Get ROS parameters
    ros::NodeHandle nh_private("~"); // to get private parameters

    // Required parameters
    if (!nh_private.getParam("load_bag_file", load_bag_file_))
      ROS_ERROR("Couldn't get bag_load_file from ros param server");
    if (!nh_private.getParam("save_bag_file", save_bag_file_))
      ROS_ERROR("Couldn't get bag_save_file from ros param server");
    if (!nh_private.getParam("scan_topic", scan_topic_))
      ROS_ERROR("Couldn't get scan_topic from ros param server");
    if (!nh_private.getParam("laser_frame", laser_frame_))
      ROS_ERROR("Couldn't get laser_frame from ros param server");

    // Optional parameters (i.e., params with defaults)
    nh_private.param("cluster_dist_euclid", cluster_dist_euclid_, 0.1);
    nh_private.param("min_points_per_cluster", min_points_per_cluster_, 5);

    // Optional parameters - either the x-y coordinates of a bounding box can be specified
    // or the min/max angle and length for an arc
    if (!nh_private.getParam("x_min", x_min_) or
        !nh_private.getParam("x_max", x_max_) or
        !nh_private.getParam("y_min", y_min_) or
        !nh_private.getParam("y_max", y_max_))
    {
      ROS_INFO("Couldn't get bounding box for positive clusters. Assuming you've specified a min/max scan angle and a max distance instead.");
      use_bounding_box_ = false;
    }
    else
    {
      use_bounding_box_ = true;
    }
    if (!nh_private.getParam("min_angle", min_angle_) or
        !nh_private.getParam("max_angle", max_angle_) or
        !nh_private.getParam("max_dist", max_dist_))
    {
      if (use_bounding_box_)
      {
        ROS_INFO("Couldn't get min/max scan angle for positive clusters. Assuming you've specified a bounding box instead.");
      }
      else
      {
        ROS_ERROR("Couldn't get bounding box or scan min/max angle for positive clusters");
      }
    }

    // Print back params:
    printf("\nROS parameters: \n");
    printf("cluster_dist_euclid:%.2fm \n", cluster_dist_euclid_);
    printf("min_points_per_cluster:%i \n", min_points_per_cluster_);
    printf("\n");
  }

  /**
  * @brief Extract the positive clusters and record their position with a leg_cluster_positions message
  */
  void extract()
  {
    // Open rosbag we'll be saving to
    rosbag::Bag save_bag;
    save_bag.open(save_bag_file_.c_str(), rosbag::bagmode::Write);

    // Open rosbag we'll be loading from
    rosbag::Bag load_bag;
    load_bag.open(load_bag_file_.c_str(), rosbag::bagmode::Read);

    // Iterate through all scan messages in the loaded rosbag
    std::vector<std::string> topics;
    topics.push_back(std::string(scan_topic_));
    rosbag::View view(load_bag, rosbag::TopicQuery(topics));
    BOOST_FOREACH(rosbag::MessageInstance const m, view)
    {
      sensor_msgs::LaserScan::ConstPtr scan = m.instantiate<sensor_msgs::LaserScan>();
      if (scan != NULL)
      {
        // Processes scan
        laser_processor::ScanProcessor processor(*scan);
        processor.splitConnected(cluster_dist_euclid_);
        processor.removeLessThan(min_points_per_cluster_);

        geometry_msgs::PoseArray leg_cluster_positions;
        leg_cluster_positions.header.frame_id = laser_frame_;

        for (std::list<laser_processor::SampleSet*>::iterator i = processor.getClusters().begin();
          i != processor.getClusters().end();
          ++i)
        {
          // Only use scan clusters that are in the specified positive cluster area
          tf::Point cluster_position = (*i)->getPosition();

          double x_pos = cluster_position[0];
          double y_pos = cluster_position[1];
          double angle = atan2(y_pos,x_pos) * 180 / PI;
          double dist_abs = sqrt(x_pos*x_pos + y_pos*y_pos);

          bool in_bounding_box = use_bounding_box_ and x_pos > x_min_ and x_pos < x_max_ and y_pos > y_min_ and y_pos < y_max_;
          bool in_arc = !use_bounding_box_ and angle > min_angle_ and angle < max_angle_ and dist_abs < max_dist_;
          if (in_bounding_box or in_arc)
          {
            geometry_msgs::Pose new_leg_cluster_position;
            new_leg_cluster_position.position.x = cluster_position[0];
            new_leg_cluster_position.position.y = cluster_position[1];
            leg_cluster_positions.poses.push_back(new_leg_cluster_position);
          }
        }
        if (!leg_cluster_positions.poses.empty())  // at least one leg has been found in current scan
        {
          // Save position of leg to be used later for training
          save_bag.write("/leg_cluster_positions", ros::Time::now(), leg_cluster_positions);

          // Save scan
          save_bag.write("/training_scan", ros::Time::now(), *scan);

          // Save a marker of the position of the cluster we extracted.
          // Just used so we can playback the rosbag file
          // and visually verify the correct clusters have been extracted
          visualization_msgs::MarkerArray ma;
          for (int i = 0;
              i < leg_cluster_positions.poses.size();
              i++)
          {
            visualization_msgs::Marker m;
            m.header.frame_id = "laser_frame";
            m.ns = "LEGS";
            m.id = i;
            m.type = m.SPHERE;
            m.pose.position.x = leg_cluster_positions.poses[i].position.x;
            m.pose.position.y = leg_cluster_positions.poses[i].position.y;
            m.pose.position.z = 0.1;
            m.scale.x = .2;
            m.scale.y = .2;
            m.scale.z = .2;
            m.color.a = 1;
            m.lifetime = ros::Duration(0.2);
            m.color.b = 0.0;
            m.color.r = 1.0;
            ma.markers.push_back(m);
          }
          save_bag.write("/visualization_marker_array", ros::Time::now(), ma);
        }
      }
    }
    load_bag.close();
  }

private:
  tf::TransformListener tfl_;
  std::string laser_frame_;
  std::string scan_topic_;

  ros::NodeHandle nh_;

  std::string save_bag_file_;
  std::string load_bag_file_;

  double cluster_dist_euclid_;
  int min_points_per_cluster_;

  // to describe bounding box containing positive clusters
  bool use_bounding_box_;
  double x_min_;
  double x_max_;
  double y_min_;
  double y_max_;

  // to describe scan angles containing positive clusters
  int min_angle_;
  int max_angle_;
  int max_dist_;
};

int main(int argc, char **argv)
{
  ros::init(argc, argv,"extract_positive_leg_clusters");
  ExtractPositiveTrainingClusters eptc;
  eptc.extract();
  ROS_INFO("Finished successfully! (you still have press ctrl+c to terminate if you ran from a launch file)");
  /** @todo Automatically terminate after finishing successfully */
  return 0;
}


// Copyright (c) 2015-2021 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#ifndef haplotype_likelihood_model_hpp
#define haplotype_likelihood_model_hpp

#include <vector>
#include <iterator>
#include <cstddef>
#include <cstdint>
#include <algorithm>
#include <functional>
#include <memory>
#include <stdexcept>

#include <boost/optional.hpp>

#include "config/common.hpp"
#include "basics/contig_region.hpp"
#include "basics/cigar_string.hpp"
#include "basics/aligned_read.hpp"
#include "basics/aligned_template.hpp"
#include "core/types/haplotype.hpp"
#include "core/models/error/snv_error_model.hpp"
#include "core/models/error/indel_error_model.hpp"
#include "pairhmm/pair_hmm.hpp"

namespace octopus {

class HaplotypeLikelihoodModel
{
public:
    using LogProbability = double;
    using Penalty = hmm::Penalty;

    struct Config
    {
        bool use_mapping_quality = true;
        boost::optional<AlignedRead::MappingQuality> mapping_quality_cap_trigger = boost::none;
        AlignedRead::MappingQuality mapping_quality_cap = 120;
        bool use_flank_state = true;
        unsigned max_indel_error = 8;
        bool use_int_scores = false;
    };

    struct FlankState
    {
        ContigRegion::Position lhs_flank, rhs_flank;
    };

    class ShortHaplotypeError;

    using MappingPosition       = std::size_t;
    using MappingPositionVector = std::vector<MappingPosition>;
    using MappingPositionItr    = MappingPositionVector::const_iterator;

    struct Alignment
    {
        MappingPosition mapping_position;
        CigarString cigar;
        LogProbability likelihood;
    };

    HaplotypeLikelihoodModel();
    HaplotypeLikelihoodModel(Config config);
    HaplotypeLikelihoodModel(std::unique_ptr<SnvErrorModel> snv_model,
                             std::unique_ptr<IndelErrorModel> indel_model);
    HaplotypeLikelihoodModel(std::unique_ptr<SnvErrorModel> snv_model,
                             std::unique_ptr<IndelErrorModel> indel_model,
                             Config config);

    HaplotypeLikelihoodModel(const HaplotypeLikelihoodModel&);
    HaplotypeLikelihoodModel& operator=(const HaplotypeLikelihoodModel&);
    HaplotypeLikelihoodModel(HaplotypeLikelihoodModel&&)            = default;
    HaplotypeLikelihoodModel& operator=(HaplotypeLikelihoodModel&&) = default;

    friend void swap(HaplotypeLikelihoodModel& lhs, HaplotypeLikelihoodModel& rhs) noexcept;

    ~HaplotypeLikelihoodModel() = default;

    const Config& config() const noexcept;
    void set(Config config);

    unsigned pad_requirement() const noexcept;

    bool can_use_flank_state() const noexcept;

    void reset(const Haplotype& haplotype, boost::optional<FlankState> flank_state = boost::none);

    void clear() noexcept;

    // ln p(read | haplotype, model)
    LogProbability evaluate(const AlignedRead& read) const;
    LogProbability evaluate(const AlignedRead& read, const MappingPositionVector& mapping_positions) const;
    LogProbability evaluate(const AlignedRead& read, MappingPositionItr first_mapping_position, MappingPositionItr last_mapping_position) const;

    // ln p(read template | haplotype, model)
    LogProbability evaluate(const AlignedTemplate& reads) const;
    LogProbability evaluate(const AlignedTemplate& reads, const std::vector<MappingPositionVector>& mapping_positions) const;

    Alignment align(const AlignedRead& read) const;
    Alignment align(const AlignedRead& read, const MappingPositionVector& mapping_positions) const;
    Alignment align(const AlignedRead& read, MappingPositionItr first_mapping_position, MappingPositionItr last_mapping_position) const;

private:
    using HMM = hmm::PairHMM<hmm::MutationModel>;

    std::unique_ptr<SnvErrorModel> snv_error_model_;
    std::unique_ptr<IndelErrorModel> indel_error_model_;

    const Haplotype* haplotype_;

    boost::optional<FlankState> haplotype_flank_state_;

    std::vector<char> haplotype_snv_forward_mask_, haplotype_snv_reverse_mask_;
    std::vector<Penalty> haplotype_snv_forward_priors_, haplotype_snv_reverse_priors_;

    std::vector<Penalty> haplotype_gap_open_penalities_, haplotype_gap_extend_penalities_;
    Config config_;
    mutable HMM hmm_;
};

class HaplotypeLikelihoodModel::ShortHaplotypeError : public std::runtime_error
{
public:
    using Length = Haplotype::NucleotideSequence::size_type;

    ShortHaplotypeError() = delete;

    ShortHaplotypeError(const Haplotype& haplotype, Length required_extension);

    const Haplotype& haplotype() const noexcept;

    Length required_extension() const noexcept;

private:
    const Haplotype& haplotype_;
    Length required_extension_;
};

HaplotypeLikelihoodModel make_haplotype_likelihood_model(const std::string label, bool use_mapping_quality = true);

} // namespace octopus

#endif


//==============================================================================
//         Copyright 2003 - 2012 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2014 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//         Copyright 2013 - 2014 MetaScale SAS
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_ARITHMETIC_FUNCTIONS_SIMD_VMX_ALTIVEC_ADDS_HPP_INCLUDED
#define BOOST_SIMD_ARITHMETIC_FUNCTIONS_SIMD_VMX_ALTIVEC_ADDS_HPP_INCLUDED
#ifdef BOOST_SIMD_HAS_VMX_SUPPORT

#include <boost/simd/arithmetic/functions/adds.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT          ( adds_
                                    , boost::simd::tag::vmx_
                                    , (A0)
                                    , ((simd_< int_<A0>
                                            , boost::simd::tag::vmx_
                                            >
                                      ))
                                      ((simd_< int_<A0>
                                            , boost::simd::tag::vmx_
                                            >
                                      ))
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return vec_adds( a0(), a1() );
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( adds_
                                    , boost::simd::tag::vmx_
                                    , (A0)
                                    , ((simd_< uint_<A0>
                                            , boost::simd::tag::vmx_
                                            >
                                      ))
                                      ((simd_< uint_<A0>
                                            , boost::simd::tag::vmx_
                                            >
                                      ))
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return vec_adds( a0(), a1() );
    }
  };
} } }

#endif
#endif


//          Copyright Gavin Band 2008 - 2012.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#ifndef QCTOOL_MISSINGNESS_HETEROZYGOSITY_COMPUTATION_HPP
#define QCTOOL_MISSINGNESS_HETEROZYGOSITY_COMPUTATION_HPP

#include <Eigen/Core>
#include <boost/optional.hpp>
#include "components/SampleSummaryComponent/SampleSummaryComputation.hpp"

namespace sample_stats {
	struct MissingnessHeterozygosityComputation: public SampleSummaryComputation
	{
		MissingnessHeterozygosityComputation() ;
		MissingnessHeterozygosityComputation( genfile::Chromosome ) ;
		void accumulate( genfile::VariantIdentifyingData const&, Genotypes const&, genfile::VariantDataReader& ) ;
		void compute( int sample, ResultCallback ) ;
		std::string get_summary( std::string const& prefix = "", std::size_t column_width = 20 ) const ;
	private:
		boost::optional< genfile::Chromosome > const m_chromosome ;
		std::size_t m_snp_index ;
		double m_threshhold ;
		Eigen::VectorXd m_ones ;
		Eigen::VectorXd m_total_probabilities ;
		Eigen::VectorXd m_total_calls ;
		Eigen::VectorXd m_het_snps ;
		Eigen::VectorXd m_het_snp_calls ;
	} ;
}

#endif


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020 Nikita Kaskov <nbering@nil.foundation>
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//---------------------------------------------------------------------------//

#include <boost/program_options.hpp>

#include <nil/crypto3/zk/snark/default_types/ram_zksnark_pp.hpp>
#include <nil/crypto3/zk/snark/relations/ram_computations/memory/examples/memory_contents_examples.hpp>
#include <nil/crypto3/zk/snark/relations/ram_computations/rams/examples/ram_examples.hpp>
#include <nil/crypto3/zk/snark/relations/ram_computations/rams/tinyram/tinyram_params.hpp>
#include <nil/crypto3/zk/snark/schemes/zksnark/ram_zksnark/examples/run_ram_zksnark.hpp>
#include <nil/crypto3/zk/snark/schemes/zksnark/ram_zksnark/ram_zksnark.hpp>

using namespace nil::crypto3::zk::snark;

template<typename FieldType>
void simulate_random_memory_contents(const tinyram_architecture_params &ap, const std::size_t input_size,
                                     const std::size_t program_size) {
    const std::size_t num_addresses = 1ul << ap.dwaddr_len();
    const std::size_t value_size = 2 * ap.w;
    memory_contents init_random =
        random_memory_contents(num_addresses, value_size, program_size + (input_size + 1) / 2);

    std::cout << "Initialize random delegated memory" << std::endl;
    delegated_ra_memory<FieldType> dm_random(num_addresses, value_size, init_random);
}

template<typename CurveType>
void profile_ram_zksnark_verifier(const tinyram_architecture_params &ap, const std::size_t input_size,
                                  const std::size_t program_size) {
    typedef ram_zksnark_machine_pp<CurveType> RAMType;
    const std::size_t time_bound = 10;

    const std::size_t boot_trace_size_bound = program_size + input_size;
    const ram_example<RAMType> example = gen_ram_example_complex<RAMType>(ap, boot_trace_size_bound, time_bound, true);

    ram_zksnark_proof<CurveType> pi;
    ram_zksnark_verification_key<CurveType> vk = ram_zksnark_verification_key<CurveType>::dummy_verification_key(ap);

    std::cout << "Verify fake proof" << std::endl;
    ram_zksnark_verifier<CurveType>(vk, example.boot_trace, time_bound, pi);
}

template<typename CurveType>
void print_ram_zksnark_verifier_profiling() {
    algebra::inhibit_profiling_info = true;
    for (std::size_t w : {16, 32}) {
        const std::size_t k = 16;

        for (std::size_t input_size : {0, 10, 100}) {
            for (std::size_t program_size = 10; program_size <= 10000; program_size *= 10) {
                const tinyram_architecture_params ap(w, k);

                profile_ram_zksnark_verifier<CurveType>(ap, input_size, program_size);

                const double input_map = algebra::last_times["Call to ram_zksnark_verifier_input_map"];
                const double preprocessing = algebra::last_times["Call to r1cs_ppzksnark_process_verification_key"];
                const double accumulate = algebra::last_times["Call to r1cs_ppzksnark_IC_query::accumulate"];
                const double pairings = algebra::last_times["Online pairing computations"];
                const double total = algebra::last_times["Call to ram_zksnark_verifier"];
                const double rest = total - (input_map + preprocessing + accumulate + pairings);

                const double delegated_ra_memory_init =
                    algebra::last_times["Construct delegated_ra_memory from memory map"];
                simulate_random_memory_contents<algebra::Fr<typename CurveType::curve_A_pp>>(ap, input_size,
                                                                                program_size);
                const double delegated_ra_memory_init_random =
                    algebra::last_times["Initialize random delegated memory"];
                const double input_map_random = input_map - delegated_ra_memory_init + delegated_ra_memory_init_random;
                const double total_random = total - delegated_ra_memory_init + delegated_ra_memory_init_random;

                printf(
                    "w = %zu, k = %zu, program_size = %zu, input_size = %zu, input_map = %0.2fms, preprocessing = "
                    "%0.2fms, accumulate = %0.2fms, pairings = %0.2fms, rest = %0.2fms, total = %0.2fms "
                    "(input_map_random = %0.2fms, total_random = %0.2fms)\n",
                    w, k, program_size, input_size, input_map * 1e-6, preprocessing * 1e-6, accumulate * 1e-6,
                    pairings * 1e-6, rest * 1e-6, total * 1e-6, input_map_random * 1e-6, total_random * 1e-6);
            }
        }
    }
}

template<typename CurveType>
void profile_ram_zksnark(const tinyram_architecture_params &ap, const std::size_t program_size,
                         const std::size_t input_size, const std::size_t time_bound) {
    typedef ram_zksnark_machine_pp<CurveType> RAMType;

    const std::size_t boot_trace_size_bound = program_size + input_size;
    const ram_example<RAMType> example = gen_ram_example_complex<RAMType>(ap, boot_trace_size_bound, time_bound, true);
    const bool bit = run_ram_zksnark<CurveType>(example);
    assert(bit);
}

namespace po = boost::program_options;

bool process_command_line(const int argc, const char **argv, bool &profile_gp, std::size_t &w, std::size_t &k,
                          bool &profile_v, std::size_t &l) {
    try {
        po::options_description desc("Usage");
        desc.add_options()("help", "print this help message")("profile_gp", "profile generator and prover")(
            "w", po::value<std::size_t>(&w)->default_value(16), "word size")(
            "k", po::value<std::size_t>(&k)->default_value(16), "register count")("profile_v", "profile verifier")(
            "v", "print version info")("l", po::value<std::size_t>(&l)->default_value(10), "program length");

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

        if (vm.count("v")) {
            algebra::print_compilation_info();
            exit(0);
        }

        if (vm.count("help")) {
            std::cout << desc << "\n";
            return false;
        }

        profile_gp = vm.count("profile_gp");
        profile_v = vm.count("profile_v");

        if (!(vm.count("profile_gp") ^ vm.count("profile_v"))) {
            std::cout << "Must choose between profiling generator/prover and profiling verifier (see --help)\n";
            return false;
        }

        po::notify(vm);
    } catch (std::exception &e) {
        std::cerr << "Error: " << e.what() << "\n";
        return false;
    }

    return true;
}

int main(int argc, const char *argv[]) {
    bool profile_gp;
    std::size_t w;
    std::size_t k;
    bool profile_v;
    std::size_t l;

    if (!process_command_line(argc, argv, profile_gp, w, k, profile_v, l)) {
        return 1;
    }

    tinyram_architecture_params ap(w, k);

    if (profile_gp) {
        profile_ram_zksnark<default_ram_zksnark_pp>(ap, 100, 100, 10);    // w, k, l, n, T
    }

    if (profile_v) {
        profile_ram_zksnark_verifier<default_ram_zksnark_pp>(ap, l / 2, l / 2);
    }
}


// Copyright (c) 2011-2013, Pacific Biosciences of California, Inc.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted (subject to the limitations in the
// disclaimer below) provided that the following conditions are met:
//
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
//  * Redistributions in binary form must reproduce the above
//    copyright notice, this list of conditions and the following
//    disclaimer in the documentation and/or other materials provided
//    with the distribution.
//
//  * Neither the name of Pacific Biosciences nor the names of its
//    contributors may be used to endorse or promote products derived
//    from this software without specific prior written permission.
//
// NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
// GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY PACIFIC
// BIOSCIENCES AND ITS CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL PACIFIC BIOSCIENCES OR ITS
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.

// Author: David Alexander

#include <ConsensusCore/Quiver/SimpleRecursor.hpp>

#include <ConsensusCore/Edna/EdnaEvaluator.hpp>
#include <ConsensusCore/Matrix/DenseMatrix.hpp>
#include <ConsensusCore/Matrix/SparseMatrix.hpp>
#include <ConsensusCore/Quiver/detail/Combiner.hpp>
#include <ConsensusCore/Quiver/detail/RecursorBase.hpp>
#include <ConsensusCore/Quiver/QvEvaluator.hpp>
#include <ConsensusCore/Interval.hpp>
#include <ConsensusCore/Utils.hpp>

#include <algorithm>
#include <boost/tuple/tuple.hpp>
#include <climits>
#include <utility>

using std::min;
using std::max;

#define NEG_INF -FLT_MAX

namespace ConsensusCore {

    template<typename M, typename E, typename C>
    void
    SimpleRecursor<M, E, C>::FillAlpha(const E& e, const M& guide, M& alpha) const
    {
        int I = e.ReadLength();
        int J = e.TemplateLength();

        assert(alpha.Rows() == I + 1 && alpha.Columns() == J + 1);
        assert(guide.IsNull() ||
               (guide.Rows() == alpha.Rows() && guide.Columns() == alpha.Columns()));

        int hintBeginRow = 0, hintEndRow = 0;

        for (int j = 0; j <= J; ++j)
        {
            this->RangeGuide(j, guide, alpha, &hintBeginRow, &hintEndRow);

            int requiredEndRow = min(I + 1, hintEndRow);

            int i;
            float score = NEG_INF;
            float thresholdScore = NEG_INF;
            float maxScore = NEG_INF;

            alpha.StartEditingColumn(j, hintBeginRow, hintEndRow);

            int beginRow = hintBeginRow, endRow;
            for (i = beginRow;
                 i < I + 1 && (score >= thresholdScore || i < requiredEndRow);
                 ++i)
            {
                float thisMoveScore;
                score = NEG_INF;

                // Start:
                if (i == 0 && j == 0)
                {
                    score = 0.0f;
                }

                // Incorporation:
                if (i > 0 && j > 0)
                {
                    thisMoveScore = alpha(i - 1, j - 1) + e.Inc(i - 1, j - 1);
                    score = C::Combine(score, thisMoveScore);
                }

                // Extra:
                if (i > 0)
                {
                    thisMoveScore = alpha(i - 1, j) + e.Extra(i - 1, j);
                    score = C::Combine(score, thisMoveScore);
                }

                // Delete:
                if (j > 0)
                {
                    thisMoveScore = alpha(i, j - 1) + e.Del(i, j - 1);
                    score = C::Combine(score, thisMoveScore);
                }

                // Merge:
                if ((this->movesAvailable_ & MERGE) && j > 1 && i > 0)
                {
                    thisMoveScore = alpha(i - 1, j - 2) + e.Merge(i - 1, j - 2);
                    score = C::Combine(score, thisMoveScore);
                }

                //  Save score
                alpha.Set(i, j, score);

                if (score > maxScore)
                {
                    maxScore = score;
                    thresholdScore = maxScore - this->bandingOptions_.ScoreDiff;
                }
            }

            endRow = i;
            alpha.FinishEditingColumn(j, beginRow, endRow);

            // Now, revise the hints to tell the caller where the mass of the
            // distribution really lived in this column.
            hintEndRow = endRow;
            for (i = beginRow; i < endRow && alpha(i, j) < thresholdScore; ++i);
            hintBeginRow = i;
        }
    }

    template<typename M, typename E, typename C>
    void
    SimpleRecursor<M, E, C>::FillBeta(const E& e, const M& guide, M& beta) const
    {
        int I = e.ReadLength();
        int J = e.TemplateLength();

        assert(beta.Rows() == I + 1 && beta.Columns() == J + 1);
        assert(guide.IsNull() ||
               (guide.Rows() == beta.Rows() && guide.Columns() == beta.Columns()));

        int hintBeginRow = I + 1, hintEndRow = I + 1;

        for (int j = J; j >= 0; --j)
        {
            this->RangeGuide(j, guide, beta, &hintBeginRow, &hintEndRow);

            int requiredBeginRow = max(0, hintBeginRow);

            beta.StartEditingColumn(j, hintBeginRow, hintEndRow);

            int i;
            float score = NEG_INF;
            float thresholdScore = NEG_INF;
            float maxScore = NEG_INF;

            int beginRow, endRow = hintEndRow;
            for (i = endRow - 1;
                 i >= 0 && (score >= thresholdScore || i >= requiredBeginRow);
                 --i)
            {
                float thisMoveScore;
                score = NEG_INF;

                // Start:
                if (i == I && j == J)
                {
                    score = 0.0f;
                }

                // Incorporation:
                if (i < I && j < J)
                {
                    thisMoveScore = beta(i + 1, j + 1) + e.Inc(i, j);
                    score = C::Combine(score, thisMoveScore);
                }

                // Extra:
                if (i < I)
                {
                    thisMoveScore = beta(i + 1, j) + e.Extra(i, j);
                    score = C::Combine(score, thisMoveScore);
                }

                // Delete:
                if (j < J)
                {
                    thisMoveScore = beta(i, j + 1) + e.Del(i, j);
                    score = C::Combine(score, thisMoveScore);
                }

                // Merge:
                if ((this->movesAvailable_ & MERGE) && j < J - 1 && i < I)
                {
                    thisMoveScore = beta(i + 1, j + 2) + e.Merge(i, j);
                    score = C::Combine(score, thisMoveScore);
                }

                //  Save score
                beta.Set(i, j, score);

                if (score > maxScore)
                {
                    maxScore = score;
                    thresholdScore = maxScore - this->bandingOptions_.ScoreDiff;
                }
            }

            beginRow = i + 1;
            beta.FinishEditingColumn(j, beginRow, endRow);

            // Now, revise the hints to tell the caller where the mass of the
            // distribution really lived in this column.
            hintBeginRow = beginRow;
            for (i = endRow;
                 i > beginRow && beta(i - 1, j) < thresholdScore;
                 --i);
            hintEndRow = i;
        }
    }

    /// Calculate the recursion score by "stitching" together partial
    /// alpha and beta matrices.  alphaColumn, betaColumn, and
    /// absoluteColumn all refer to the same logical position in the
    /// template, but may have different values if, for instance,
    /// alpha here is a sub-range of the columns of the full alpha
    /// matrix.  Columns betaColumn and betaColumn + 1 of beta will be
    /// read; columns alphaColumn - 1 and alphaColumn - 2 of alpha
    /// will be read.
    template<typename M, typename E, typename C>
    float
    SimpleRecursor<M, E, C>::LinkAlphaBeta(const E& e,
                                           const M& alpha, int alphaColumn,
                                           const M& beta, int betaColumn,
                                           int absoluteColumn) const
    {
        const int I = e.ReadLength();

        assert(alphaColumn > 1 && absoluteColumn > 1);
        assert(absoluteColumn < e.TemplateLength());

        int usedBegin, usedEnd;
        boost::tie(usedBegin, usedEnd) = \
            RangeUnion(alpha.UsedRowRange(alphaColumn - 2),
                       alpha.UsedRowRange(alphaColumn - 1),
                       beta.UsedRowRange(betaColumn),
                       beta.UsedRowRange(betaColumn + 1));

        float v = NEG_INF, thisMoveScore;

        for (int i = usedBegin; i < usedEnd; i++)
        {
            if (i < I)
            {
                // Incorporate
                thisMoveScore = alpha(i, alphaColumn - 1) +
                                e.Inc(i, absoluteColumn - 1) +
                                beta(i + 1, betaColumn);
                v = C::Combine(v, thisMoveScore);

                // Merge (2 possible ways):
                thisMoveScore = alpha(i, alphaColumn - 2) +
                                e.Merge(i, absoluteColumn - 2) +
                                beta(i + 1, betaColumn);
                v = C::Combine(v, thisMoveScore);

                thisMoveScore = alpha(i, alphaColumn - 1) +
                                e.Merge(i, absoluteColumn - 1) +
                                beta(i + 1, betaColumn + 1);
                v = C::Combine(v, thisMoveScore);
            }

            // Delete:
            thisMoveScore = alpha(i, alphaColumn - 1) +
                            e.Del(i, absoluteColumn - 1) +
                            beta(i, betaColumn);
            v = C::Combine(v, thisMoveScore);
        }

        return v;
    }

    //
    // Reads: alpha(:, (beginColumn-2)..)
    //
    template<typename M, typename E, typename C>
    void
    SimpleRecursor<M, E, C>::ExtendAlpha(const E& e,
                                         const M& alpha, int beginColumn,
                                         M& ext, int numExtColumns) const
    {
        assert(numExtColumns >= 2);
        assert(alpha.Rows() == e.ReadLength() + 1 &&
               ext.Rows() == e.ReadLength() + 1);

        // The new template may not be the same length as the old template.
        // Just make sure that we have anough room to fill out the extend buffer
        assert(beginColumn + 1 < e.TemplateLength() + 1);
        assert(ext.Columns() >= numExtColumns);
        assert(beginColumn >= 2);

        for (int extCol = 0; extCol < numExtColumns; extCol++)
        {
            int j = beginColumn + extCol;
            int beginRow, endRow;

            //
            // If this extend is contained within the column bounds of
            // the original alpha, we use the row range that was
            // previously determined.  Otherwise start at alpha's last
            // UsedRow beginRow and go to the end.
            //
            if (j < alpha.Columns())
            {
                boost::tie(beginRow, endRow) = alpha.UsedRowRange(j);
            }
            else
            {
                beginRow = alpha.UsedRowRange(alpha.Columns() - 1).Begin;
                endRow = alpha.Rows();
            }

            ext.StartEditingColumn(extCol, beginRow, endRow);

            int i;
            float score;

            for (i = beginRow; i < endRow; i++)
            {
                float thisMoveScore;
                score = NEG_INF;

                // Incorporation:
                if (i > 0 && j > 0)
                {
                    float prev = extCol == 0 ?
                            alpha(i - 1, j - 1) :
                            ext(i - 1, extCol - 1);
                    thisMoveScore = prev + e.Inc(i - 1, j - 1);
                    score = C::Combine(score, thisMoveScore);
                }

                // Extra:
                if (i > 0)
                {
                    thisMoveScore = ext(i - 1, extCol) + e.Extra(i - 1, j);
                    score = C::Combine(score, thisMoveScore);
                }

                // Delete:
                if (j > 0)
                {
                    float prev = extCol == 0 ?
                            alpha(i, j - 1) :
                            ext(i, extCol - 1);
                    thisMoveScore = prev + e.Del(i, j - 1);
                    score = C::Combine(score, thisMoveScore);
                }

                // FIXME: is the merge code below incorrect for numExtColumns > 2?
                // Merge:
                if ((this->movesAvailable_ & MERGE) && j > 1 && i > 0)
                {
                    float prev = alpha(i - 1, j - 2);
                    thisMoveScore = prev + e.Merge(i - 1, j - 2);
                    score = C::Combine(score, thisMoveScore);
                }

                ext.Set(i, extCol, score);
            }
            assert (i == endRow);
            ext.FinishEditingColumn(extCol, beginRow, endRow);
        }
    }

    // Semantic: After ExtendBeta(B, j), we have
    //    ext(:, numExtColumns-1) = B'(:,j)
    //    ext(:, numExtColumns-2) = B'(:,j-1) ...
    //
    // Note: lastColumn is the numerically largest column number that
    // will be filled, but it is filled first since beta fill is done
    // backwards.
    //
    // Accesses B(:, ..(j+2))
    template<typename M, typename E, typename C>
    void
    SimpleRecursor<M, E, C>::ExtendBeta(const E& e,
                                        const M& beta, int lastColumn,
                                        M& ext, int numExtColumns,
                                        int lengthDiff) const
    {
        int I = beta.Rows() - 1;
        int J = beta.Columns() - 1;

        int lastExtColumn = numExtColumns - 1;

        assert(beta.Rows() == I + 1 &&
               ext.Rows() == I + 1);

        // The new template may not be the same length as the old template.
        // Just make sure that we have anough room to fill out the extend buffer
        assert(lastColumn + 2 <= J);
        assert(lastColumn >= 0);
        assert(ext.Columns() >= numExtColumns);

        for (int j = lastColumn; j > lastColumn - numExtColumns; j--)
        {
            int jp = j + lengthDiff;
            int extCol = lastExtColumn - (lastColumn - j);
            int beginRow, endRow;

            if (j < 0)
            {
                beginRow = 0;
                endRow = beta.UsedRowRange(0).End;
            }
            else
            {
                boost::tie(beginRow, endRow) = beta.UsedRowRange(j);
            }

            ext.StartEditingColumn(extCol, beginRow, endRow);

            int i;
            float score;

            for (i = endRow - 1;
                 i >= beginRow;
                 i--)
            {
                float thisMoveScore;
                score = NEG_INF;

                // Incorporation:
                if (i < I && j < J)
                {
                    float prev = (extCol == lastExtColumn) ?
                        beta(i + 1, j + 1) :
                        ext(i + 1, extCol + 1);
                    thisMoveScore = prev + e.Inc(i, jp);
                    score = C::Combine(score, thisMoveScore);
                }

                // Extra:
                if (i < I)
                {
                    thisMoveScore = ext(i + 1, extCol) + e.Extra(i, jp);
                    score = C::Combine(score, thisMoveScore);
                }

                // Delete:
                if (j < J)
                {
                    float prev = (extCol == lastExtColumn) ?
                        beta(i, j + 1) :
                        ext(i, extCol + 1);
                    thisMoveScore = prev + e.Del(i, jp);
                    score = C::Combine(score, thisMoveScore);
                }

                // FIXME: is the merge code below incorrect for numExtColumns > 2?
                // Merge:
                if ((this->movesAvailable_ & MERGE) && j < J - 1 && i < I)
                {
                    thisMoveScore = beta(i + 1, j + 2) + e.Merge(i, jp);
                    score = C::Combine(score, thisMoveScore);
                }

                ext.Set(i, extCol, score);
            }
            ext.FinishEditingColumn(extCol, beginRow, endRow);
        }
    }

    template<typename M, typename E, typename C>
    SimpleRecursor<M, E, C>::SimpleRecursor(int movesAvailable, const BandingOptions& banding)
        : detail::RecursorBase<M, E, C>(movesAvailable, banding)
    {}

    template class SimpleRecursor<DenseMatrixF,  QvEvaluator, detail::ViterbiCombiner>;
    template class SimpleRecursor<SparseMatrixF, QvEvaluator, detail::ViterbiCombiner>;
    template class SimpleRecursor<SparseMatrixF, QvEvaluator, detail::SumProductCombiner>;
    template class SimpleRecursor<SparseMatrixF, EdnaEvaluator, detail::SumProductCombiner>;
}