#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/expr.h"

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

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

#include "s2s/dynet/dict.h"
#include "s2s/corpus/corpora.hpp"
#include "s2s/corpus/comp.hpp"
#include "s2s/corpus/options.hpp"
#include "s2s/corpus/batch.hpp"

#ifndef INCLUDE_GUARD_S2S_TEST_ENCDEC_HPP
#define INCLUDE_GUARD_S2S_TEST_ENCDEC_HPP

namespace s2s {

    void test_encdec(s2s_options &opts, dicts &dicts, parallel_corpus &para_corp, std::unique_ptr<encoder_decoder_attn>& encdec){
        dynet::ParameterCollection model;
        dynet::Trainer* trainer;
        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->learning_rate = learning_rate;
        trainer->clipping_enabled = opts.clipping_enabled;
        trainer->clip_threshold = opts.clip_threshold;
        unsigned int batch_cnt = 0;
        batch batch_local(opts, dicts);
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        while(para_corp.next_batch_para(batch_local)){
            // network construction
            dynet::ComputationGraph cg;
            std::vector<dynet::Expression> errs_att;
            std::vector<dynet::Expression> errs_out;
            encdec->encoder(batch_local, cg);
            // checking distributions of the higher order attention module
            for (unsigned int t = 1; t < batch_local.sent_label.size(); ++t) {
                dynet::Expression i_att_t = encdec->decoder_attention(cg, t, batch_local.sent_label[t-1]);
                if(opts.guided_alignment == true){
                    for(unsigned int i = 0; i < batch_local.align.at(t).size(); i++){
                        assert(0 <= batch_local.align.at(t).at(i) && batch_local.align.at(t).at(i) < batch_local.sent_word.size());
                    }
                    dynet::Expression i_err = pickneglogsoftmax(i_att_t, batch_local.align.at(t));
                    errs_att.push_back(i_err);
                }
                dynet::Expression i_out_t = encdec->decoder_output(cg, t, i_att_t);
                dynet::Expression i_err = pickneglogsoftmax(i_out_t, batch_local.sent_label[t]);
                errs_out.push_back(i_err);
            }
            dynet::Expression i_nerr_out = sum_batches(sum(errs_out)) / (float)(batch_local.batch_size());
            float loss_out = as_scalar(cg.forward(i_nerr_out));
            float loss_att = 0.f;
            dynet::Expression i_nerr_all;
            if(opts.guided_alignment == true){
                dynet::Expression i_nerr_att = sum_batches(sum(errs_att)) / (float)(batch_local.batch_size());
                loss_att = as_scalar(cg.incremental_forward(i_nerr_att));
                i_nerr_all = i_nerr_out + opts.guided_alignment_weight * i_nerr_att;
            }else{
                i_nerr_all = i_nerr_out;
            }
            float loss_all = as_scalar(cg.incremental_forward(i_nerr_all));
            // checking values
            {
                float inf = std::numeric_limits<float>::infinity();
                float nan = std::numeric_limits<float>::quiet_NaN();
                assert(-inf < loss_out && loss_out < inf);
                assert(-inf < loss_att && loss_att < inf);
                assert(-inf < loss_all && loss_all < inf);
                assert(loss_out != nan);
                assert(loss_att != nan);
                assert(loss_all != nan);
            }
            cg.backward(i_nerr_all);
            //cg.print_graphviz();
            trainer->update();
            std::cerr << "batch id: " << batch_cnt << "\tloss (all): " << loss_all << "\tloss (out): " << loss_out << "\tloss (att): " << loss_att << std::endl;
            batch_cnt++;
        }
        assert(batch_cnt > 0);
        // greedy decoding
        batch_cnt = 0;
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        while(para_corp.next_batch_para(batch_local)){
            std::vector<hypothesis<encoder_decoder_attn>> hyp_stack(1);
            s2s::greedy_decode_ensemble(batch_local, hyp_stack, encdec, dicts, opts);
            std::cerr << "batch id: " << batch_cnt << std::endl;
            assert(hyp_stack.size() == 1);
            batch_cnt++;
        }
        assert(batch_cnt > 0);
        // beam decoding
        batch_cnt = 0;
        opts.max_batch_train = 1;
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        opts.beam_size = 10;
        while(para_corp.next_batch_para(batch_local)){
            std::vector<hypothesis<encoder_decoder_attn>> hyp_stack(1);
            s2s::beam_decode_ensemble(batch_local, hyp_stack, encdec, dicts, opts);
            std::cerr << "batch id: " << batch_cnt << std::endl;
            if(batch_local.sent_word.size() >= 4 && hyp_stack.size() < opts.beam_size){
                assert(false);
            }
            if(hyp_stack.size() > opts.beam_size){
                assert(false);
            }
            batch_cnt++;
        }
        assert(batch_cnt > 0);
    }

    void test_encdec(s2s_options &opts, dicts &dicts, parallel_corpus &para_corp, std::unique_ptr<encoder_decoder_base>& encdec){
        dynet::ParameterCollection model;
        dynet::Trainer* trainer;
        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->learning_rate = learning_rate;
        trainer->clipping_enabled = opts.clipping_enabled;
        trainer->clip_threshold = opts.clip_threshold;
        unsigned int batch_cnt = 0;
        batch batch_local(opts, dicts);
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        while(para_corp.next_batch_para(batch_local)){
            // network construction
            dynet::ComputationGraph cg;
            std::vector<dynet::Expression> errs_att;
            std::vector<dynet::Expression> errs_out;
            encdec->encoder(batch_local, cg);
            // checking distributions of the higher order attention module
            for (unsigned int t = 1; t < batch_local.sent_label.size(); ++t) {
                dynet::Expression i_out_t = encdec->decoder_output(cg, batch_local.sent_label[t-1], t);
                dynet::Expression i_err = pickneglogsoftmax(i_out_t, batch_local.sent_label[t]);
                errs_out.push_back(i_err);
            }
            dynet::Expression i_nerr_out = sum_batches(sum(errs_out)) / (float)(batch_local.batch_size());
            float loss_out = as_scalar(cg.forward(i_nerr_out));
            // checking values
            {
                float inf = std::numeric_limits<float>::infinity();
                float nan = std::numeric_limits<float>::quiet_NaN();
                assert(-inf < loss_out && loss_out < inf);
                assert(loss_out != nan);
            }
            cg.backward(i_nerr_out);
            //cg.print_graphviz();
            trainer->update();
            std::cerr << "batch id: " << batch_cnt << "\tloss (out): " << loss_out << std::endl;
            batch_cnt++;
        }
        assert(batch_cnt > 0);
        // greedy decoding
        batch_cnt = 0;
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        while(para_corp.next_batch_para(batch_local)){
            std::vector<hypothesis<encoder_decoder_base>> hyp_stack(1);
            s2s::greedy_decode_ensemble(batch_local, hyp_stack, encdec, dicts, opts);
            std::cerr << "batch id: " << batch_cnt << std::endl;
            assert(hyp_stack.size() == 1);
            batch_cnt++;
        }
        assert(batch_cnt > 0);
        // beam decoding
        batch_cnt = 0;
        opts.max_batch_train = 1;
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        opts.beam_size = 10;
        while(para_corp.next_batch_para(batch_local)){
            std::vector<hypothesis<encoder_decoder_base>> hyp_stack(1);
            s2s::beam_decode_ensemble(batch_local, hyp_stack, encdec, dicts, opts);
            std::cerr << "batch id: " << batch_cnt << std::endl;
            if(batch_local.sent_word.size() >= 4 && hyp_stack.size() < opts.beam_size){
                assert(false);
            }
            if(hyp_stack.size() > opts.beam_size){
                assert(false);
            }
            batch_cnt++;
        }
        assert(batch_cnt > 0);
    }

    void test_encdec(s2s_options &opts, dicts &dicts, parallel_corpus &para_corp, std::unique_ptr<encoder_decoder_slahan>& encdec){
        dynet::ParameterCollection model;
        dynet::Trainer* trainer;
        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->learning_rate = learning_rate;
        trainer->clipping_enabled = opts.clipping_enabled;
        trainer->clip_threshold = opts.clip_threshold;
        unsigned int batch_cnt = 0;
        batch batch_local(opts, dicts);
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        while(para_corp.next_batch_para(batch_local)){
            // network construction
            dynet::ComputationGraph cg;
            std::vector<dynet::Expression> errs_att;
            std::vector<dynet::Expression> errs_out;
            encdec->encoder(batch_local, cg);
            std::vector<dynet::Expression> h_att_self = encdec->attention(cg, batch_local);
            encdec->recursive_attention(cg, h_att_self);
            // checking distributions of the higher order attention module
            if(opts.self_attn_type == "given"){
                std::vector<std::vector<std::vector<unsigned int> > > h_att_gold(opts.rec_attn_dep.back());
                h_att_gold[0] = batch_local.align;
                for(unsigned int d = 1; d < opts.rec_attn_dep.back(); d++){
                    h_att_gold[d].resize(batch_local.align.size());
                    std::vector<dynet::Expression> errs_att_local;
                    for (unsigned int t = 0; t < batch_local.align.size(); ++t) {
                        h_att_gold[d][t].resize(batch_local.batch_size());
                        for(unsigned int batch_id = 0; batch_id < batch_local.batch_size(); batch_id++){
                            h_att_gold[d][t][batch_id] =  h_att_gold[0][h_att_gold.at(d - 1).at(t).at(batch_id)][batch_id] ;
                        }
                        dynet::Expression i_err_att = pickneglogsoftmax(log(encdec->f_alpha_d[d][t]), h_att_gold[d][t]);
                        errs_att_local.push_back(i_err_att);
                    }
                    dynet::Expression i_nerr_att = sum_batches(sum(errs_att_local)) / (float)(batch_local.batch_size());
                    float loss_att = as_scalar(cg.incremental_forward(i_nerr_att));
                    assert(loss_att == 0.f);
                }
            }
            for (unsigned int t = 1; t < batch_local.sent_label.size(); ++t) {
                for(unsigned int batch_id = 0; batch_id < batch_local.batch_size(); batch_id++){
                    if(batch_local.align.at(t).at(batch_id) >= batch_local.sent_label.size()){
                        std::cerr << "Dependency range is wrong." << std::endl;
                        assert(false);
                    }
                }
                // calculate attention loss
                dynet::Expression i_err_att = pickneglogsoftmax(h_att_self[t], batch_local.align.at(t));
                errs_att.push_back(i_err_att);
                // calculate output loss
                dynet::Expression i_out_t = encdec->decoder_output(cg, t, batch_local.sent_label[t-1]);
                dynet::Expression i_err_out = pickneglogsoftmax(i_out_t, batch_local.sent_label[t]);
                errs_out.push_back(i_err_out);
            }
            dynet::Expression i_nerr_out = sum_batches(sum(errs_out)) / (float)(batch_local.batch_size());
            float loss_out = as_scalar(cg.forward(i_nerr_out));
            dynet::Expression i_nerr_att = sum_batches(sum(errs_att)) / (float)(batch_local.batch_size());
            float loss_att = as_scalar(cg.incremental_forward(i_nerr_att));
            dynet::Expression i_nerr_all = i_nerr_out + opts.guided_alignment_weight * i_nerr_att;
            float loss_all = as_scalar(cg.incremental_forward(i_nerr_all));
            // checking values
            {
                float inf = std::numeric_limits<float>::infinity();
                float nan = std::numeric_limits<float>::quiet_NaN();
                assert(-inf < loss_out && loss_out < inf);
                assert(-inf < loss_att && loss_att < inf);
                assert(-inf < loss_all && loss_all < inf);
                assert(loss_out != nan);
                assert(loss_att != nan);
                assert(loss_all != nan);
            }
            cg.backward(i_nerr_all);
            //cg.print_graphviz();
            trainer->update();
            std::cerr << "batch id: " << batch_cnt << "\tloss (all): " << loss_all << "\tloss (out): " << loss_out << "\tloss (att): " << loss_att << std::endl;
            batch_cnt++;
        }
        assert(batch_cnt > 0);
        // greedy decoding
        batch_cnt = 0;
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        while(para_corp.next_batch_para(batch_local)){
            std::vector<hypothesis<encoder_decoder_slahan>> hyp_stack(1);
            s2s::greedy_decode_ensemble(batch_local, hyp_stack, encdec, dicts, opts);
            std::cerr << "batch id: " << batch_cnt << std::endl;
            assert(hyp_stack.size() == 1);
            batch_cnt++;
        }
        assert(batch_cnt > 0);
        // beam decoding
        batch_cnt = 0;
        opts.max_batch_train = 1;
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        opts.beam_size = 10;
        while(para_corp.next_batch_para(batch_local)){
            std::vector<hypothesis<encoder_decoder_slahan>> hyp_stack(1);
            s2s::beam_decode_ensemble(batch_local, hyp_stack, encdec, dicts, opts);
            std::cerr << "batch id: " << batch_cnt << std::endl;
            if(batch_local.sent_word.size() >= 4 && hyp_stack.size() < opts.beam_size){
                assert(false);
            }
            if(hyp_stack.size() > opts.beam_size){
                assert(false);
            }
            batch_cnt++;
        }
        assert(batch_cnt > 0);
    }

    void test_encdec(s2s_options &opts, dicts &dicts, parallel_corpus &para_corp, std::unique_ptr<encoder_decoder_lstm>& encdec){
        dynet::ParameterCollection model;
        dynet::Trainer* trainer;
        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->learning_rate = learning_rate;
        trainer->clipping_enabled = opts.clipping_enabled;
        trainer->clip_threshold = opts.clip_threshold;
        unsigned int batch_cnt = 0;
        batch batch_local(opts, dicts);
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        while(para_corp.next_batch_para(batch_local)){
            // network construction
            dynet::ComputationGraph cg;
            std::vector<dynet::Expression> errs_att;
            std::vector<dynet::Expression> errs_out;
            encdec->encoder(batch_local, cg);
            // checking distributions of the higher order attention module
            for (unsigned int t = 1; t < batch_local.sent_label.size(); ++t) {
                std::vector<dynet::real> bit_features = encdec->bit_features(t, batch_local.sent_label, batch_local.align[t]);
                dynet::Expression i_out_t = encdec->decoder_output(cg, t, batch_local.align[t], bit_features);
                dynet::Expression i_err = pickneglogsoftmax(i_out_t, batch_local.sent_label[t]);
                errs_out.push_back(i_err);
            }
            dynet::Expression i_nerr_out = sum_batches(sum(errs_out)) / (float)(batch_local.batch_size());
            float loss_out = as_scalar(cg.forward(i_nerr_out));
            // checking values
            {
                float inf = std::numeric_limits<float>::infinity();
                float nan = std::numeric_limits<float>::quiet_NaN();
                assert(-inf < loss_out && loss_out < inf);
                assert(loss_out != nan);
            }
            cg.backward(i_nerr_out);
            //cg.print_graphviz();
            trainer->update();
            std::cerr << "batch id: " << batch_cnt << "\tloss (out): " << loss_out << std::endl;
            batch_cnt++;
        }
        assert(batch_cnt > 0);
        // greedy decoding
        batch_cnt = 0;
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        while(para_corp.next_batch_para(batch_local)){
            std::vector<hypothesis<encoder_decoder_lstm>> hyp_stack(1);
            s2s::greedy_decode_ensemble(batch_local, hyp_stack, encdec, dicts, opts);
            std::cerr << "batch id: " << batch_cnt << std::endl;
            assert(hyp_stack.size() == 1);
            batch_cnt++;
        }
        assert(batch_cnt > 0);
        // beam decoding
        batch_cnt = 0;
        opts.max_batch_train = 1;
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        opts.beam_size = 10;
        while(para_corp.next_batch_para(batch_local)){
            std::vector<hypothesis<encoder_decoder_lstm>> hyp_stack(1);
            s2s::beam_decode_ensemble(batch_local, hyp_stack, encdec, dicts, opts);
            std::cerr << "batch id: " << batch_cnt << std::endl;
            if(batch_local.sent_word.size() >= 4 && hyp_stack.size() < opts.beam_size){
                assert(false);
            }
            if(hyp_stack.size() > opts.beam_size){
                assert(false);
            }
            batch_cnt++;
        }
        assert(batch_cnt > 0);
    }

    void test_encdec(s2s_options &opts, dicts &dicts, parallel_corpus &para_corp, std::unique_ptr<tagger>& encdec){
        dynet::ParameterCollection model;
        dynet::Trainer* trainer;
        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->learning_rate = learning_rate;
        trainer->clipping_enabled = opts.clipping_enabled;
        trainer->clip_threshold = opts.clip_threshold;
        unsigned int batch_cnt = 0;
        batch batch_local(opts, dicts);
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        while(para_corp.next_batch_para(batch_local)){
            // network construction
            dynet::ComputationGraph cg;
            std::vector<dynet::Expression> errs_att;
            std::vector<dynet::Expression> errs_out;
            encdec->encoder(batch_local, cg);
            // checking distributions of the higher order attention module
            for (unsigned int t = 0; t < batch_local.sent_label.size(); ++t) {
                dynet::Expression i_out_t = encdec->output(cg, t);
                dynet::Expression i_err = pickneglogsoftmax(i_out_t, batch_local.sent_label[t]);
                errs_out.push_back(i_err);
            }
            dynet::Expression i_nerr_out = sum_batches(sum(errs_out)) / (float)(batch_local.batch_size());
            float loss_out = as_scalar(cg.forward(i_nerr_out));
            // checking values
            {
                float inf = std::numeric_limits<float>::infinity();
                float nan = std::numeric_limits<float>::quiet_NaN();
                assert(-inf < loss_out && loss_out < inf);
                assert(loss_out != nan);
            }
            cg.backward(i_nerr_out);
            //cg.print_graphviz();
            trainer->update();
            std::cerr << "batch id: " << batch_cnt << "\tloss (out): " << loss_out << std::endl;
            batch_cnt++;
        }
        assert(batch_cnt > 0);
        // greedy decoding
        batch_cnt = 0;
        para_corp.reset_index();
        para_corp.set_para_batch_order(opts.max_batch_train, opts.src_tok_lim_train, opts.trg_tok_lim_train, opts.batch_type_train);
        while(para_corp.next_batch_para(batch_local)){
            std::vector<hypothesis<tagger>> hyp_stack(1);
            s2s::greedy_decode_ensemble(batch_local, hyp_stack, encdec, dicts, opts);
            std::cerr << "batch id: " << batch_cnt << std::endl;
            assert(hyp_stack.size() == 1);
            batch_cnt++;
        }
        assert(batch_cnt > 0);
    }

};

#endif


// 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.

#include "read_end_bias.hpp"

#include <algorithm>
#include <iterator>
#include <random>
#include <functional>
#include <cmath>
#include <cassert>

#include <boost/variant.hpp>

#include "io/variant/vcf_record.hpp"
#include "basics/aligned_read.hpp"
#include "core/types/allele.hpp"
#include "utils/maths.hpp"
#include "../facets/samples.hpp"
#include "../facets/alleles.hpp"
#include "../facets/read_assignments.hpp"

namespace octopus { namespace csr {

const std::string ReadEndBias::name_ = "REB";

std::unique_ptr<Measure> ReadEndBias::do_clone() const
{
    return std::make_unique<ReadEndBias>(*this);
}

Measure::ValueType ReadEndBias::get_value_type() const
{
    return double {};
}

void ReadEndBias::do_set_parameters(std::vector<std::string> params)
{
    if (params.size() != 1) {
        throw BadMeasureParameters {this->name(), "only has one parameter (end fraction)"};
    }
    try {
        end_fraction_ = boost::lexical_cast<decltype(end_fraction_)>(params.front());
    } catch (const boost::bad_lexical_cast&) {
        throw BadMeasureParameters {this->name(), "given parameter \"" + params.front() + "\" cannot be parsed"};
    }
    if (end_fraction_ < 0 || end_fraction_ > 1) {
        throw BadMeasureParameters {this->name(), "end fraction must be between 0 and 1"};
    }
}

namespace {

struct EndDefinition
{
    double end_fraction = 0.03;
};

struct PositionCounts
{
    unsigned end, middle;
};

bool overlaps_rhs(const Allele& allele, const AlignedRead& read)
{
    using D = GenomicRegion::Distance;
    return overlaps(allele, expand_lhs(mapped_region(read), -static_cast<D>(region_size(read) / 2)));
}

auto head_region(const AlignedRead& read, const unsigned tail_bases)
{
    if (is_forward_strand(read)) {
        return octopus::head_region(read, tail_bases);
    } else {
        return octopus::tail_region(read, tail_bases);
    }
}

auto tail_region(const AlignedRead& read, const unsigned tail_bases)
{
    if (is_forward_strand(read)) {
        return octopus::tail_region(read, tail_bases);
    } else {
        return  octopus::head_region(read, tail_bases);
    }
}

bool is_in_end(const Allele& allele, const AlignedRead& read, const EndDefinition end_def = EndDefinition {})
{
    assert(contains(read, allele));
    const auto end_bases = std::max(static_cast<unsigned>(sequence_size(read) * end_def.end_fraction), 1u);
    return contains(head_region(read, end_bases), allele) || contains(tail_region(read, end_bases), allele);
}

auto compute_head_tail_counts(const Allele& allele, const ReadRefSupportSet& support, const EndDefinition end_def = EndDefinition {})
{
    PositionCounts result {};
    for (const auto& read : support) {
        if (contains(read, allele)) {
            if (is_in_end(allele, read)) {
                ++result.end;
            } else {
                ++result.middle;
            }
        }
    }
    return result;
}

double calculate_tail_bias(const PositionCounts counts, const double tolerance = 0.5, const double prior = 1.0)
{
    assert(tolerance > 0.0 && tolerance < 1.0);
    return maths::beta_cdf(counts.middle + prior, counts.end + prior, tolerance);
}

double calculate_tail_bias(const Allele& allele, const ReadRefSupportSet& support, const EndDefinition end_def = EndDefinition {})
{
    const auto counts = compute_head_tail_counts(allele, support);
    return calculate_tail_bias(counts, std::min(3 * end_def.end_fraction, 0.5));
}

double calculate_max_tail_bias(const std::vector<Allele>& alleles, const AlleleSupportMap& support, const EndDefinition end_def = EndDefinition {})
{
    double result {0};
    for (const auto& allele : alleles) {
        const auto support_set_itr = support.find(allele);
        if (support_set_itr != std::cend(support)) {
            auto bias = calculate_tail_bias(allele, support_set_itr->second, end_def);
            result = std::max(result, bias);
        }
    }
    return result;
}

} // namespace

Measure::ResultType ReadEndBias::do_evaluate(const VcfRecord& call, const FacetMap& facets) const
{
    const auto& samples = get_value<Samples>(facets.at("Samples"));
    const auto& alleles = get_value<Alleles>(facets.at("Alleles"));
    const auto& assignments = get_value<ReadAssignments>(facets.at("ReadAssignments")).alleles;
    Array<ValueType> result {};
    result.reserve(samples.size());
    const EndDefinition end_def {end_fraction_};
    for (const auto& sample : samples) {
        result.emplace_back(calculate_max_tail_bias(get_called(alleles, call, sample), assignments.at(sample), end_def));
    }
    return result;
}

Measure::ResultCardinality ReadEndBias::do_cardinality() const noexcept
{
    return ResultCardinality::samples;
}

const std::string& ReadEndBias::do_name() const
{
    return name_;
}

std::string ReadEndBias::do_describe() const
{
    return "Probability allele occurs at the end (head or tail) of supporting reads";
}

std::vector<std::string> ReadEndBias::do_requirements() const
{
    return {"Samples", "Alleles", "ReadAssignments"};
}

bool ReadEndBias::is_equal(const Measure& other) const noexcept
{
    return end_fraction_ == static_cast<const ReadEndBias&>(other).end_fraction_;
}

} // namespace csr
} // namespace octopus


//Tencent is pleased to support the open source community by making FeatherCNN available.

//Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.

//Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
//in compliance with the License. You may obtain a copy of the License at
//
//https://opensource.org/licenses/BSD-3-Clause
//
//Unless required by applicable law or agreed to in writing, software distributed
//under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
//CONDITIONS OF ANY KIND, either express or implied. See the License for the
//specific language governing permissions and limitations under the License.

#include <booster/booster.h>
#include <booster/depthwise.h>
#include <booster/generic_kernels.h>
#include <booster/sgemm.h>
#include <booster/sgeconv.h>
#include <booster/helper.h>
#include <booster/winograd_kernels.h>

#include <string.h>

namespace booster
{
//NAIVE Methods
int NAIVE_GetBufferSize(ConvParam *param, int* buffer_size, int* processed_kernel_size)
{
    *buffer_size = param->input_channels * param->output_h * param->output_w * param->kernel_h * param->kernel_w;
    *processed_kernel_size = param->input_channels * param->output_channels * param->kernel_h * param->kernel_w;
    return 0;
}

int NAIVE_Init(ConvParam *param, float* processed_kernel, float* kernel)
{
    memcpy(processed_kernel, kernel, sizeof(float) * param->output_channels * param->input_channels * param->kernel_h * param->kernel_w);
    return 0;
}

int NAIVE_Forward(ConvParam *param, float* output, float* input, float* processed_kernel, float* buffer, float* bias_arr)
{
    const int M = param->output_channels;
    const int N = param->output_h * param->output_w;
    const int K = param->input_channels * param->kernel_h * param->kernel_w;
    im2col(param, buffer, input);
    naive_sgemm(M, N, K, processed_kernel, buffer, output);
    if (param->bias_term)
    {
        size_t out_stride = param->output_w * param->output_h;
        for (int i = 0; i < param->output_channels; ++i)
        {
            float bias = bias_arr[i];
            for (int j = 0; j < out_stride; ++j)
            {
                output[out_stride * i + j] = output[out_stride * i + j] + bias;
            }
        }
    }
    return 0;
}

//IM2COL Methods
int IM2COL_GetBufferSize(ConvParam *param, int* buffer_size, int* processed_kernel_size)
{
    const int nc = 160;
    const int kc = 320;
    *buffer_size = (kc + 6) * nc + param->input_channels * param->output_h * param->output_w * param->kernel_h * param->kernel_w;
    *processed_kernel_size = param->input_channels * param->output_channels * param->kernel_h * param->kernel_w;
    return 0;
}

int IM2COL_Init(ConvParam *param, float* processed_kernel, float* kernel)
{
    const int nc = 160;
    const int kc = 320;
    const int M = param->output_channels;
    const int K = param->input_channels * param->kernel_h * param->kernel_w;
    packed_sgemm_init<6>(M, K, kc, processed_kernel, kernel, K);
    return 0;
}

int IM2COL_Forward(ConvParam *param, float* output, float* input, float* processed_kernel, float* buffer, float* bias_arr)
{
    const int nc = 160;
    const int kc = 320;
    const int offset = (kc + 6) * nc;
    const int M = param->output_channels;
    const int N = param->output_h * param->output_w;
    const int K = param->input_channels * param->kernel_h * param->kernel_w;
    float* im2col_buf = buffer + offset;
    im2col(param, im2col_buf, input);
    if ((!param->bias_term) && (param->activation == None))
        packed_sgemm_activation<false, false>(M, N, K, processed_kernel, im2col_buf, N, output, N, nc, kc, bias_arr, 1, buffer);
    else if ((param->bias_term) && (param->activation == None))
        packed_sgemm_activation<true,  false>(M, N, K, processed_kernel, im2col_buf, N, output, N, nc, kc, bias_arr, 1, buffer);
    else if ((!param->bias_term) && (param->activation == ReLU))
        packed_sgemm_activation<false,  true>(M, N, K, processed_kernel, im2col_buf, N, output, N, nc, kc, bias_arr, 1, buffer);
    else if ((param->bias_term) && (param->activation == ReLU))
        packed_sgemm_activation<true,   true>(M, N, K, processed_kernel, im2col_buf, N, output, N, nc, kc, bias_arr, 1, buffer);
    return 0;
}

//SGECONV Methods
int SGECONV_GetBufferSize(ConvParam *param, int* buffer_size, int* processed_kernel_size)
{
    return 0;
}

int SGECONV_Init(ConvParam *param, float* processed_kernel, float* kernel)
{
    return 0;
}

int SGECONV_Forward(ConvParam *param, float* output, float* input, float* processed_kernel, float* buffer, float* bias_arr)
{
    return 0;
}

//DEPTHWISE Methods
int DEPTHWISE_GetBufferSize(ConvParam *param, int* buffer_size, int* processed_kernel_size)
{
	ConvParam padded_param = *param;
	padded_param.AssignPaddedDim();
	*buffer_size = param->input_channels * padded_param.input_h * padded_param.input_w;
    *processed_kernel_size = param->group * param->kernel_h * param->kernel_w;
    return 0;
}

int DEPTHWISE_Init(ConvParam *param, float* processed_kernel, float* kernel)
{
    memcpy(processed_kernel, kernel, sizeof(float) * param->group * param->kernel_h * param->kernel_w);
    return 0;
}

int DEPTHWISE_Forward(ConvParam *param, float* output, float* input, float* processed_kernel, float* buffer, float* bias_arr)
{
    void (*dwConv)(float *, float *, int, int, int, int, int, float *, int, int, int, int, float *);
    if (param->bias_term && (param->activation == ReLU))
        dwConv = dwConv_template<true, true>;
    else if (param->bias_term && !(param->activation == ReLU))
        dwConv = dwConv_template<true, false>;
    else if (!param->bias_term && (param->activation == ReLU))
        dwConv = dwConv_template<false, true>;
    else if (!param->bias_term && !(param->activation == ReLU))
        dwConv = dwConv_template<false, false>;

    if (param->pad_left > 0 || param->pad_right > 0 || param->pad_top > 0 || param->pad_bottom > 0)
    {
		ConvParam padded_param = *param;
		padded_param.AssignPaddedDim();
        pad_input(buffer, input, param->input_channels, param->input_w, param->input_h, param->pad_left,
                  param->pad_top, param->pad_right, param->pad_bottom);
		dwConv(output, buffer, param->input_channels, padded_param.input_w, padded_param.input_h, param->stride_w, param->stride_h, processed_kernel, param->kernel_w, param->kernel_h, param->group, 1, bias_arr);
    }
    else
        dwConv(output, input, param->input_channels, param->input_w, param->input_h, param->stride_w, param->stride_h, processed_kernel, param->kernel_w, param->kernel_h, param->group, 1, bias_arr);
    return 0;
}
//WINOGRADF23 Methods
int WINOGRADF23_GetBufferSize(ConvParam *param, int* buffer_size, int* processed_kernel_size)
{
    return 0;
}

int WINOGRADF23_Init(ConvParam *param, float* processed_kernel, float* kernel)
{
    return 0;
}

int WINOGRADF23_Forward(ConvParam *param, float* output, float* input, float* processed_kernel, float* buffer, float* bias_arr)
{
    return 0;
}

//WINOGRADF63 Methods
int WINOGRADF63_GetBufferSize(ConvParam *param, int* buffer_size, int* processed_kernel_size)
{
    int num_threads = 1;
    ConvParam padded_param = *param;
    padded_param.AssignPaddedDim();
    size_t packArraySize = getPackArraySize_F6x6_3x3(padded_param.input_channels, num_threads);
    int nRowBlocks = (padded_param.input_w + 3) / 6;
    int nColBlocks = (padded_param.input_h + 3) / 6;
    int nBlocks = nRowBlocks * nColBlocks;

    size_t winograd_mem_size = 0;
    winograd_mem_size += 64 * nBlocks * padded_param.input_channels;    //VT
    winograd_mem_size += 64 * nBlocks * padded_param.output_channels;   //WT
    winograd_mem_size += packArraySize;                    //WT
    winograd_mem_size += padded_param.input_w * padded_param.input_h * padded_param.input_channels; //Padded Input

    *buffer_size = winograd_mem_size;
    *processed_kernel_size = 64 * padded_param.input_channels * padded_param.output_channels;
    return 0;
}

int WINOGRADF63_Init(ConvParam *param, float* processed_kernel, float* kernel)
{
    transformKernel_F6x6_3x3(processed_kernel, kernel, param->input_channels, param->output_channels);
    return 0;
}

int WINOGRADF63_Forward(ConvParam *param, float* output, float* input, float* processed_kernel, float* buffer, float* bias_arr)
{
    const size_t inputw = param->input_w + param->pad_left + param->pad_right;
    const size_t inputh = param->input_h + param->pad_top + param->pad_bottom;
    const int nRowBlocks = (inputw + 3) / 6;
    const int nColBlocks = (inputh + 3) / 6;
    const int nBlocks = nRowBlocks * nColBlocks;

    //Get addresses
    float *VT = buffer;
    float *WT = VT + 64 * nBlocks * param->input_channels;                      //Offset by sizeof VT
    float *padded_input = WT + 64 * nBlocks * param->output_channels;           //Offset by sizeof WT
    float *pack_array = padded_input + inputw * inputh * param->input_channels; //Offset by sizeof WT
    pad_input(padded_input, input, param->input_channels, param->input_w, param->input_h, param->pad_left, param->pad_top, param->pad_right, param->pad_bottom);
    WinogradOutType out_type;
    if ((!param->bias_term) && (param->activation == None))
        out_type = Nothing;
    else if ((param->bias_term) && (param->activation == None))
        out_type = Bias;
    else if ((!param->bias_term) && (param->activation == ReLU))
        out_type = Relu;
    else if ((param->bias_term) && (param->activation == ReLU))
        out_type = BiasReLU;
    winogradNonFusedTransform_F6x6_3x3(output, param->output_channels, WT, VT, processed_kernel, padded_input, param->input_channels, inputh, inputw, out_type, bias_arr, pack_array, 1);
    return 0;
}

//WINOGRADF63 Methods
int WINOGRADF63FUSED_GetBufferSize(ConvParam *param, int* buffer_size, int* processed_kernel_size)
{
    int num_threads = 1;
    const int cache_block = 48;
    size_t winograd_mem_size = 0;
    winograd_mem_size += cache_block * 64 * param->input_channels; //fused VT
    winograd_mem_size += cache_block * 64 * param->output_channels * 4; //fused WT

    *buffer_size = winograd_mem_size;
    *processed_kernel_size = 64 * param->input_channels * param->output_channels;
    return 0;
}

int WINOGRADF63FUSED_Init(ConvParam *param, float* processed_kernel, float* kernel)
{
    Winograd_F63_Fused::transformKernel_F6x6_3x3(processed_kernel, kernel, param->input_channels, param->output_channels);
    return 0;
}

int WINOGRADF63FUSED_Forward(ConvParam *param, float* output, float* input, float* processed_kernel, float* buffer, float* bias_arr)
{
    // printf("Fused Winograd Forward\n");
    // param->LogParams("FORWARD_TEST");
    if (!param->bias_term && param->activation == None)
    {
        Winograd_F63_Fused::WinogradF63Fused<false, false>(param, output, input, processed_kernel, bias_arr, buffer, NULL);
    }
    else if (!param->bias_term && param->activation == ReLU)
    {
        Winograd_F63_Fused::WinogradF63Fused<true, false>(param, output, input, processed_kernel, bias_arr, buffer, NULL);
    }
    else if (param->bias_term && param->activation == None)
    {
        Winograd_F63_Fused::WinogradF63Fused<false, true>(param, output, input, processed_kernel, bias_arr, buffer, NULL);
    }
    else if (param->bias_term && param->activation == ReLU)
    {
        Winograd_F63_Fused::WinogradF63Fused<true, true>(param, output, input, processed_kernel, bias_arr, buffer, NULL);
    }
    return 0;
}

//Class wrappers
ConvBooster::ConvBooster()
    : GetBufferSize(NULL), Init(NULL), Forward(NULL)
{
}

//Conditional algo selecter
int ConvBooster::SelectAlgo(ConvParam* param)
{
    if (param->group == param->input_channels)
    {
        this->algo = DEPTHWISE;
    }
    else if (param->group == 1 && param->kernel_h == 3 && param->kernel_w == 3 && param->stride_h == 1 && param->stride_w == 1  && param->input_h > 8 && param->input_w > 8 &&  param->output_channels % 4 == 0 && param->input_channels % 4 == 0)
    {
        //this->algo = WINOGRADF63FUSED;
        this->algo = WINOGRADF63;
    }
    else if (param->group == 1 && param->kernel_w > 1 && param->kernel_h > 1)
    {
        //this->algo = SGECONV;
        this->algo = IM2COL;
    }
    else if (param->group == 1)
    {
        this->algo = IM2COL;
        //this->algo = NAIVE;
    }
    else
    {
        LOGE("Partial group conv is not yet supported. If you need it, try develop your own im2col method.");
        return -1;
    }
    return this->SetFuncs();
}

//Force algo selecter
int ConvBooster::ForceSelectAlgo(ConvAlgo algo)
{
    this->algo = algo;
    return this->SetFuncs();
}

int ConvBooster::SetFuncs()
{
    switch (this->algo)
    {
        case NAIVE:
            this->GetBufferSize = NAIVE_GetBufferSize;
            this->Init = NAIVE_Init;
            this->Forward = NAIVE_Forward;
            return 0;
        case IM2COL:
            this->GetBufferSize = IM2COL_GetBufferSize;
            this->Init = IM2COL_Init;
            this->Forward = IM2COL_Forward;
            return 0;
        case WINOGRADF63:
            this->GetBufferSize = WINOGRADF63_GetBufferSize;
            this->Init = WINOGRADF63_Init;
            this->Forward = WINOGRADF63_Forward;
            return 0;
        case WINOGRADF63FUSED:
            this->GetBufferSize = WINOGRADF63FUSED_GetBufferSize;
            this->Init = WINOGRADF63FUSED_Init;
            this->Forward = WINOGRADF63FUSED_Forward;
            return 0;
        case DEPTHWISE:
            this->GetBufferSize = DEPTHWISE_GetBufferSize;
            this->Init = DEPTHWISE_Init;
            this->Forward = DEPTHWISE_Forward;
            return 0;
        default:
            LOGE("This algo is not supported on AVX2.");
            this->GetBufferSize = NULL;
            this->Init = NULL;
            this->Forward = NULL;
            return -1;
    }
}
}; // namespace booster


#include "builders/generators/CylinderGenerator.hpp"
#include "builders/generators/IcoSphereGenerator.hpp"
#include "builders/generators/TreeGenerator.hpp"
#include "entities/Node.hpp"
#include "utils/GradientUtils.hpp"

#include <boost/test/unit_test.hpp>

#include "test_utils/DependencyProvider.hpp"
#include "test_utils/ElementUtils.hpp"

#include <cstdio>

using namespace utymap;
using namespace utymap::builders;
using namespace utymap::entities;
using namespace utymap::heightmap;
using namespace utymap::mapcss;
using namespace utymap::meshing;
using namespace utymap::index;
using namespace utymap::utils;

namespace {
    const std::string stylesheet = "node|z1[natural=tree] { color:gradient(red);}";
    struct Builders_Generators_GeneratorFixture
    {
        Builders_Generators_GeneratorFixture() :
            dependencyProvider(),
            mesh(""),
            gradient(),
            style(dependencyProvider.getStyleProvider(stylesheet)
                ->forElement(ElementUtils::createElement<Node>(*dependencyProvider.getStringTable(), 0, { { "natural", "tree" } }), 1)),
            builderContext(
            QuadKey{ 1, 1, 1 },
            *dependencyProvider.getStyleProvider(stylesheet),
            *dependencyProvider.getStringTable(),
            *dependencyProvider.getElevationProvider(),
            [](const Mesh&) {},
            [](const Element&) {}),
            meshContext(mesh, style, gradient, TextureRegion())
        {
        }

        DependencyProvider dependencyProvider;
        Mesh mesh;
        ColorGradient gradient;
        Style style;
        BuilderContext builderContext;
        MeshContext meshContext;
    };
}

BOOST_FIXTURE_TEST_SUITE(Builders_Generators_Generator, Builders_Generators_GeneratorFixture)

BOOST_AUTO_TEST_CASE(GivenIcoSphereGeneratorWithSimpleData_WhenGenerate_ThenCanGenerateMesh)
{
    IcoSphereGenerator icoSphereGenerator(builderContext, meshContext);
    icoSphereGenerator
        .setCenter(Vector3(0, 0, 0))
        .setSize(Vector3(10, 10, 10))
        .setRecursionLevel(2)
        .isSemiSphere(false)
        .setColorNoiseFreq(0.1)
        .setVertexNoiseFreq(0.5)
        .generate();

    BOOST_CHECK_GT(mesh.vertices.size(), 0);
    BOOST_CHECK_GT(mesh.triangles.size(), 0);
    BOOST_CHECK_GT(mesh.colors.size(), 0);
}

BOOST_AUTO_TEST_CASE(GivenCylinderGeneratorWithSimpleData_WhenGenerate_ThenCanGenerateMesh)
{
    CylinderGenerator cylinderGenerator(builderContext, meshContext);
    cylinderGenerator
            .setCenter(Vector3(0, 0, 0))
            .setHeight(10)
            .setMaxSegmentHeight(5)
            .setRadialSegments(7)
            .setRadius(1)
            .setColorNoiseFreq(0.1)
            .generate();

    BOOST_CHECK_GT(mesh.vertices.size(), 0);
    BOOST_CHECK_GT(mesh.triangles.size(), 0);
    BOOST_CHECK_GT(mesh.colors.size(), 0);
}

BOOST_AUTO_TEST_CASE(GivenTreeGeneratorWithSimpleData_WhenGenerate_ThenCanGenerateMesh)
{
    TreeGenerator treeGenerator(builderContext, mesh, style,
        ColorGradient(), ColorGradient(), TextureRegion(), TextureRegion());
    treeGenerator
            .setPosition(Vector3(0, 0, 0))
            .setTrunkHeight(5)
            .setTrunkRadius(0.5)
            .setFoliageSize(Vector3(4, 4, 4))
            .setTrunkColorNoiseFreq(0.1)
            .setFoliageColorNoiseFreq(0.1)
            .generate();

    BOOST_CHECK_GT(mesh.vertices.size(), 0);
    BOOST_CHECK_GT(mesh.triangles.size(), 0);
    BOOST_CHECK_GT(mesh.colors.size(), 0);
}

BOOST_AUTO_TEST_SUITE_END()


#include "tile/lib/lib.h"

#include <memory>
#include <tuple>

#include <boost/format.hpp>

#include "base/util/stream_container.h"
#include "plaidml2/edsl/autodiff.h"
#include "tile/util/tile_file.h"

namespace vertexai {
namespace tile {
namespace lib {

using namespace plaidml::edsl;  // NOLINT

namespace {

lang::RunInfo Evaluate(const std::string& name, const std::vector<Tensor>& vars) {
  Program program(name, vars, {});
  return *static_cast<const tile::lang::RunInfo*>(program.runinfo());
}

std::shared_ptr<lang::BufferBase> MakeBuffer(const LogicalShape& shape) {
  std::vector<size_t> sizes;
  for (const auto& dim : shape.int_dims()) {
    sizes.push_back(dim);
  }
  auto tensor_shape = tile::SimpleShape(static_cast<DataType>(shape.dtype()), sizes);
  auto buffer = std::make_shared<util::SimpleBuffer>();
  buffer->bytes.resize(tensor_shape.byte_size());
  return buffer;
}

Tensor MatMul(const Tensor& A, const Tensor& B) {
  TensorDim M, N, K;
  A.bind_dims(M, K);
  B.bind_dims(K, N);
  TensorIndex k("k"), m("m"), n("n");
  auto C = NamedTensorOutput("C", M, N);
  C(m, n) += A(m, k) * B(k, n);
  return C;
}

Tensor DilatedConvolution2(const Tensor& I, const Tensor& K) {
  TensorDim N, Lx, Ly, LKx, LKy, CI, CO;
  I.bind_dims(N, Lx, Ly, CI);
  K.bind_dims(LKx, LKy, CI, CO);
  auto O = NamedTensorOutput("O", N, Lx - 2 * (LKx - 1), Ly - 3 * (LKy - 1), CO);
  TensorIndex n, x, y, kx, ky, ci, co;
  O(n, x, y, co) += I(n, x + 2 * kx, y + 3 * ky, ci) * K(kx, ky, ci, co);
  return O;
}

Tensor Relu(const Tensor& X) { return Call("relu", X); }

Tensor Sin(const Tensor& X) { return Call("sin", X); }

Tensor Tanh(const Tensor& X) { return Call("tanh", X); }

}  // namespace

Tensor Convolution(const Tensor& I,                      //
                   const Tensor& K,                      //
                   const std::vector<int64_t>& O_sizes,  //
                   std::vector<size_t> strides,          //
                   ConvolutionFormat I_format,           //
                   ConvolutionFormat K_format) {
  TensorDim N, CI, CO;
  auto I_shape = I.shape();
  auto K_shape = K.shape();
  IVLOG(1, "I.shape(): " << I_shape);
  IVLOG(1, "K.shape(): " << K_shape);
  auto ndims = I_shape.ndims() - 2;
  if (strides.empty()) {
    for (size_t i = 0; i < ndims; i++) {
      strides.push_back(1);
    }
  } else if (strides.size() != ndims) {
    throw std::runtime_error(str(
        boost::format("Convolution strides length inconsistent with input shape: %1% (ndims %2%) v %3% (ndims %4%)") %
        StreamContainer(strides) % strides.size() % I_shape % ndims));
  }
  TensorIndex n("n"), co("co"), ci("ci");
  std::vector<TensorDim> I_dims = {N};
  std::vector<TensorDim> I_spatial_dims(ndims);
  std::vector<TensorDim> K_dims;
  std::vector<TensorDim> K_spatial_dims(ndims);
  std::vector<TensorDim> O_dims;
  for (const auto& size : O_sizes) {
    O_dims.emplace_back(size);
  }
  std::vector<TensorIndex> K_idxs;
  std::vector<TensorIndex> I_idxs = {n};
  std::vector<TensorIndex> O_idxs = {n};
  size_t K_spatial_dims_offset = 0;
  if (K_format == ConvolutionFormat::ChannelsFirst) {
    K_spatial_dims_offset = 2;
    K_idxs.push_back(co);
    K_idxs.push_back(ci);
    K_dims.push_back(CO);
    K_dims.push_back(CI);
  }
  if (I_format == ConvolutionFormat::ChannelsFirst) {
    I_idxs.push_back(ci);
    O_idxs.push_back(co);
    I_dims.push_back(CI);
  }
  K_dims.insert(std::end(K_dims), std::begin(K_spatial_dims), std::end(K_spatial_dims));
  I_dims.insert(std::end(I_dims), std::begin(I_spatial_dims), std::end(I_spatial_dims));
  if (I_format == ConvolutionFormat::ChannelsLast) {
    I_dims.push_back(CI);
  }
  if (K_format == ConvolutionFormat::ChannelsLast) {
    K_dims.push_back(CI);
    K_dims.push_back(CO);
  }
  I.bind_dims(I_dims);
  K.bind_dims(K_dims);
  for (size_t i = 0; i < ndims; i++) {
    TensorIndex x(str(boost::format("x%1%") % i));
    TensorIndex k(str(boost::format("k%1%") % i));
    IVLOG(1, "Adding " << i);
    I_idxs.emplace_back(strides[i] * x + k - K_dims[K_spatial_dims_offset + i] / 2);
    K_idxs.push_back(k);
    O_idxs.push_back(x);
  }
  if (I_format == ConvolutionFormat::ChannelsLast) {
    I_idxs.push_back(ci);
    O_idxs.push_back(co);
  }
  if (K_format == ConvolutionFormat::ChannelsLast) {
    K_idxs.push_back(ci);
    K_idxs.push_back(co);
  }
  Tensor O("O", O_dims);
  O(O_idxs) += I(I_idxs) * K(K_idxs);
  return O;
}

lang::RunInfo LoadMatMul(const std::string& name, const LogicalShape& i1, const LogicalShape& i2) {
  auto A = Placeholder(i1, "A");
  auto B = Placeholder(i2, "B");
  return Evaluate(name, {MatMul(A, B)});
}

lang::RunInfo LoadMatMulIntermediate(const std::string& name, const LogicalShape& i1, const LogicalShape& i2,
                                     const LogicalShape& i3) {
  auto A = Placeholder(i1, "A");
  auto B = Placeholder(i2, "B");
  auto C = Placeholder(i3, "C");
  Tensor D = MatMul(A, B);
  Tensor E = D + C;
  return Evaluate(name, {D, E});
}

lang::RunInfo LoadEltwiseMulFlip(const std::string& name, const LogicalShape& i1, const LogicalShape& i2) {
  auto A = Placeholder(i1, "A");
  auto B = Placeholder(i2, "B");
  return Evaluate(name, {~(A * B)});
}

lang::RunInfo LoadMatMulAmongEltwise(const std::string& name, const LogicalShape& i1, const LogicalShape& i2,
                                     const LogicalShape& i3) {
  auto A = Placeholder(i1, "A");
  auto B = Placeholder(i2, "B");
  auto C = Placeholder(i3, "C");
  Tensor NegA = -A;
  Tensor NegB = -B;
  Tensor P = MatMul(NegA, NegB);
  return Evaluate(name, {P + C});
}

lang::RunInfo LoadEltwiseAdd(const std::string& name, const LogicalShape& i1, const LogicalShape& i2) {
  auto A = Placeholder(i1, "A");
  auto B = Placeholder(i2, "B");
  return Evaluate(name, {A + B});
}

lang::RunInfo LoadEltwiseMultiAdd(const std::string& name, const LogicalShape& i1, const LogicalShape& i2,
                                  const LogicalShape& i3, const LogicalShape& i4) {
  auto A = Placeholder(i1, "A");
  auto B = Placeholder(i2, "B");
  auto C = Placeholder(i3, "C");
  auto D = Placeholder(i4, "D");
  return Evaluate(name, {A + B + C + D});
}

lang::RunInfo LoadEltwiseDiv(const std::string& name, const LogicalShape& i1, const LogicalShape& i2) {
  auto A = Placeholder(i1, "A");
  auto B = Placeholder(i2, "B");
  return Evaluate(name, {A / B});
}

lang::RunInfo LoadConstScalarMul(const std::string& name, const double s, const LogicalShape& i1) {
  Tensor scalar(s);
  auto A = Placeholder(i1, "A");
  return Evaluate(name, {scalar * A});
}

lang::RunInfo LoadEltwiseMul(const std::string& name, const LogicalShape& i1, const LogicalShape& i2) {
  auto A = Placeholder(i1, "A");
  auto B = Placeholder(i2, "B");
  return Evaluate(name, {A * B});
}

lang::RunInfo LoadEltwiseMultiMul(const std::string& name, const LogicalShape& i1, const LogicalShape& i2,
                                  const LogicalShape& i3, const LogicalShape& i4) {
  auto A = Placeholder(i1, "A");
  auto B = Placeholder(i2, "B");
  auto C = Placeholder(i3, "C");
  auto D = Placeholder(i4, "D");
  return Evaluate(name, {A * B * C * D});
}

lang::RunInfo LoadSin(const std::string& name, const LogicalShape& i1) {
  auto A = Placeholder(i1, "A");
  return Evaluate(name, {Sin(A)});
}

lang::RunInfo LoadTanh(const std::string& name, const LogicalShape& i1) {
  auto A = Placeholder(i1, "A");
  return Evaluate(name, {Tanh(A)});
}

lang::RunInfo LoadMulThenNeg(const std::string& name, const LogicalShape& i1, const LogicalShape& i2) {
  auto A = Placeholder(i1, "A");
  auto B = Placeholder(i2, "B");
  Tensor C = A * B;
  return Evaluate(name, {-C});
}

lang::RunInfo LoadNegThenMul(const std::string& name, const LogicalShape& i1, const LogicalShape& i2) {
  auto A = Placeholder(i1, "A");
  auto B = Placeholder(i2, "B");
  Tensor NegA = -A;
  Tensor NegB = -B;
  return Evaluate(name, {NegA * NegB});
}

lang::RunInfo LoadConstCalc(const std::string& name) {
  Tensor N(1);
  Tensor F(0.0);
  Tensor F2(3.7);
  TensorIndex i;
  auto Simple = TensorOutput(1);
  Simple(i) = F();
  auto DoubleN = TensorOutput(1);
  DoubleN(i) = N();
  Tensor Partial = Simple + DoubleN;
  Tensor O = Partial + F2;
  return Evaluate(name, {O});
}

lang::RunInfo LoadConv1d(const std::string& name,     //
                         const LogicalShape& input,   //
                         const LogicalShape& kernel,  //
                         const std::vector<int64_t>& output) {
  auto I = Placeholder(input, "I");
  auto K = Placeholder(kernel, "K");
  auto runinfo = Evaluate(name, {Convolution(I, K, output)});
  runinfo.const_inputs = {"K"};
  runinfo.input_buffers = {{"K", MakeBuffer(kernel)}};
  return runinfo;
}

lang::RunInfo LoadConv2d(const std::string& name,     //
                         const LogicalShape& input,   //
                         const LogicalShape& kernel,  //
                         const std::vector<int64_t>& output) {
  auto I = Placeholder(input, "I");
  auto K = Placeholder(kernel, "K");
  auto runinfo = Evaluate(name, {Convolution(I, K, output)});
  runinfo.const_inputs = {"K"};
  runinfo.input_buffers = {{"K", MakeBuffer(kernel)}};
  return runinfo;
}

lang::RunInfo LoadConv2dRelu(const std::string& name,     //
                             const LogicalShape& input,   //
                             const LogicalShape& kernel,  //
                             const std::vector<int64_t>& output) {
  auto I = Placeholder(input, "I");
  auto K = Placeholder(kernel, "K");
  auto runinfo = Evaluate(name, {Relu(Convolution(I, K, output))});
  runinfo.const_inputs = {"K"};
  runinfo.input_buffers = {{"K", MakeBuffer(kernel)}};
  return runinfo;
}

lang::RunInfo LoadConv2dBnRelu(const std::string& name,       //
                               const LogicalShape& input,     //
                               const LogicalShape& kernel,    //
                               const LogicalShape& channels,  //
                               const std::vector<int64_t>& output) {
  auto I = Placeholder(input, "I");
  auto K = Placeholder(kernel, "K");
  auto B = Placeholder(channels, "B");
  auto S = Placeholder(channels, "S");
  auto O = Convolution(I, K, output);
  auto R = Relu((O + B) * S);
  auto runinfo = Evaluate(name, {R});
  runinfo.const_inputs = {"K"};
  runinfo.input_buffers = {
      {"K", MakeBuffer(kernel)},
      {"B", MakeBuffer(channels)},
      {"S", MakeBuffer(channels)},
  };
  return runinfo;
}

lang::RunInfo LoadConv2d3Deep(const std::string& name,      //
                              const LogicalShape& input,    //
                              const LogicalShape& kernel1,  //
                              const LogicalShape& kernel2,  //
                              const LogicalShape& kernel3) {
  auto I = Placeholder(input, "I");
  auto K1 = Placeholder(kernel1, "K1");
  auto K2 = Placeholder(kernel2, "K2");
  auto K3 = Placeholder(kernel3, "K3");
  auto dims = input.int_dims();
  auto O1 = Convolution(I, K1, {dims[0], dims[1], dims[2], kernel1.int_dims()[3]});
  auto O2 = Convolution(O1, K2, {dims[0], dims[1], dims[2], kernel2.int_dims()[3]});
  auto O3 = Convolution(O2, K3, {dims[0], dims[1], dims[2], kernel3.int_dims()[3]});
  auto runinfo = Evaluate(name, {O3});
  runinfo.const_inputs = {"K1", "K2", "K3"};
  runinfo.input_buffers = {
      {"K1", MakeBuffer(kernel1)},
      {"K2", MakeBuffer(kernel2)},
      {"K3", MakeBuffer(kernel3)},
  };
  return runinfo;
}

lang::RunInfo LoadDilatedConv2d(const std::string& name,    //
                                const LogicalShape& input,  //
                                const LogicalShape& kernel) {
  auto I = Placeholder(input, "I");
  auto K = Placeholder(kernel, "K");
  return Evaluate(name, {DilatedConvolution2(I, K)});
}

Tensor Normalize(const Tensor& X) {
  auto XSqr = X * X;
  auto X_MS = TensorOutput();
  std::vector<TensorIndex> idxs(X.shape().ndims());
  X_MS() += XSqr(idxs);
  return sqrt(X_MS);
}

std::tuple<Tensor, Tensor> LarsMomentum(const Tensor& X,           //
                                        const Tensor& Grad,        //
                                        const Tensor& Veloc,       //
                                        const Tensor& LR,          //
                                        double lars_coeff,         //
                                        double lars_weight_decay,  //
                                        double momentum) {
  auto XNorm = Normalize(X);
  auto GradNorm = Normalize(Grad);
  auto LocLR = LR * lars_coeff * XNorm / (GradNorm + lars_weight_decay * XNorm);
  auto NewVeloc = momentum * Veloc + LocLR * (Grad + lars_weight_decay * X);
  return std::make_tuple(X - NewVeloc, NewVeloc);
}

lang::RunInfo LoadLarsMomentum4d(const std::string& name,      //
                                 const LogicalShape& x_shape,  //
                                 const LogicalShape& lr_shape) {
  // Note: X/Grad/Veloc/NewX/NewVeloc should all have the same shape for the
  // semantics of this operation to be correct, so we only pass in 1 shape for
  // all of them.
  double lars_coeff = 1. / 1024.;
  double lars_weight_decay = 1. / 2048.;
  double momentum = 1. / 8.;
  auto X = Placeholder(x_shape);
  auto Grad = Placeholder(x_shape);
  auto Veloc = Placeholder(x_shape);
  auto LR = Placeholder(lr_shape);
  auto R = LarsMomentum(X, Grad, Veloc, LR, lars_coeff, lars_weight_decay, momentum);
  return Evaluate("lars_momentum4d", {std::get<0>(R), std::get<1>(R)});
}

lang::RunInfo LoadPow(const std::string& name,  //
                      const LogicalShape& i1,   //
                      const LogicalShape& i2) {
  auto X = Placeholder(i1, "X");
  auto Y = Placeholder(i2, "Y");
  auto runinfo = Evaluate(name, {pow(X, Y)});
  runinfo.input_buffers = {
      {"X", MakeBuffer(i1)},
      {"Y", MakeBuffer(i2)},
  };
  return runinfo;
}

Tensor Norm4dAx2(const Tensor& I, const Tensor& G, const Tensor& B, const Tensor& Epsilon) {
  TensorDim I0, I1, I2, I3;
  I.bind_dims(I0, I1, I2, I3);
  auto H = I2 * I3;
  auto Sum = TensorOutput(I0, I1, 1, 1);
  TensorIndex i0, i1, i2, i3;
  Sum(i0, i1, 0, 0) += I(i0, i1, i2, i3);
  auto Mu = Sum / H;
  auto Diff = I - Mu;
  auto SqDiff = Diff * Diff;
  auto SumSqDiff = TensorOutput(I0, I1, 1, 1);
  SumSqDiff(i0, i1, 0, 0) += SqDiff(i0, i1, i2, i3);
  auto Stdev = sqrt(SumSqDiff + Epsilon) / H;
  return (G / Stdev) * (I - Mu) + B;
}

lang::RunInfo LoadLayerNorm4dAx2(const std::string& name,  //
                                 const LogicalShape& input) {
  // Note: I/G/B/O should all have the same shape, so pass in one shape to share
  auto I = Placeholder(input);
  auto G = Placeholder(input);
  auto B = Placeholder(input);
  auto Epsilon = Placeholder(PLAIDML_DATA_FLOAT32, {});
  return Evaluate(name, {Norm4dAx2(I, G, B, Epsilon)});
}

Tensor PolygonBoxTransform(const Tensor& I) {
  TensorDim N, C, H, W;
  I.bind_dims(N, C, H, W);
  auto TEpartial = TensorOutput(N, C, H, W);
  auto TOpartial = TensorOutput(N, C, H, W);
  TensorIndex n, c, h, w;
  auto Widx = index(I, 3);
  TEpartial(2 * n, c, h, w) = I(2 * n, c, h, w);
  auto TE = 4 * Widx - TEpartial;
  TOpartial(2 * n + 1, c, h, w) = I(2 * n + 1, c, h, w);
  auto Hidx = index(I, 2);
  auto TO = 4 * Hidx - TOpartial;
  return TE + TO;
}

lang::RunInfo LoadPolygonBoxTransform(const std::string& name,  //
                                      const LogicalShape& input) {
  // Note: I and O have the same shape
  auto I = Placeholder(input);
  return Evaluate(name, {PolygonBoxTransform(I)});
}

lang::RunInfo LoadSoftmax(const std::string& name,      //
                          const LogicalShape& input) {  //
  auto X1 = Placeholder(input);
  TensorDim I, J;
  X1.bind_dims(I, J);
  TensorIndex i("i"), j("j");
  auto M = NamedTensorOutput("M", I, 1);
  M(i, 0) >= X1(i, j);
  auto E = exp(X1 - M);
  auto N = NamedTensorOutput("N", I, 1);
  N(i, 0) += E(i, j);
  return Evaluate(name, {E / N});
}

}  // namespace lib
}  // namespace tile
}  // namespace vertexai


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_UNARY_MINUS_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_UNARY_MINUS_HPP_INCLUDED

#ifdef BOOST_MSVC
  #pragma warning(disable: 4146) // unary minus applied to unsigned
  #pragma warning(push)
#endif
#include <boost/config.hpp>
#include <boost/simd/meta/as_logical.hpp>
#include <boost/simd/constant/valmax.hpp>
#include <boost/simd/constant/valmin.hpp>
#include <boost/simd/function/unary_minus.hpp>
#include <boost/simd/function/saturated.hpp>
#
namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  BOOST_DISPATCH_OVERLOAD ( unary_minus_
                          , (typename A0)
                          , bd::cpu_
                          , bd::scalar_< bd::arithmetic_<A0> >
                          )
  {
    BOOST_FORCEINLINE A0 operator() ( A0 a0) const { return -a0; }
  };

  BOOST_DISPATCH_OVERLOAD ( unary_minus_
                          , (typename T)
                          , bd::cpu_
                          , bs::saturated_tag
                          , bd::scalar_<bd::signed_<T>>
                           )
  {
    BOOST_FORCEINLINE T operator()(const saturated_tag &, T a0) const BOOST_NOEXCEPT
    {
      return a0 == Valmin<T>() ? Valmax<T>() : bs::unary_minus(a0);
    }
  };

  BOOST_DISPATCH_OVERLOAD ( unary_minus_
                          , (typename T)
                          , bd::cpu_
                          , bs::saturated_tag
                          , bd::scalar_<bd::unspecified_<T>>
                          )
  {
    BOOST_FORCEINLINE T operator()(const saturated_tag &, T a) const BOOST_NOEXCEPT
    {
      return unary_minus(a);
    }
  };

} } }

#ifdef BOOST_MSVC
  #pragma warning(pop)
#endif

#endif


/*
 * Copyright (C) 2021 LEIDOS.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
#include <wgs84_utils/proj_tools.h>
#include <tf2/LinearMath/Quaternion.h>
#include <vector>
#include <string>
#include <boost/algorithm/string.hpp>
#include <algorithm>

namespace wgs84_utils
{
namespace proj_tools
{
std::string getAxisFromProjString(std::string proj_string)
{
  boost::erase_all(proj_string, " ");  // Remove white space from string

  std::vector<std::string> strs;
  boost::split(strs, proj_string, boost::is_any_of("+"));  // Split on + sign used to denote proj parameters

  std::string axis = "enu";  // Default axis alignment is ENU in proj

  for (const auto& param : strs)  // Iterate over proj string parameters until axis parameter is found
  {
    size_t axis_pos = param.find("axis=");
    if (axis_pos != 0)
    {
      continue;
    }

    size_t equal_pos = param.find("=");
    if (equal_pos >= param.size() - 1)
    {
      throw std::invalid_argument("Cannot extract +axis tag as georeference has an +axis tag which "
                                  "is empty: " +
                                  param);
    }

    axis = param.substr(param.find("=") + 1);

    break;  // If axis is found no reason to keep iterating.
  }

  return axis;
}

tf2::Quaternion getRotationOfNEDFromProjAxis(const std::string& axis)
{
  /*

    Proj Axis notation
    “e” - Easting
    “w” - Westing
    “n” - Northing
    “s” - Southing
    “u” - Up
    “d” - Down

    */

  tf2::Quaternion axis_in_ned;

  // Convert axis into transform with NED frame
  // Only support right handed coordinate systems at this time
  if (axis.compare("enu") == 0)
  {  // East North Up

    axis_in_ned.setRPY(180.0 * wgs84_utils::DEG2RAD, 0,
                       90.0 * wgs84_utils::DEG2RAD);  // Convert from NED frame using fixed-axis roll pitch yaw
                                                      // rotations
  }
  else if (axis.compare("nwu") == 0)
  {  // North West Up

    axis_in_ned.setRPY(180.0 * wgs84_utils::DEG2RAD, 0, 0);
  }
  else if (axis.compare("wsu") == 0)
  {  // West South Up

    axis_in_ned.setRPY(180.0 * wgs84_utils::DEG2RAD, 0, -90.0 * wgs84_utils::DEG2RAD);
  }
  else if (axis.compare("seu") == 0)
  {  // South East Up

    axis_in_ned.setRPY(180.0 * wgs84_utils::DEG2RAD, 0, -180.0 * wgs84_utils::DEG2RAD);
  }
  else if (axis.compare("ned") == 0)
  {  // North East Down

    axis_in_ned = tf2::Quaternion::getIdentity();
  }
  else if (axis.compare("wnd") == 0)
  {  // West North Down

    axis_in_ned.setRPY(0, 0, -90.0 * wgs84_utils::DEG2RAD);
  }
  else if (axis.compare("swd") == 0)
  {  // South West Down

    axis_in_ned.setRPY(0, 0, 180.0 * wgs84_utils::DEG2RAD);
  }
  else if (axis.compare("esd") == 0)
  {  // East South Down

    axis_in_ned.setRPY(0, 0, 90.0 * wgs84_utils::DEG2RAD);
  }
  else
  {
    throw std::invalid_argument("Proj axis to enu conversion only supports projections using right handed coordinate frames");
  }

  return axis_in_ned.inverse();  // Inverse rotation to get rotations required to come from existing axis to ned
}
}  // namespace proj_tools
}  // namespace wgs84_utils


// Copyright (c) 2015  GeometryFactory (France). All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s)     : Sebastien Loriot <sebastien.loriot@cgal.org>,
//                 Jane Tournois
//

#include <CGAL/Mesh_3/io_signature.h>
#include <QtCore/qglobal.h>

#include "Scene_surface_mesh_item.h"
#include "Scene_c3t3_item.h"
#include "Scene_polylines_item.h"
#include "Scene_points_with_normal_item.h"

#include <CGAL/Three/Polyhedron_demo_plugin_helper.h>
#include <CGAL/Three/Polyhedron_demo_io_plugin_interface.h>
#include <CGAL/Three/Three.h>

#include <QApplication>
#include <QMessageBox>
#include <QMainWindow>
#include <QFileDialog>
#include <QString>

#include <fstream>

#include <boost/graph/graph_traits.hpp>
#include <boost/unordered_map.hpp>

#include <CGAL/boost/graph/properties.h>
#include <CGAL/boost/graph/Euler_operations.h>
#include <CGAL/property_map.h>
#include <CGAL/IO/Complex_3_in_triangulation_3_to_vtk.h>
#include <CGAL/Mesh_3/tet_soup_to_c3t3.h>
#include <CGAL/IO/output_to_vtu.h>
#include <CGAL/boost/graph/io.h>

#include <vtkSmartPointer.h>
#include <vtkDataSetReader.h>
#include <vtkXMLPolyDataReader.h>
#include <vtkPolyDataWriter.h>
#include <vtkUnstructuredGrid.h>
#include <vtkXMLUnstructuredGridReader.h>
#include <vtkXMLUnstructuredGridWriter.h>
#include <vtkXMLPolyDataWriter.h>
#include <vtkPolyData.h>
#include <vtkIdTypeArray.h>
#include <vtkFieldData.h>
#include <vtkFloatArray.h>
#include <vtkDoubleArray.h>
#include <vtkCell.h>
#include <vtkCellData.h>
#include <vtkIdList.h>
#include <vtkAppendFilter.h>
#include <vtkSphereSource.h>
#include <vtkVersion.h>
#include <vtkXMLUnstructuredGridWriter.h>
#include <vtkPoints.h>
#include <vtkCellArray.h>
#include <vtkType.h>
#include <vtkCommand.h>
#include <vtkXMLUnstructuredGridWriter.h>

#include <CGAL/Named_function_parameters.h>
#include <CGAL/Polygon_mesh_processing/internal/named_params_helper.h>
typedef Scene_surface_mesh_item Scene_facegraph_item;
typedef Scene_facegraph_item::Face_graph FaceGraph;
typedef boost::property_traits<boost::property_map<FaceGraph,
CGAL::vertex_point_t>::type>::value_type Point;

namespace CGAL{

  template <class Point_3>
  void extract_segments_from_vtkPointSet(vtkPointSet* poly_data,
                                         std::vector< std::vector<Point_3> >& segments)
  {
    // get nb of points and cells
    vtkIdType nb_points = poly_data->GetNumberOfPoints();
    vtkIdType nb_cells = poly_data->GetNumberOfCells();

    //extract points
    std::vector<Point_3> point_map(nb_points);
    for (vtkIdType i = 0; i<nb_points; ++i)
    {
      double coords[3];
      poly_data->GetPoint(i, coords);
      point_map[i]=Point_3(coords[0], coords[1], coords[2]);
    }

    //extract segments
    for (vtkIdType i = 0; i<nb_cells; ++i)
    {
      if(poly_data->GetCellType(i) != 3
         && poly_data->GetCellType(i) != 4)
        continue;
      vtkCell* cell_ptr = poly_data->GetCell(i);

      vtkIdType nb_vertices = cell_ptr->GetNumberOfPoints();
      segments.push_back( std::vector<Point_3>() );
      for(int j = 0; j < nb_vertices; ++j)
      {
        segments.back().push_back(point_map[cell_ptr->GetPointId(j)]);
      }
    }
  }

  template<typename VtkWriter, typename PM>
  void polygon_mesh_to_vtkUnstructured(const PM& pmesh,//PolygonMesh
                                       const char* filename)
  {
    typedef typename boost::graph_traits<PM>::vertex_descriptor   vertex_descriptor;
    typedef typename boost::graph_traits<PM>::face_descriptor     face_descriptor;
    typedef typename boost::graph_traits<PM>::halfedge_descriptor halfedge_descriptor;

    typedef typename boost::property_map<PM, CGAL::vertex_point_t>::const_type VPMap;
    typedef typename boost::property_map_value<PM, CGAL::vertex_point_t>::type Point_3;
    VPMap vpmap = get(CGAL::vertex_point, pmesh);

    vtkPoints* const vtk_points = vtkPoints::New();
    vtkCellArray* const vtk_cells = vtkCellArray::New();

    vtk_points->Allocate(num_vertices(pmesh));
    vtk_cells->Allocate(num_faces(pmesh));

    std::map<vertex_descriptor, vtkIdType> Vids;
    vtkIdType inum = 0;

    for(vertex_descriptor v : vertices(pmesh))
    {
      const Point_3& p = get(vpmap, v);
      vtk_points->InsertNextPoint(CGAL::to_double(p.x()),
                                  CGAL::to_double(p.y()),
                                  CGAL::to_double(p.z()));
      Vids[v] = inum++;
    }
    for(face_descriptor f : faces(pmesh))
    {
      vtkIdList* cell = vtkIdList::New();
      for(halfedge_descriptor h :
                    halfedges_around_face(halfedge(f, pmesh), pmesh))
      {
        cell->InsertNextId(Vids[target(h, pmesh)]);
      }
      vtk_cells->InsertNextCell(cell);
      cell->Delete();
    }

    vtkSmartPointer<vtkUnstructuredGrid> usg =
      vtkSmartPointer<vtkUnstructuredGrid>::New();

    usg->SetPoints(vtk_points);
    vtk_points->Delete();

    usg->SetCells(5,vtk_cells);
    vtk_cells->Delete();

    // Combine the two data sets
    //vtkSmartPointer<vtkAppendFilter> appendFilter =
    //  vtkSmartPointer<vtkAppendFilter>::New();
    //appendFilter->AddInputData(polydata);
    //appendFilter->Update();

    //vtkSmartPointer<vtkPolyData> unstructuredGrid =
    //  vtkSmartPointer<vtkPolyData>::New();
    //unstructuredGrid->ShallowCopy(appendFilter->GetOutput());

    // Write the unstructured grid
    vtkSmartPointer<VtkWriter> writer =
      vtkSmartPointer<VtkWriter>::New();
    writer->SetFileName(filename);
    writer->SetInputData(usg);
    writer->Write();
  }
}//end namespace CGAL

class Polyhedron_demo_vtk_plugin :
  public QObject,
  public CGAL::Three::Polyhedron_demo_io_plugin_interface
{
  Q_OBJECT
  Q_INTERFACES(CGAL::Three::Polyhedron_demo_io_plugin_interface)
  Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.IOPluginInterface/1.90" FILE "vtk_io_plugin.json")

public:
  typedef boost::graph_traits<FaceGraph>::vertex_descriptor vertex_descriptor;
  typedef boost::graph_traits<FaceGraph>::face_descriptor face_descriptor;

  QString nameFilters() const {
    return "VTK XML UnstructuredGrid (*.vtu);;VTK PolyData files (*.vtk);; VTK XML PolyData (*.vtp)"; }
  QString name() const { return "vtk_plugin"; }
  bool canSave(const CGAL::Three::Scene_item* item)
  {

    return (qobject_cast<const Scene_facegraph_item*>(item)
            || qobject_cast<const Scene_c3t3_item*>(item));
  }

  bool save(QFileInfo fileinfo,QList<CGAL::Three::Scene_item*>& items)
  {
    Scene_item* item = items.front();
    std::string extension = fileinfo.suffix().toLower().toStdString();
    if ( extension != "vtk" && extension != "vtp" && extension != "vtu")
      return false;

    std::string output_filename = fileinfo.absoluteFilePath().toStdString();

    const Scene_facegraph_item* poly_item =
      qobject_cast<const Scene_facegraph_item*>(item);

    if (poly_item)
    {
      if (extension != "vtp")
      {
        if(!CGAL::is_triangle_mesh(*poly_item->polyhedron()))
        {
          QMessageBox::warning(0, "Error",
                               "Cannot save a mesh in vtu format if "
                               "it is not pure triangle.");
          return false;
        }
        CGAL::polygon_mesh_to_vtkUnstructured<vtkXMLUnstructuredGridWriter>(
          *poly_item->polyhedron(),
          output_filename.data());
      }
      else
      {
        const FaceGraph* mesh = poly_item->face_graph();
        std::ofstream os(output_filename.data());
        os << std::setprecision(16);
        //write header
        CGAL::IO::write_VTP(os, *mesh);
      }
    }
    else
    {
      const Scene_c3t3_item* c3t3_item =
          qobject_cast<const Scene_c3t3_item*>(item);
      if(!c3t3_item || extension != "vtu")
        return false;

      std::ofstream os(output_filename.data());
      os << std::setprecision(16);
      const C3t3& c3t3 = c3t3_item->c3t3();

      CGAL::IO::output_to_vtu(os, c3t3);
    }
    items.pop_front();
    return true;
  }

  bool canLoad(QFileInfo) const { return true; }

  QList<Scene_item*> load(QFileInfo fileinfo, bool& ok, bool add_to_scene)
  {
    std::string extension=fileinfo.suffix().toLower().toStdString();
    if (extension != "vtk" && extension != "vtp" && extension != "vtu")
    {
      ok = false;
      return QList<Scene_item*>();
    }

    std::string fname = fileinfo.absoluteFilePath().toStdString();

    // Try to read .vtk in a facegraph
    if(fileinfo.size() == 0)
    {
      CGAL::Three::Three::warning( tr("The file you are trying to load is empty."));
      Scene_facegraph_item* item =
          new Scene_facegraph_item();
      item->setName(fileinfo.completeBaseName());
      ok = true;
      if(add_to_scene)
        CGAL::Three::Three::scene()->addItem(item);
      return QList<Scene_item*>()<<item;
    }

    vtkSmartPointer<vtkPointSet> data;
    vtkSmartPointer<CGAL::IO::internal::ErrorObserverVtk> obs =
      vtkSmartPointer<CGAL::IO::internal::ErrorObserverVtk>::New();

    if (extension=="vtp")
      data = CGAL::IO::internal::read_vtk_file<vtkXMLPolyDataReader>(fname,obs)
              ->GetOutput();
    else
     if (extension=="vtu")
       data = CGAL::IO::internal::read_vtk_file<vtkXMLUnstructuredGridReader>(fname,obs)
                ->GetOutput();
     else{
       //read non-XML data
       vtkSmartPointer<vtkDataSetReader> reader =
         CGAL::IO::internal::read_vtk_file<vtkDataSetReader>(fname,obs);
       data = vtkPolyData::SafeDownCast(reader->GetOutput());
       if (!data)
        data = vtkUnstructuredGrid::SafeDownCast(reader->GetOutput());
     }

    if (obs->GetError())
    {
      QMessageBox msgBox;
      msgBox.setText("This type of data can't be opened");
      msgBox.setInformativeText(QString("VTK error message :\n")
        .append(QString(obs->GetErrorMessage().data())));
      msgBox.setStandardButtons(QMessageBox::Ok);
      msgBox.setIcon(QMessageBox::Critical);
      msgBox.exec();
      ok = false;
      return QList<Scene_item*>();
    }
    if (obs->GetWarning())
    {
      QMessageBox msgBox;
      msgBox.setText("This file generates a warning");
      msgBox.setInformativeText(QString("VTK warning message :\n")
        .append(QString(obs->GetWarningMessage().data())));
      msgBox.setStandardButtons(QMessageBox::Ok);
      msgBox.setIcon(QMessageBox::Warning);
      msgBox.exec();
    }

    //check celltypes
    bool is_polygon_mesh(false),
        is_c3t3(false),
        is_polyline(false);
    for(int i = 0; i< data->GetNumberOfCells(); ++i)
    {
      int t = data->GetCellType(i);
      if( t == 5 || t == 7 || t == 9) //tri, quad or polygon
        is_polygon_mesh = true;
      else if(t == 10) //tetrahedron
        is_c3t3 = true;
      else if( t == 3 || t == 4) //line or polyline
        is_polyline = true;
    }
    Scene_group_item* group = nullptr;
    if((is_polygon_mesh && is_c3t3)
       || (is_polygon_mesh && is_polyline)
       || (is_c3t3 && is_polyline) )
    {
      group = new Scene_group_item(fileinfo.baseName());
    }

    if(is_polygon_mesh)
    {
      FaceGraph* poly = new FaceGraph();
      if (CGAL::IO::internal::vtkPointSet_to_polygon_mesh(data, *poly, CGAL::parameters::default_values()))
      {
        Scene_facegraph_item* poly_item = new Scene_facegraph_item(poly);
        if(group)
        {
          poly_item->setName(QString("%1_faces").arg(fileinfo.baseName()));
          CGAL::Three::Three::scene()->addItem(poly_item);
          CGAL::Three::Three::scene()->changeGroup(poly_item, group);
        }
        else{
          poly_item->setName(fileinfo.baseName());
          ok = true;
          if(add_to_scene)
            CGAL::Three::Three::scene()->addItem(poly_item);
          return QList<Scene_item*>()<<poly_item;
        }
      }
    }

    if (is_c3t3)
    {
      typedef std::array<int, 3> Facet; // 3 = id
      typedef std::array<int, 5> Tet_with_ref; // first 4 = id, fifth = reference
      Scene_c3t3_item* c3t3_item = new Scene_c3t3_item();
      c3t3_item->set_valid(false);
      //build a triangulation from data:
      std::vector<Tr::Point> points;
      vtkPoints* dataP = data->GetPoints();;
      for(int i = 0; i< data->GetNumberOfPoints(); ++i)
      {
        double *p = dataP->GetPoint(i);
        points.push_back(Tr::Point(p[0],p[1],p[2]));
      }
      std::vector<Tet_with_ref> finite_cells;
      bool has_mesh_domain = data->GetCellData()->HasArray("MeshDomain");
      vtkDataArray* domains = data->GetCellData()->GetArray("MeshDomain");
      for(int i = 0; i< data->GetNumberOfCells(); ++i)
      {
        if(data->GetCellType(i) != 10 )
          continue;
        vtkIdList* pids = data->GetCell(i)->GetPointIds();
        Tet_with_ref cell;
        for(int j = 0; j<4; ++j)
          cell[j] = pids->GetId(j);
        cell[4] = has_mesh_domain ? static_cast<int>(domains->GetComponent(i,0))
                                  :1;
        finite_cells.push_back(cell);
      }
      std::map<Facet, int> border_facets;
      //Preprocessing for build_triangulation
      //check for orientation and swap in cells if not good.
      for(std::size_t i=0; i<finite_cells.size(); ++i)
      {
        Point_3 test_points[4];
        for (int j = 0; j < 4; ++j)
        {
          Tr::Point tp = points[finite_cells[i][j]];
          test_points[j] = Point_3(tp.x(), tp.y(), tp.z());
        }
        if(CGAL::orientation(test_points[0], test_points[1], test_points[2], test_points[3])
                             != CGAL::POSITIVE)
        {
          std::swap(finite_cells[i][1], finite_cells[i][3]);
        }
      }
      std::vector<typename Tr::Vertex_handle> new_vertices;
      CGAL::build_triangulation<Tr, true>(c3t3_item->c3t3().triangulation(),
        points, finite_cells, border_facets, new_vertices);

      for( C3t3::Triangulation::Finite_cells_iterator
           cit = c3t3_item->c3t3().triangulation().finite_cells_begin();
           cit != c3t3_item->c3t3().triangulation().finite_cells_end();
           ++cit)
      {
        CGAL_assertion(cit->info() >= 0);
        c3t3_item->c3t3().add_to_complex(cit, cit->info());
        for(int i=0; i < 4; ++i)
        {
          if(cit->surface_patch_index(i)>0)
          {
            c3t3_item->c3t3().add_to_complex(cit, i, cit->surface_patch_index(i));
          }
        }
      }

      //if there is no facet in the complex, we add the border facets.
      if(c3t3_item->c3t3().number_of_facets_in_complex() == 0)
      {
        for( C3t3::Triangulation::Finite_facets_iterator
             fit = c3t3_item->c3t3().triangulation().finite_facets_begin();
             fit != c3t3_item->c3t3().triangulation().finite_facets_end();
             ++fit)
        {
          typedef C3t3::Triangulation::Cell_handle      Cell_handle;

          Cell_handle c = fit->first;
          Cell_handle nc = c->neighbor(fit->second);

          // By definition, Subdomain_index() is supposed to be the id of the exterior
          if(c->subdomain_index() != C3t3::Triangulation::Cell::Subdomain_index() &&
             nc->subdomain_index() == C3t3::Triangulation::Cell::Subdomain_index())
          {
            // Color the border facet with the index of its cell
            c3t3_item->c3t3().add_to_complex(c, fit->second, c->subdomain_index());
          }
        }
      }
      c3t3_item->c3t3_changed();
      c3t3_item->resetCutPlane();
      if(group)
      {
        c3t3_item->setName(QString("%1_tetrahedra").arg(fileinfo.baseName()));
        CGAL::Three::Three::scene()->addItem(c3t3_item);
        CGAL::Three::Three::scene()->changeGroup(c3t3_item, group);
      }
      else{
        c3t3_item->setName(fileinfo.baseName());
        ok = true;
        if(add_to_scene)
          CGAL::Three::Three::scene()->addItem(c3t3_item);
        return QList<Scene_item*>()<<c3t3_item;
      }
    }

    if(is_polyline)
    {
      std::vector< std::vector<Point> > segments;
      extract_segments_from_vtkPointSet(data,segments);
      Scene_polylines_item* polyline_item = new Scene_polylines_item();
      for(const std::vector<Point>& segment : segments)
          polyline_item->polylines.push_back(segment);
      if(group)
      {
        polyline_item->setName(QString("%1_lines").arg(fileinfo.baseName()));
        CGAL::Three::Three::scene()->addItem(polyline_item);
        CGAL::Three::Three::scene()->changeGroup(polyline_item, group);
      }
      else{
        polyline_item->setName(fileinfo.baseName());
        ok = true;
        if(add_to_scene)
          CGAL::Three::Three::scene()->addItem(polyline_item);
        return QList<Scene_item*>()<<polyline_item;
      }
    }

    if(group){
      ok = true;
      if(add_to_scene)
        CGAL::Three::Three::scene()->addItem(group);
      return QList<Scene_item*>()<<group;
    }

    QApplication::restoreOverrideCursor();
    QMessageBox::warning(CGAL::Three::Three::mainWindow(),
                         "Problematic file",
                         "Cell type not recognized. Only points will be displayed.");
    QApplication::setOverrideCursor(Qt::WaitCursor);
    Scene_points_with_normal_item* point_item = new Scene_points_with_normal_item();
    for(int i=0; i< data->GetNumberOfPoints(); ++i)
    {
      double* p = data->GetPoint(i);
      point_item->point_set()->insert(Point_3(p[0], p[1], p[2]));
    }
    point_item->setName(fileinfo.baseName());
    ok = true;
    if(add_to_scene)
      CGAL::Three::Three::scene()->addItem(point_item);
    return QList<Scene_item*>()<<point_item;
  }
}; // end Polyhedron_demo_vtk_plugin

#include "VTK_io_plugin.moc"


#include <iostream>
#include <chrono>
#include <memory>
#include <boost/asio.hpp>

using namespace std;
namespace asio = ::boost::asio;

int main() {
	asio::io_context ioc;

	auto keepAliveTimer = make_shared<asio::steady_timer>(
            ioc, chrono::hours(11 * 24));

	auto quitAfterGreetingPersonToo =
        [keepAliveTimer](const boost::system::error_code& ec,
                         const string& name) {
            if (!ec) {
                cout << "Hello " << name << "!" << endl;

                keepAliveTimer->cancel();
            }
        };

	// timer begins to run now
	auto greetTimer = asio::steady_timer(ioc, chrono::seconds(1));

	// later we can attach the completion handler
	greetTimer.async_wait(bind(
		quitAfterGreetingPersonToo,
		std::placeholders::_1,
		"World"));

	ioc.run();
	return 0;
}


#include <iostream>
#include <fstream>
#include <algorithm>
#include <vector>
#include <numeric>
#include <unordered_map>
#include <boost/algorithm/string/find.hpp>

class Messenger
{
    public:
        std::unordered_map<int, std::vector<std::string>> map_rules_and_messages;
        std::unordered_map<int, std::string> map_rules_and_dependencies;
        std::unordered_map<int, std::string> map_rules_and_letters;
        std::vector<std::string> messages;

        Messenger() = default;

        enum class ReadMode
        {
            rules = 1, messages = 2
        };

        bool all_dependencies_processed(std::string dependencies)
        {
            std::string unprocessed_part = dependencies;
            while (unprocessed_part.size() > 0)
            {
                if(unprocessed_part.find(" ") < unprocessed_part.find("|") || (unprocessed_part.find(" ") == unprocessed_part.find("|")))
                {
                    int end = (unprocessed_part.find(" ") > (unprocessed_part.size() - 1)) ? unprocessed_part.size() : unprocessed_part.find(" ");
                    // just read the number - dependency rule number
                    int number = std::stoi(unprocessed_part.substr(0, end));
                    if(map_rules_and_messages.find(number) == map_rules_and_messages.end()) return false;
                    if(end != unprocessed_part.size() - 1 && (unprocessed_part.find(" ") != unprocessed_part.find("|")))
                        unprocessed_part = unprocessed_part.substr(end + 1);
                    else break;
                }
                else if(unprocessed_part.find("|") < SIZE_MAX)
                {
                    unprocessed_part = unprocessed_part.substr(unprocessed_part.find("|") + 2);
                }
            }
            return true;
        }

        void append(std::vector<std::string>& building_message, int rule_number)
        {
            std::vector<std::string> messages_to_append = map_rules_and_messages[rule_number];
            std::vector<std::string> new_building_message;
            if(building_message.size() > 0)
            {
                for(std::string building_mess : building_message)
                {
                    for(std::string append_mess : messages_to_append)
                    {
                        new_building_message.push_back(building_mess + append_mess);
                    }
                }
            }
            else
            {
                for(std::string append_mess : messages_to_append)
                {
                    new_building_message.push_back(append_mess);
                }
            }
            building_message = new_building_message;
        }

        std::vector<std::string> all_possible_messages(std::string dependencies)
        {
            std::vector<std::string> possible_messages;
            std::string unprocessed_part = dependencies;
            std::vector<std::string> building_message;
            while (unprocessed_part.size() > 0)
            {
                if(unprocessed_part.find(" ") < unprocessed_part.find("|") || (unprocessed_part.find(" ") == unprocessed_part.find("|")))
                {
                    int end = (unprocessed_part.find(" ") > (unprocessed_part.size() - 1)) ? unprocessed_part.size() : unprocessed_part.find(" ");
                    // just read the number - dependency rule number
                    int number = std::stoi(unprocessed_part.substr(0, end));
                    if(end != unprocessed_part.size() - 1 && (unprocessed_part.find(" ") != unprocessed_part.find("|")))
                        unprocessed_part = unprocessed_part.substr(end + 1);
                    else unprocessed_part = "";
                    append(building_message, number);
                }
                else if(unprocessed_part.find("|") < SIZE_MAX)
                {
                    unprocessed_part = unprocessed_part.substr(unprocessed_part.find("|") + 2);
                    possible_messages.insert(possible_messages.end(), building_message.begin(), building_message.end());
                    building_message.clear();
                }
            }
            possible_messages.insert(possible_messages.end(), building_message.begin(), building_message.end());
            return possible_messages;
        }

        void build_messages()
        {
            for(auto pair : map_rules_and_letters)
                map_rules_and_messages[pair.first].push_back(pair.second);
            while(map_rules_and_messages.size() != map_rules_and_letters.size() + map_rules_and_dependencies.size())
            {
                for(std::pair<int, std::string> rule : map_rules_and_dependencies)
                {
                    if(map_rules_and_messages.find(rule.first) == map_rules_and_messages.end() &&
                    all_dependencies_processed(rule.second))
                    {
                        map_rules_and_messages[rule.first] = all_possible_messages(rule.second);
                    }
                }
            }
        }

        Messenger(std::string file_name, bool advanced = false)
        {
            std::ifstream file(file_name);
            std::string line;
            ReadMode read_mode = ReadMode::rules;
            if(file.is_open())
            {
                while(std::getline(file, line))
                {
                    if(line.empty())
                        read_mode = ReadMode::messages;
                    else
                        read_mode == ReadMode::rules ? add_rule(line, advanced) : messages.push_back(line);
                }
            }
            build_messages();
        }

        void add_rule(std::string line, bool advanced = false)
        {
            int rule_number = std::stoi(line.substr(0, line.find(":")));
            if(advanced && (rule_number == 0 || rule_number == 8 || rule_number == 11)) // skip special cases
            {
                return;
            }
            bool rule_with_letter = line.find("\"") != SIZE_MAX;
            if(rule_with_letter)
                map_rules_and_letters[rule_number] = line.substr(line.find("\"") + 1, 1);
            else
                map_rules_and_dependencies[rule_number] = line.substr(line.find(": ") + 2);
        }

        bool is_match(int rule, std::string message)
        {
            return std::find(
                map_rules_and_messages[rule].begin(),
                map_rules_and_messages[rule].end(), message)
                != map_rules_and_messages[rule].end();
        }

        int count_valid_messages(int rule)
        {
            int count = 0;
            count = std::count_if(messages.begin(), messages.end(),
            [&](const auto message) { return is_match(rule, message); });
            return count;
        }

        enum class CheckMode
        {
            check_42 = 1, check_31 = 2
        };

        bool is_match_for_rule_0(std::string message)
        {
            int combination_size_42 = map_rules_and_messages[42][0].size();
            int combination_size_31 = map_rules_and_messages[31][0].size();
            int count_42 = 0;
            int count_31 = 0;
            CheckMode mode = CheckMode::check_42;
            while(!message.empty())
            {
                std::string subset = message.substr(0, mode == CheckMode::check_42 ?
                combination_size_42 : combination_size_31);
                if(mode == CheckMode::check_42)
                {
                    if(!is_match(42, subset))
                    {
                        mode = CheckMode::check_31;
                        continue;
                    }
                    count_42++;
                }
                else
                {
                    if(!is_match(31, subset)) return false;
                    count_31++;
                }
                message = message.substr(mode == CheckMode::check_42 ?
                combination_size_42 : combination_size_31);
            }
            return count_42 > count_31 && count_31 > 0;
        }

        int count_valid_messages_for_rule_0()
        {
            int count = 0;
            count = std::count_if(messages.begin(), messages.end(),
            [&](const auto message) { return is_match_for_rule_0(message); });
            return count;
        }
};

void part1(const std::string& file_name)
{
    std::cout << "======\nPart 1\n======\n";
    Messenger messenger(file_name); // builds rules and stores messages to check
    std::cout << messenger.count_valid_messages(0) << " messages match rule 0." << '\n';
}

void part2(const std::string& file_name)
{
    std::cout << "======\nPart 2\n======\n";
    Messenger messenger(file_name, true); // builds rules and stores messages to check
    std::cout << messenger.count_valid_messages_for_rule_0() << " messages match rule 0." << '\n';
}

int main()
{
    const std::string file_name = "/home/daria/Documents/AoC2020/input/day19.txt";
    part1(file_name);
    std::cout << '\n';
    part2(file_name);
    std::cout << '\n';
}


//  Copyright (c) 2018 Robert Ramey
//
// 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)

#include <boost/integer.hpp>
#include <boost/safe_numerics/utility.hpp>

// include headers to support safe integers
#include <boost/safe_numerics/cpp.hpp>

using promotion_policy = boost::safe_numerics::cpp<
    8,  // char      8 bits
    16, // short     16 bits
    16, // int       16 bits
    16, // long      32 bits
    32  // long long 32 bits
>;

template<typename R, typename T, typename U>
struct test {
    using ResultType = promotion_policy::result_type<T,U>;
    //boost::safe_numerics::utility::print_type<ResultType> pt;
    static_assert(
        std::is_same<R, ResultType>::value,
        "is_same<R, ResultType>"
    );
};

test<std::uint16_t, std::uint8_t, std::uint8_t> t1;

int main(){
    return 0;
}


//
// Copyright © 2017 Arm Ltd. All rights reserved.
// SPDX-License-Identifier: MIT
//
#include <armnn/ArmNN.hpp>

#include <boost/algorithm/string.hpp>
#include <boost/test/unit_test.hpp>
#include <layers/BatchToSpaceNdLayer.hpp>
#include <Graph.hpp>

BOOST_AUTO_TEST_SUITE(LayerValidateOutput)

BOOST_AUTO_TEST_CASE(TestBatchToSpaceInferOutputShape)
{
    armnn::Graph graph;

    armnn::BatchToSpaceNdDescriptor descriptor;
    descriptor.m_BlockShape = {2, 2};
    descriptor.m_Crops = {{0, 0}, {2, 0}};
    descriptor.m_DataLayout = armnn::DataLayout::NHWC;

    armnn::BatchToSpaceNdLayer* const batchToSpaceLayer =
        graph.AddLayer<armnn::BatchToSpaceNdLayer>(descriptor, "batchToSpace");

    std::vector<armnn::TensorShape> shapes;
    const std::vector<unsigned int> theDimSizes = {8, 1, 3, 1};
    armnn::TensorShape shape(4, theDimSizes.data());
    shapes.push_back(shape);

    const std::vector<unsigned int> expectedDimSizes = {2, 2, 4, 1};
    armnn::TensorShape expectedShape(4, expectedDimSizes.data());

    BOOST_CHECK(expectedShape == batchToSpaceLayer->InferOutputShapes(shapes).at(0));
}

BOOST_AUTO_TEST_SUITE_END()