#include <fstream>
#include <sstream>
#include <iostream>
#include <thread>
#include <string>
#include <cmath>
#include <map>
#include <utility>
#include <unordered_map>

//#include <inttypes.h>

#include <boost/tr1/random.hpp>

#include "utils.h"
#include "utils/hdf5.h"
#include "utils/string.h"

//#include "WordEmbed.h"

const int exp_table_size = 1000;
const int max_exp = 6;
const int max_sentence_length = 1000;
const int64_t table_size = 100000000;

using namespace util::io;

///// Data Structure
namespace word2vec{
namespace type{

using int_t = int64_t;
using float_t = float;
using char_t = char;

using hashmap_t = std::unordered_map<std::string, int_t>;
}//namespace word2vec::type
}//namespace word2vec

namespace word2vec{

struct TokenizedFile{
    TokenizedFile(std::string train_file) {
        val.open(train_file, std::ifstream::in);

        if(val.fail()) {
            std::cout << "ERROR: training data file not found!\n";
            exit(1);
        }
    }
    std::ifstream val;
};

//CAUTION!! Do not include a following line in a header.
using namespace word2vec::type;

void VocabLearn(TokenizedFile & file, hashmap_t &word_count, std::vector<std::pair<int64_t,std::string>> &word_position, int64_t &pos){
    std::string line;
    while (std::getline(file.val, line)){
        std::istringstream iss{line};
        auto words = util::string::split(line);
        for(auto x : words) {
            word_position.push_back(std::make_pair(pos,x));
            pos++;
            auto isin = word_count.find(x);
            if(isin != word_count.end()) {
                word_count[x]+=1;
            } else {
                word_count[x]= 1;
            }
        }
    }
    /*
    hashmap_t word_count_temp;
    word_count_temp = word_count;
    for(auto x : word_count_temp) {
        if(x.second < 5) {
            word_count.erase(x.first);
        }
        }*/
    pos--; // to have last index of pos as pos_max
}

void PrintWordCount(hashmap_t const &word_count){
    for(auto x : word_count){
        std::cout << x.first << " " <<x.second<< std::endl;
    }
}

void InsertSpecialTag(hashmap_t &word_count){
    auto word2vec_sentence_delim = "</s>";
    word_count[word2vec_sentence_delim]=1;
}

auto MapValues(hashmap_t const &map){
    std::vector<hashmap_t::mapped_type> values;
    for(auto x : map){
        values.push_back(x.second);
    }
    return values;
}
auto MapKeys(hashmap_t const &map){
    std::vector<hashmap_t::key_type> values;
    for(auto x : map){
        values.push_back(x.first);
    }
    return values;
}

auto Concat(std::vector<std::string> const &words){
    std::vector<char> vec;
    for(auto const &x:words){
        std::copy(x.cbegin(),x.cend(),std::back_inserter(vec));
        vec.push_back('\0');
    }
    return vec;
}
auto ToStrings(std::vector<char> const &concat_words){
    std::vector<std::string> words;
    auto it =concat_words.cbegin();
    auto end=concat_words.cend();
    while(it!=end){
        words.push_back(std::string{&(*it)});
        //std::cout<<std::string{&(*it)}<<std::endl;
        it=std::find(it, end, '\0');
        ++it;
    }
    return words;
}
}//namespace word2vec

///// Data Structure

bool VCompare(int64_t i, int64_t j) { return (i>j); }

// main function arguments

using namespace word2vec;

class Word2Vec {
private:
    int layer1_size;
    int window;
    int num_threads;
    int iter;
    int hs, negative;
    int cbow;

    int min_reduce;
    int min_count;
    int64_t vocab_size;
    int64_t train_words;
    int64_t word_count_actual;
    int64_t pos_max;

    float sample;
    float alpha, starting_alpha;

    std::string train_file, output_file;
    std::string save_vocab_file, read_vocab_file;

    std::array<float, exp_table_size> expTable;
    std::array<int, table_size> table;

    clock_t start;

    std::vector<hashmap_t::key_type> vocab;
    std::vector<hashmap_t::mapped_type> vocabcn;

    hashmap_t word_cn;
    std::vector<std::pair<int64_t,std::string>> word_pos;
    hashmap_t word_idx;

    std::vector<float> syn0, syn1, syn1neg;

public:
    Word2Vec (
        std::string train_file, //Use text data from <file> to train the model
        std::string output_file, //Use <file> to save the resulting word vectors / word clusters
        unsigned int min_count, //This will discard words that appear less than <int> times; default is 5
        std::string save_vocab_file, //The vocabulary will be saved to <file>
        std::string read_vocab_file, //The vocabulary will be read from <file>, not constructed from the training data
        int layer1_size, //Set size of word vectors; default is 100
        int window, //Set max skip length between words; default is 5
        float sample, //Set threshold for occurrence of words. default is 1e-3, useful range is (0, 1e-5)
        int hs, //Use Hierarchical Softmax; default is 0 (not used)
        int negative, //Number of negative examples; default is 5, common values are 3 - 10 (0 = not used)
        int num_threads, //Use <int> threads (default 12)
        int iter, //Run more training iterations (default 5)
        float alpha, //Set the starting learning rate; default is 0.025 for skip-gram and 0.05 for CBOW
        int cbow = 0 //Use the continuous bag of words model; default is 1 (use 0 for skip-gram model)
        ):
        train_file (train_file),
        output_file (output_file),
        save_vocab_file (save_vocab_file),
        read_vocab_file (read_vocab_file),
        min_count (min_count),
        layer1_size (layer1_size),
        window (window),
        sample (sample),
        hs (hs),
        negative (negative),
        num_threads (num_threads),
        iter (iter),
        alpha (alpha),
        cbow (cbow)
    {
        min_reduce = 1;
        vocab_size = 0;
        train_words = 0;
        word_count_actual = 0;
        pos_max = 0;

        //infile = TokenizedFile sinfile{train_file};
        //auto word_count = WordCount(infile);

        //auto word_count_values = MapValues(word_count);
        //auto word_count_keys = MapKeys(word_count);

    };
    void LearnVocab();
    //void CreateBinaryTree();
    void InitUnigramTable();
    void testFunction();
    void InitNet();
    void TrainModelThread(int tid);
    void TrainModel();
};

void Word2Vec::LearnVocab(){

    std::cout << "I'm here!\n";
    TokenizedFile infile{train_file};
    VocabLearn(infile, word_cn, word_pos, pos_max);

    hashmap_t::iterator it = word_cn.begin();
    while(it != word_cn.end()){
        if(it -> second < 5){
            it = word_cn.erase(it);
        } else {
            ++it;
        }
    }

    std::cout << "Vocab Learning is complete.\n";
    vocab = MapKeys(word_cn);
    vocabcn = MapValues(word_cn);

    std::vector<std::pair<std::string,int64_t>> v;
    for(int i = 0; i < vocab.size(); i++){
        v.push_back(std::make_pair(vocab[i],vocabcn[i]));
    }
    //std::sort(vocabcn.begin(), vocabcn.end(), VCompare);
    //std::cout << vocabcn[0] << " " << vocabcn[1] << " " << vocabcn[2] << std::endl;

    vocab_size = vocabcn.size();

    std::sort(v.begin(), v.end(), [](auto &left, auto &right) {
    return left.second > right.second;
    });

    std::cout << v[0].first << std::endl;
    std::cout << v[0].second << std::endl;

    int64_t idx = 0;
    for(auto x : v){
        word_idx[x.first] = idx;
        idx++;
    }
    std::cout << "Indexing words is complete.\n";
}

void Word2Vec::InitUnigramTable() {
    unsigned int i;
    float train_words_pow = 0;
    float d1;
    float power = 0.75;

    for(auto w : vocabcn) train_words_pow += pow(w, power);
    i = 0;
    d1 = pow(vocabcn[0], power)/train_words_pow;
    for(unsigned int a = 0; a < table_size; a++){
        table[a] = i;
        if(a / (float)table_size > d1){
            i++;
            d1 += pow(vocabcn[i],power)/train_words_pow;
        }
        if(i >= vocab_size) i = vocab_size -1;
    }

}

void Word2Vec::testFunction(){
    //    for(auto x : vocab) std::cout << x << std::endl;
    for(auto x : word_pos) std::cout << x.first << "  " << x.second << std::endl;
    std::cout << pos_max << std::endl;
}

void Word2Vec::InitNet() {

    unsigned int a, b;

    boost::mt19937 rand_engine_double;  // rand engine
    boost::uniform_real<> rand_double(0.0, 1.0);
    boost::variate_generator<boost::mt19937, boost::uniform_real<>> rand_gen_double(rand_engine_double, rand_double);

    for (int i = 0; i <= exp_table_size; i++) {
        expTable[i] = exp((i / (double)exp_table_size * 2 - 1) * max_exp);
        expTable[i] = expTable[i] / (expTable[i] + 1);
    }

    syn0.resize(vocab_size * layer1_size);
    for(auto &x : syn0) x= (rand_gen_double() - 0.5) / layer1_size;

    if(hs) {
        syn1.resize(vocab_size * layer1_size);
        for(auto &x : syn1) x=0;
    }
    if(negative > 0) {
        syn1neg.resize(vocab_size * layer1_size);
        for(auto &x : syn1neg) x=0;
    }

    //CreateBinaryTree();
}

void Word2Vec::TrainModelThread(int tid){
    int64_t a, b, d, word, last_word, sentence_length = 0, sentence_position = 0;
    int64_t word_count = 0, last_word_count = 0;
    std::vector<int64_t> sen(max_sentence_length+1,int64_t{0});//[max_sentence_length + 1];
    int64_t l1, l2, c, target, label, local_iter = iter;
    int64_t next_random;
    float f, g;
    clock_t now;
    int64_t pos= 0; // Should be reset.
    int64_t starting_pos;
    int64_t cnt;

    next_random = 1;
    //sen.resize(max_sentence_length+1);
    //std::cout << "sen = " << sen.at(max_sentence_length+3) << std::endl;
    boost::mt19937 rand_engine_int;    // rand engine
    //boost::uniform_int : min,max is inclusive
    boost::uniform_int<> rand_int_in_vocab(1,vocab_size-1);
    boost::variate_generator<boost::mt19937, boost::uniform_int<>> sample_in_vocab(rand_engine_int, rand_int_in_vocab);
    boost::uniform_int<> rand_int_in_table(0,table_size-1);
    boost::variate_generator<boost::mt19937, boost::uniform_int<>> sample_in_table(rand_engine_int, rand_int_in_table);
    boost::uniform_int<> rand_int_in_window(0,window-1);
    boost::variate_generator<boost::mt19937, boost::uniform_int<>> sample_in_window(rand_engine_int, rand_int_in_window);

    boost::mt19937 rand_engine_double;  // rand engine
    boost::uniform_real<> rand_double(0.0, 1.0);
    boost::variate_generator<boost::mt19937, boost::uniform_real<>> rand_gen_double(rand_engine_double, rand_double);

    std::vector<float> neu1;
    std::vector<float> neu1e;

    neu1.resize(layer1_size);
    neu1e.resize(layer1_size);

    //inFile.seekg(file_size / (int64_t)num_threads * (int64_t)tid);
    starting_pos = pos_max / (int64_t)num_threads * (int64_t)tid;
    pos = starting_pos;
    std::cout << pos << std::endl;fflush(stdout);
    while(1) {

        if(word_count - last_word_count > 10000) {
            word_count_actual += word_count - last_word_count;
            last_word_count = word_count;

            now = clock();
            printf("%cAlpha: %f  Progress: %.2f%%  Words/thread/sec: %.2fk   ", 13, alpha,
                    word_count_actual / (double)(iter * pos_max + 1) * 100,
                    word_count_actual / ((double)(now - start + 1) / (double)CLOCKS_PER_SEC * 1000));
            fflush(stdout);

            alpha = starting_alpha * (1 - word_count_actual / (double)(iter * pos_max + 1));
            if(alpha < starting_alpha * 0.0001) alpha = starting_alpha * 0.0001;
        }

        if(sentence_length == 0) {
            while(1) {
                if(pos>=pos_max) break;

                if(word_idx.find(word_pos[pos].second) == word_idx.end()){
                    pos++;
                    continue;
                }
                word_count++;
                //if(word == 0) break;
                cnt = word_cn.find(word_pos[pos].second) -> second; // log(n)
                 // The subsampling randomly discards frequent words while keeping the ranking same
                if(sample > 0) {
                    double ran = (sqrt(cnt / (sample * pos_max)) + 1) * (sample * pos_max) / cnt;
                    if(ran < rand_gen_double()) {
                        pos++;
                        continue;
                    }
                }

                sen[sentence_length] = word_idx.find(word_pos[pos].second) -> second;//word_cn.find(word_pos[pos].second);
                pos++;
                sentence_length++;
                if(sentence_length >= max_sentence_length) break;
            }
            sentence_position = 0;
        }

        if((pos>=pos_max) || (word_count > pos_max / (int64_t)num_threads )) {
            word_count_actual += word_count - last_word_count;
            local_iter--;
            if(local_iter == 0) break;
            word_count = 0;
            last_word_count = 0;
            sentence_length = 0;
            pos = starting_pos;
            continue;
        }

        word = sen[sentence_position];
        //if(word == -1) continue;
        // Network Initialization
        for(auto &x:neu1) x = 0;
        for(auto &x:neu1e) x = 0;

        b = sample_in_window();
        for(a = b; a < window * 2 + 1 - b; a++) if(a != window) {
                c = sentence_position - window + a;
                if(c < 0) continue;
                if(c >= sentence_length) continue;
                last_word = sen[c];
                if(last_word == -1) continue;
                l1 = last_word * layer1_size;
                // Hierarchical Softmax
                /*if(hs) for(d = 0; d < vocab[word].codelen; d++) {
                    f = 0;
                    l2 = vocab[word].point[d] * layer1_size;
                    // Propagate hidden -> output
                    for(c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1[c + l2];
                    if(f <= -MAX_EXP) continue;
                    else if(f >= MAX_EXP) continue;
                    else f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))];
                    // g is the gradient multiplied by the learning rate
                    g = (1 - vocab[word].code[d] - f) * alpha;
                    // Backpropagate errors output -> hidden
                    for(c = 0; c < layer1_size; c++) neu1e[c] += g * syn1[c + l2];
                    // Learn weights hidden -> output
                    for(c = 0; c < layer1_size; c++) syn1[c + l2] += g * syn0[c +l1];
                    }*/
                // Negative Sampling
                if(negative > 0) for (d = 0; d < negative + 1 ; d++) {
                        if(d == 0) {
                            target = word;
                            label = 1;
                        } else {
                            //next_random = sample_in_table();
                            //target = table[next_random];
                            //if(target == 0) target = next_random % (vocab_size - 1) + 1;
                            target = table[sample_in_table()];
                            //if(target == 0) target = sample_in_vocab();
                            if(target == word) continue;
                            target = sample_in_vocab();
                            label = 0;
                        }

                        l2 = target * layer1_size;
                        f = 0;

                        for(c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1neg[c + l2];

                        if(f > max_exp) g = (label - 1) * alpha;
                        else if(f < - max_exp) g = (label - 0) * alpha;
                        else g = (label - expTable[(int)((f + max_exp) * (exp_table_size / max_exp / 2))]) * alpha;

                        for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1neg[c + l2];
                        for (c = 0; c < layer1_size; c++) syn1neg[c + l2] += g * syn0[c + l1];
                    }
                // Learn weights input -> hidden
                for(c = 0; c < layer1_size; c++) syn0[c + l1] += neu1e[c];
            }
        sentence_position++;
        if(sentence_position >= sentence_length) {
            sentence_length = 0;
            continue;
        }

    }
}

void Word2Vec::TrainModel() {
    std::ofstream outFile;

    std::vector<std::thread> th;
    starting_alpha = alpha;

    std::cout << "Starting training using file " << train_file << std::endl;
    if(output_file[0] == 0) return;

    // Initialization
    InitNet();
    if(negative > 0)
        InitUnigramTable();

    start = clock(); // start to measure time

    for(int i = 0; i < num_threads; i++) {
      th.push_back(std::thread(&Word2Vec::TrainModelThread, this, i));
    }

    for(auto &t : th) {
      t.join();
    }

    //TrainModelThread(); // For the single thread

    outFile.open(output_file, std::ofstream::out);
    outFile << vocab_size << " " << layer1_size << std::endl;
    for(auto x : word_idx) {
        outFile << x.first << " ";
        for(unsigned int b = 0; b < layer1_size; b++) outFile << syn0[x.second * layer1_size + b] << " ";
        outFile << std::endl;
    }

    auto word_concat = Concat(MapKeys(word_idx));
    //syn0: beg=syn0[idx*dim], end=syn0[(idx+1)*dim];
    H5file file{H5name{"data.h5"}, hdf5::FileMode::replace};
    file.writeRawData(H5name{std::string{"foo.vec" }}, syn0);
    //TODO: check if syn0 and word_concat has consistantly ordered.
    file.writeRawData(H5name{std::string{"foo.word"}}, word_concat);//it was vocab, which was the cause of compile error.
}

// End of Learning Net

void printHelp() {
    std::cout << "c++ word2vector implementation \n\n";
    std::cout << "Options:\n";
    std::cout << "Parameters for training:\n";
    std::cout << "\t-train <file>\n";
    std::cout << "\t\tUse text data from <file> to train the model\n";
    std::cout << "\t-output <file>\n";
    std::cout << "\t\tUse <file> to save the resulting word vectors / word clusters\n";
    std::cout << "\t-size <int>\n";
    std::cout << "\t\tSet size of word vectors; default is 100\n";
    std::cout << "\t-window <int>\n";
    std::cout << "\t\tSet max skip length between words; default is 5\n";
    std::cout << "\t-sample <float>\n";
    std::cout << "\t\tSet threshold for occurrence of words. Those that appear with higher frequency in the training data\n";
    std::cout << "\t\twill be randomly down-sampled; default is 1e-3, useful range is (0, 1e-5)\n";
    std::cout << "\t-hs <int>\n";
    std::cout << "\t\tUse Hierarchical Softmax; default is 0 (not used)\n";
    std::cout << "\t-negative <int>\n";
    std::cout << "\t\tNumber of negative examples; default is 5, common values are 3 - 10 (0 = not used)\n";
    std::cout << "\t-threads <int>\n";
    std::cout << "\t\tUse <int> threads (default 12)\n";
    std::cout << "\t-iter <int>\n";
    std::cout << "\t\tRun more training iterations (default 5)\n";
    std::cout << "\t-min-count <int>\n";
    std::cout << "\t\tThis will discard words that appear less than <int> times; default is 5\n";
    std::cout << "\t-alpha <float>\n";
    std::cout << "\t\tSet the starting learning rate; default is 0.025 for skip-gram and 0.05 for CBOW\n";
    std::cout << "\t-save-vocab <file>\n";
    std::cout << "\t\tThe vocabulary will be saved to <file>\n";
    std::cout << "\t-read-vocab <file>\n";
    std::cout << "\t\tThe vocabulary will be read from <file>, not constructed from the training data\n";
    std::cout << "\t-cbow <int>\n";
    std::cout << "\t\tUse the continuous bag of words model; default is 1 (use 0 for skip-gram model)\n";
    std::cout << "\nExamples:\n";
    std::cout << "./word2vec -train data.txt -output vec.txt -size 200 -window 5 -sample 1e-4 -negative 5 -hs 0 -binary 0 -cbow 1 -iter 3\n\n";

}

int argpos(const char *str, int argc, char **argv) {
    std::string s_str(str);
    std::vector<std::string> s_argv(argv,argv+argc);

    int i=0;
    while (i<argc) {
        if(s_str == s_argv[i]) break;
        i++;
    }
    if(i == argc) i=0;

    return i;
}

Word2Vec *arg_to_w2v(int argc, char **argv) {

    int i;
    int layer1_size = 100;
    std::string train_file ="", save_vocab_file = "", read_vocab_file = "";
    int debug_mode =2, binary = 0, cbow = 0;
    float alpha = 0.05;
    std::string output_file = "";
    int window = 5;
    float sample = 1e-3;
    int hs = 0, negative = 5, num_threads = 12;
    int64_t iter = 5;
    int min_count = 5;
    int64_t classes = 0;
    std::string wordvector_file = "";

    if ((i = argpos("-size", argc, argv)) > 0) layer1_size = atoi(argv[i + 1]);
    if ((i = argpos("-train", argc, argv)) > 0) train_file = argv[i + 1];
    if ((i = argpos("-save-vocab", argc, argv)) > 0) save_vocab_file = argv[i + 1];
    if ((i = argpos("-read-vocab", argc, argv)) > 0) read_vocab_file = argv[i + 1];
    if ((i = argpos("-cbow", argc, argv)) > 0) cbow = atoi(argv[i + 1]);
    if (cbow) alpha = 0.05; else alpha = 0.025;
    if ((i = argpos("-alpha", argc, argv)) > 0) alpha = atof(argv[i + 1]);
    if ((i = argpos("-output", argc, argv)) > 0) output_file = argv[i + 1];
    if ((i = argpos("-window", argc, argv)) > 0) window = atoi(argv[i + 1]);
    if ((i = argpos("-sample", argc, argv)) > 0) sample = atof(argv[i + 1]);
    if ((i = argpos("-hs", argc, argv)) > 0) hs = atoi(argv[i + 1]);
    if ((i = argpos("-negative", argc, argv)) > 0) negative = atoi(argv[i + 1]);
    if ((i = argpos("-threads", argc, argv)) > 0) num_threads = atoi(argv[i + 1]);
    if ((i = argpos("-iter", argc, argv)) > 0) iter = atoi(argv[i + 1]);
    if ((i = argpos("-min-count", argc, argv)) > 0) min_count = atoi(argv[i + 1]);

    Word2Vec *w2v = new Word2Vec (
                train_file,
                output_file,
                min_count,
                save_vocab_file,
                read_vocab_file,
                layer1_size,
                window,
                sample,
                hs,
                negative,
                num_threads,
                iter,
                alpha,
                cbow );

    return w2v;
}

int main(int argc, char **argv) {

    //std::vector<char> concat_words = Concat(word_count_keys);

    //PrintWordCount(word_count);

    //H5file file{H5name{"data.h5"}, hdf5::FileMode::read_exist};
    //file.writeRawData(H5name{"bar.word_count"},word_count_values);
    //file.writeRawData(H5name{"bar.word_key"},concat_words);

    //concat_read = file.readRawData(H5name{"bar.word_key"},concat_words);
    //auto words = ToStrings(concat_read);
    //assert(words=word_count_keys);

    Word2Vec *w2v;

    if(argc < 2) { printHelp(); return 1; }
    else {
        w2v = arg_to_w2v(argc, argv);
    }
    w2v -> LearnVocab();
    //w2v -> testFunction();
    w2v->TrainModel();

    return 0;
}


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020 Alexander Sokolov <asokolov@nil.foundation>
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_HASH_HAIFA_CONSTRUCTION_HPP
#define CRYPTO3_HASH_HAIFA_CONSTRUCTION_HPP

#include <boost/crypto3/hash/detail/nop_finalizer.hpp>

#include <boost/crypto3/detail/static_digest.hpp>
#include <boost/crypto3/detail/pack.hpp>

namespace boost {
    namespace crypto3 {
        namespace hashes {
            /*!
             * @brief
             * @tparam DigestEndian
             * @tparam DigestBits
             * @tparam IV
             * @tparam Compressor
             * @tparam Finalizer
             *
             * The HAIFA construction builds a block hashes from a
             * one-way compressor.  As this version operated on the block
             * level, it doesn't contain any padding or other strengthening.
             * For a Wide Pipe construction, use a digest that will
             * truncate the internal state.
             *
             * @note https://eprint.iacr.org/2007/278.pdf
             */
            template<typename Params, typename IV, typename Compressor, typename Padding,
                     typename Finalizer = detail::nop_finalizer>
            class haifa_construction {
            public:
                typedef Compressor compressor_functor;
                typedef Padding padding_functor;
                typedef Finalizer finalizer_functor;

                typedef typename Params::digest_endian endian_type;

                constexpr static const std::size_t salt_bits = compressor_functor::salt_bits;
                typedef typename compressor_functor::salt_type salt_type;
                constexpr static const salt_type salt_value = compressor_functor::salt_value;

                typedef typename compressor_functor::iv_generator iv_generator;

                constexpr static const std::size_t word_bits = compressor_functor::word_bits;
                typedef typename compressor_functor::word_type word_type;

                constexpr static const std::size_t state_bits = compressor_functor::state_bits;
                constexpr static const std::size_t state_words = compressor_functor::state_words;
                typedef typename compressor_functor::state_type state_type;

                constexpr static const std::size_t block_bits = compressor_functor::block_bits;
                constexpr static const std::size_t block_words = compressor_functor::block_words;
                typedef typename compressor_functor::block_type block_type;

                constexpr static const std::size_t digest_bits = Params::digest_bits;
                constexpr static const std::size_t digest_bytes = digest_bits / octet_bits;
                constexpr static const std::size_t digest_words =
                    digest_bits / word_bits + ((digest_bits % word_bits) ? 1 : 0);
                typedef static_digest<digest_bits> digest_type;

            protected:
                constexpr static const std::size_t length_bits = Params::length_bits;
                // FIXME: do something more intelligent than capping at 64
                constexpr static const std::size_t length_type_bits =
                    length_bits < word_bits ? word_bits : length_bits > 64 ? 64 : length_bits;
                typedef typename boost::uint_t<length_type_bits>::least length_type;
                constexpr static const std::size_t length_words = length_bits / word_bits;
                BOOST_STATIC_ASSERT(!length_bits || length_bits % word_bits == 0);

            public:
                template<typename Integer = std::size_t>
                inline haifa_construction &process_block(const block_type &block, Integer seen,
                                                         Integer finalization = 0) {
                    compressor_functor::process_block(state_, block, seen, finalization);
                    return *this;
                }

                inline digest_type digest(const block_type &block = block_type(),
                                          std::size_t total_seen = length_type()) {
                    using namespace boost::crypto3::detail;

                    block_type b = block;
                    // Process block if it is full
                    if (total_seen && !(total_seen % block_bits))
                        process_block(b, total_seen);

                    // Pad last message block
                    padding_functor padding;
                    padding(b, total_seen);

                    // Process last block
                    process_block(b, total_seen, salt_value);

                    // Apply finalizer
                    finalizer_functor()(state_);

                    // Convert digest to byte representation
                    std::array<octet_type, state_bits / octet_bits> d_full;
                    pack_from<endian_type, word_bits, octet_bits>(state_.begin(), state_.end(), d_full.begin());
                    digest_type d;
                    std::copy(d_full.begin(), d_full.begin() + digest_bytes, d.begin());

                    return d;
                }

                haifa_construction() {
                    reset();
                }

                void reset(const state_type &s) {
                    state_ = s;
                    state_[0] ^= 0x01010000U ^ (digest_bits / CHAR_BIT);
                }

                void reset() {
                    iv_generator iv;
                    reset(iv());
                }

                state_type const &state() const {
                    return state_;
                }

            private:
                state_type state_;
            };

        }    // namespace hashes
    }        // namespace crypto3
}    // namespace boost

#endif    // CRYPTO3_HASH_HAIFA_CONSTRUCTION_HPP


//   -----------------------------------------------------------------------------------------------
//    Copyright 2015 André Bergner. 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 <iostream>
#include <array>

#include <boost/mpl/int.hpp>
#include <boost/mpl/min_max.hpp>
#include <boost/typeof/typeof.hpp>
#include <boost/proto/core.hpp>
#include <boost/proto/context.hpp>
#include <boost/proto/transform.hpp>
#include <boost/proto/debug.hpp>

#include <boost/algorithm/string/erase.hpp>

#include "callable_decltype.hpp"
#include "tuple_tools.hpp"
#include "demangle.h"

namespace mpl = boost::mpl;
namespace proto = boost::proto;

template< typename S >
void print_state( S const & s)
{
   auto s_name = type_name(s);
   boost::erase_all( s_name, "std::__1::" );
   boost::erase_all( s_name, "mpl_::" );
   std::cout << s_name << std::endl;
}

namespace building_blocks
{
   using namespace proto;

   //  ---------------------------------------------------------------------------------------------
   // definition of the building blocks of the language
   //  ---------------------------------------------------------------------------------------------

   template < typename I >  struct placeholder       { using arity = I; };
   template < typename T >  struct placeholder_arity { using type = typename T::arity; };

   template < typename idx = _ >
   using delayed_placeholder = subscript< terminal<placeholder<idx>> , terminal<placeholder<_>> >;

   //  ---------------------------------------------------------------------------------------------
   // block composition operators -- TODO needs correct grammar as arguments

   using channel_operator  = comma              < _ , _ >;
   using parallel_operator = bitwise_or         < _ , _ >;
   using sequence_operator = bitwise_or_assign  < _ , _ >;
   using feedback_operator = complement         < _ >;
}

using building_blocks :: placeholder;
using building_blocks :: placeholder_arity;
using building_blocks :: delayed_placeholder;

using building_blocks :: channel_operator;
using building_blocks :: sequence_operator;
using building_blocks :: parallel_operator;
using building_blocks :: feedback_operator;

BOOST_PROTO_DEFINE_ENV_VAR( current_input_t, current_input );
BOOST_PROTO_DEFINE_ENV_VAR( delayed_input_t, delayed_input );

//  ------------------------------------------------------------------------------------------------
// very simple delay_line operation on std::array --> TODO should be own type static_delay_line !

struct no_state {};

struct
{
    template< typename T , size_t N , typename Y >
    void operator()( std::array<T,N>& xs , Y y ) const
    {
        for ( size_t n = 1 ; n < xs.size() ; ++n )  xs[n-1] = xs[n];
        xs.back() = y;
    }

    template< typename T , typename Y >
    void operator()( std::array<T,0>& , Y ) const { }

    template< typename T , typename Y >
    void operator()( no_state , Y ) const { }

    template< typename T , typename Y >   // if input is not an array, e.g. for not fully
    void operator()( T& , Y ) const { }   // unpacked state while traversing the tree (tuple o'tuples)
}
rotate_push_back;

namespace transforms
{
   using namespace proto;

   //  ---------------------------------------------------------------------------------------------
   // static analysis transforms  --  helpers that inspect structure of expression
   //  ---------------------------------------------------------------------------------------------

   struct output_arity;

   struct input_arity : or_
   <  when
      <  delayed_placeholder<>
      ,  placeholder_arity<_value(_left)>()
      >
   ,  when
      <  terminal< placeholder<_> >
      ,  placeholder_arity<_value>()
      >
   ,  when
      <  terminal<_>
      ,  mpl::int_<0>()
      >
   ,  when
      <  feedback_operator
      ,  mpl::max
         <  mpl::int_<0>
         ,  mpl::minus< input_arity(_child) , output_arity(_child) >
         >()
      >
   ,  when
      <  parallel_operator
      ,  mpl::plus< input_arity(_left) , input_arity(_right) >()
      >
   ,  when
      <  sequence_operator
      ,  mpl::plus
         <  input_arity(_left)
         ,  mpl::max
            <  mpl::int_<0>
            ,  mpl::minus< input_arity(_right) , output_arity(_left) >
            >
         >()
      >
   ,  when
      <  nary_expr<_, vararg<_>>
      ,  fold<_, mpl::int_<0>(), mpl::max<input_arity, _state>()>
      >
   >
   {};

   struct output_arity : or_
   <  when
      <  channel_operator
      ,  mpl::plus< output_arity(_left) , output_arity(_right) >()
      >
   ,  when
      <  feedback_operator
      ,  output_arity(_child)
      >
   ,  when
      <  parallel_operator
      ,  mpl::plus< output_arity(_left) , output_arity(_right) >()
      >
   ,  when
      <  sequence_operator
      ,  mpl::plus
         <  output_arity(_right)
         ,  mpl::max
            <  mpl::int_<0>
            ,  mpl::minus< output_arity(_left) , input_arity(_right) >
            >
         >()
      >
   ,  otherwise< mpl::int_<1>() >
   >
   {};

   template < typename Expr >
   using input_arity_t = typename boost::result_of<input_arity(Expr)>::type;

   template < typename Expr >
   using output_arity_t = typename boost::result_of<output_arity(Expr)>::type;

   // -------------------------------------------------------------------------------------------
   // state and wire related tuple tools
   // -------------------------------------------------------------------------------------------

   template < typename arity , typename delay = mpl::int_<0> >
   struct make_arity_impl
   {
      using type = tuple_cat_t< repeat_t< arity::value-1ul, mpl::int_<0> >
                              , std::tuple<delay>
                              >;
   };

   template < typename delay >
   struct make_arity_impl<mpl::int_<0>, delay>
   {
      using type = std::tuple<>;
   };

   template < typename arity , typename delay = mpl::int_<0> >
   using make_arity = typename make_arity_impl<arity,delay>::type;

   struct delay_per_wire : callable_decltype
   {
      using default_t = placeholder<mpl::int_<0>>;

      template < typename Placeholder = default_t , typename Delay = default_t>
      auto operator()( Placeholder const & = default_t{}, Delay const & = default_t{} ) const
      {
         return make_arity<typename Placeholder::arity, typename Delay::arity>{};
      }
   };

   struct tuple_cat_fn : callable_decltype
   {
      template < typename... Tuples >
      auto operator()( Tuples&&... ts ) const
      {
         return std::tuple_cat( std::forward<Tuples>(ts)... );
      }
   };

   struct make_tuple_fn : callable_decltype
   {
      template < typename... Ts >
      auto operator()( Ts&&... ts ) const
      {
         return std::make_tuple( std::forward<Ts>(ts)... );
      }
   };

   // -------------------------------------------------------------------------------------------
   // max-zip tuple
   // -------------------------------------------------------------------------------------------

   template < typename Tuple1, typename Tuple2, std::size_t... Ns >
   auto zip_with_max( Tuple1&& t1, Tuple2&& t2, std::index_sequence<Ns...> )
   {
      using namespace std;
      return tuple< typename mpl::max< decay_t<decltype(get<Ns>(t1))> , decay_t<decltype(get<Ns>(t2))> >::type... >{};
   }

   template < typename Tuple1, typename Tuple2 >
   auto zip_with_max( Tuple1&&, Tuple2&&, std::index_sequence<> )
   {
      return std::tuple<>{};
   }

   struct max_delay_of_wires : callable_decltype
   {
      template < typename Tuple_l , typename Tuple_r >
      auto operator()( Tuple_l&& tl, Tuple_r&& tr ) const
      {
         using tuple_tl = std::decay_t<Tuple_l>;
         using tuple_tr = std::decay_t<Tuple_r>;
         using min_size = typename mpl::min< mpl::int_<std::tuple_size<tuple_tl>::value>
                                           , mpl::int_<std::tuple_size<tuple_tr>::value>
                                           >::type;
         return  std::tuple_cat(  zip_with_max(tl,tr,std::make_index_sequence<min_size::value>{})
                               ,  tuple_drop<min_size::value>( std::forward<Tuple_l>(tl) )
                               ,  tuple_drop<min_size::value>( std::forward<Tuple_r>(tr) )
                               );
      }
   };

   struct tuple_take_ : callable_decltype
   {
      template< typename Drop , typename Tuple >
      decltype(auto) operator()( Drop , Tuple const & t ) const
      {
         return tuple_take<Drop::value>(t);
      }
   };

   struct tuple_drop_ : callable_decltype
   {
      template< typename Drop , typename Tuple >
      decltype(auto) operator()( Drop , Tuple const & t ) const
      {
         return tuple_drop<Drop::value>(t);
      }
   };

   struct input_delays : or_
   <  when
      <  delayed_placeholder<>
      ,  delay_per_wire( _value(_left),_value(_right))
      >
   ,  when
      <  terminal< placeholder<_> >
      ,  delay_per_wire( _value() )
      >
   ,  when
      <  terminal<_>
      ,  std::tuple<>()//delay_per_wire()
      >
   ,  when
      <  feedback_operator
      ,  tuple_drop_
         (  output_arity(_child)
         ,  input_delays(_child)
         )
      >
   ,  when
      <  parallel_operator
      ,  tuple_cat_fn( input_delays(_left) , input_delays(_right) )
      >
   ,  when
      <  sequence_operator
      ,  tuple_cat_fn
         (  input_delays(_left)
         ,  tuple_drop_
            (  output_arity(_left)
            ,  input_delays(_right)
            )
         )
      >
   ,  when
      <  nary_expr<_, vararg<_>>
      ,  fold<_, std::tuple<>(), max_delay_of_wires(input_delays, _state) >
      >
   >
   {};

   struct identity : callable_decltype
   {
      template < typename X >
      auto operator()( X&& x ) { return std::forward<X>(x); }
   };

   template < typename StateCtor >
   struct build_state_impl
   {
      struct apply : or_
      <  when
         <  feedback_operator
         ,  make_tuple_fn
            (  StateCtor
               (  tuple_take_
                  ( output_arity(_child)
                  , input_delays(_child)
                  )
               )
            ,  apply( _child  )
            )
         >
      ,  when
         <  sequence_operator
         ,  make_tuple_fn
            (  StateCtor(input_delays( _right  ))
            ,  apply( _left  )
            ,  apply( _right )
            )
         >
      ,  when
         <  parallel_operator
         ,  make_tuple_fn
            (  apply( _left  )
            ,  apply( _right )
            )
         >
      ,  otherwise< std::tuple<>() >
      >
      {};
   };

   template < typename StateCtor = identity >
   using build_state = typename build_state_impl<StateCtor>::apply;

   //  ---------------------------------------------------------------------------------------------
   // Evaluators -- transforms that work together for the evaluation of flowz-expressions
   //  ---------------------------------------------------------------------------------------------

   struct place_the_holder;
   struct place_delay;
   struct sequence;
   struct feedback;
   struct parallel;

   struct eval_it : or_
   <  when
      <  delayed_placeholder<>
      ,  place_delay( _value(_left) , _value(_right) , _env_var<delayed_input_t> )
      >
   ,  when
      <  terminal< placeholder<_> >
      ,  place_the_holder( _value , _env_var<current_input_t> )
      >
   ,  when
      <  feedback_operator
      ,  feedback( _child , _env_var<current_input_t> , _env_var<delayed_input_t> )
      >
   ,  when
      <  sequence_operator
      ,  sequence( _left , _right , _env_var<current_input_t> , _env_var<delayed_input_t> )
      >
   ,  when
      <  parallel_operator
      ,  parallel( _left , _right , _env_var<current_input_t> , _env_var<delayed_input_t> )
      >
   ,  when
      <  channel_operator    //  grammar rule: only allowed after sequence or recursion at top of sub-tree
      ,  make_tuple_fn( eval_it(_left), eval_it(_right) )
      >
   ,  when
      <  _
      ,  _default< eval_it >
      >
   >
   {};

   //-----------------------------------------------------------------------------------------------

   template < typename T >
   using in_t = T const &;
   //using in_t = T;

   struct place_the_holder : callable_decltype
   {
      template < typename I , typename Tuple >
      auto operator()( placeholder<I> const & , Tuple const & args ) const
      {
         return std::get<I::value-1>( args );
      }
   };

   struct place_delay : callable_decltype
   {
      template < typename I , typename J , typename Delayed_input >
      auto operator()( placeholder<I> , placeholder<J> , in_t<Delayed_input> del_in ) const
      {
         auto const & s = std::get<I::value-1>(std::get<0>(del_in));
         return s[s.size()-J::value];
      }
   };

   struct sequence : callable_decltype
   {
      template < typename LeftExpr , typename RightExpr , typename Input , typename State >
      auto operator()( in_t<LeftExpr> l , in_t<RightExpr> r , in_t<Input> input , State state ) const
      {
         eval_it  e;

         auto in_state    = std::get<0>(state);
         auto node_state  = std::get<0>(std::get<1>(state));
         auto& left_state  = std::get<1>(std::get<1>(state));
         auto& right_state = std::get<2>(std::get<1>(state));

         auto left_result =
            flatten_tuple( std::make_tuple(
               e( l, 0, ( current_input = tuple_take<input_arity_t<LeftExpr>::value>(input) , delayed_input = std::tie(in_state,left_state) ))
            ));

         using left_size = std::tuple_size<decltype(left_result)>;
         auto right_input = tuple_cat( left_result, tuple_drop<left_size::value>(input) );

         auto right_result =
            flatten_tuple( std::make_tuple(
               e( r, 0, ( current_input = right_input , delayed_input = std::tie(node_state,right_state) ))
            ));

         tuple_for_each( rotate_push_back, node_state, left_result );
         using right_size = std::tuple_size<decltype(right_result)>;
         return tuple_cat( right_result, tuple_drop<right_size::value>(std::move(left_result)) );
      }
   };

   struct bottom_type {};

   struct feedback : callable_decltype
   {
      template < typename Expr , typename Input , typename State >
      auto operator()( in_t<Expr> x , in_t<Input> input , State state ) const
      {
         eval_it  e;

         auto in_state    = deep_tie(std::get<0>(state));
         auto node_state  = deep_tie(std::get<0>(std::get<1>(state)));
         auto child_state = deep_tie(std::get<1>(std::get<1>(state)));
         auto next_state  = std::tuple_cat( node_state, in_state );

         auto aligned_input = std::tuple_cat( repeat_t<output_arity_t<Expr>::value, bottom_type>{} , input );
         auto result =
            flatten_tuple( std::make_tuple(
               e( x, 0, ( current_input = aligned_input , delayed_input = std::tie(next_state,child_state) ))
            ));

         tuple_for_each( rotate_push_back, node_state, std::tuple_cat( result, input) );
         using size = std::tuple_size<decltype(result)>;
         return tuple_cat( result, tuple_drop<size::value>(input) );
      }
   };

   struct parallel : callable_decltype
   {
      template < typename LeftExpr , typename RightExpr , typename Input , typename State >
      auto operator()( in_t<LeftExpr> l , in_t<RightExpr> r , in_t<Input> input , State state ) const
      {
         eval_it  e;

         auto in_state    = deep_tie(std::get<0>(state));
         auto left_state  = deep_tie(std::get<0>(std::get<1>(state)));
         auto right_state = deep_tie(std::get<1>(std::get<1>(state)));

         auto in_state_l = tuple_take<input_arity_t<LeftExpr>::value>(in_state);
         auto left_state_  = ( current_input = tuple_take<input_arity_t<LeftExpr>::value>(input)
                             , delayed_input = std::tie(in_state_l,left_state)
                             );

         auto in_state_r = tuple_drop<input_arity_t<LeftExpr>::value>(in_state);
         auto right_state_ = ( current_input = tuple_drop<input_arity_t<LeftExpr>::value>(input)
                             , delayed_input = std::tie(in_state_r,right_state)
                             );
         return std::make_tuple
                (  e( l, 0, left_state_ )
                ,  e( r, 0, right_state_ )
                );
      }
   };

}

using  transforms :: input_arity;
using  transforms :: input_delays;
using  transforms :: build_state;
using  transforms :: input_arity_t;
using  transforms :: output_arity;
using  transforms :: eval_it;

//  ------------------------------------------------------------------------------------------------
// supporting state tools
//  ------------------------------------------------------------------------------------------------

//  ------------------------------------------------------------------------------------------------
// lift_into_tuple  --  lifts a meta-function into a tuple,
//                      i.e. applies it on each type in tuple, returns tuple of new types

template< typename F , typename Tuple >
struct lift_into_tuple;

template< typename F , typename... Ts >
struct lift_into_tuple< F , std::tuple<Ts...> >
{
    using type = std::tuple< typename F::template apply_t<Ts>... >;
};

template< typename F , typename Tuple >
using lift_into_tuple_t = typename lift_into_tuple<F,Tuple>::type;

//  ------------------------------------------------------------------------------------------------
// to_array  --  meta-function that maps mpl::int_<N> to array<T,N>

template< typename T , typename Int >
struct to_array_impl
{
   using type = std::array< T , std::decay_t<Int>::value >;
};

template< typename T >
struct to_array_impl< T, mpl::int_<0> >
{
   using type = no_state;     // array<T,N> prevents empty base class optimization to kick in.
};

template< typename T >
struct to_array
{
   template< typename Int >
   using apply_t = typename to_array_impl<T,Int>::type;
};

template < typename T >
struct to_array_tuple
{
   struct apply : proto::callable_decltype
   {
       template< typename Tuple >
       auto operator()( Tuple ) { return lift_into_tuple_t< to_array<T>, Tuple > {}; }
   };
};

//  ------------------------------------------------------------------------------------------------
// compile  --  main function of framework
//              • takes an expression
//              • returns clojure
//  ------------------------------------------------------------------------------------------------

template
<  typename Expression
,  typename State
,  size_t   arity
>
struct stateful_lambda
{
private:

   Expression  expr_;
   State       state_;

   template < typename... Args >
   auto call_impl( mpl::int_<0> , Args const &... args ) -> decltype(auto)
   {
      std::tuple<> in_state;
      auto in_tuple = std::tie(in_state,state_);
      auto result = eval_it{}( expr_, 0, ( current_input = std::make_tuple(args...)
                                         , delayed_input = boost::ref(in_tuple) ) );
      return flatten_tuple( std::make_tuple( result ));
   }

   template < int arg_diff , typename... Args >
   auto call_impl( mpl::int_<arg_diff> , Args const &... args )
   {
      return [ args...
             , expr = *this ]           // TODO should not be a lambda, but a type that is
      ( auto const &... missing_args ) mutable  //      aware of the # of args (enable_if them)
      {
         return expr( args..., missing_args... );
      };
   }

public:

   stateful_lambda( Expression expr ) : expr_( expr )
   {
      //std::cout << "• size expr:  " << sizeof(expr_)  << std::endl;
      //std::cout << "• size state: " << sizeof(state_) << std::endl;
      //std::cout << "• state: " << type_name(state_) << std::endl;
   }

   template < typename... Args , typename = std::enable_if_t< sizeof...(Args) <= arity > >
   auto operator()( Args const &... args ) -> decltype(auto)
   {
      return call_impl( mpl::int_< arity - sizeof...(Args) >{} , args... );
   }
};

// TODO template input type determinse state type (float)
auto compile = []( auto expr_ )        // TODO need to define value_type for state
{
   auto expr = proto::deep_copy(expr_);
   using expr_t = decltype(expr);
   using arity_t = input_arity_t<expr_t>;

   auto builder = build_state< to_array_tuple<float>::apply >{};
   using state_t = decltype( builder(expr) );

   return stateful_lambda< expr_t, state_t, arity_t::value>{ expr };
};

const proto::terminal< placeholder< mpl::int_<1> >>::type   _1  = {{}};
const proto::terminal< placeholder< mpl::int_<2> >>::type   _2  = {{}};
const proto::terminal< placeholder< mpl::int_<3> >>::type   _3  = {{}};
const proto::terminal< placeholder< mpl::int_<4> >>::type   _4  = {{}};
const proto::terminal< placeholder< mpl::int_<5> >>::type   _5  = {{}};
const proto::terminal< placeholder< mpl::int_<6> >>::type   _6  = {{}};

auto print_ins_and_outs = []( auto const & expr )
{
    std::cout << "-------------------------" << std::endl;
    //proto::display_expr( expr );
    std::cout << "#ins:  " << input_arity{} (expr) << std::endl;
    std::cout << "#outs: " << output_arity{}(expr) << std::endl;
    std::cout << std::endl;
};

auto one_quad = []
{
   return compile( proto::deep_copy( ~(0.9f*_1[_1] - 0.8f*_1[_2] + _2 ) ));
};

auto one_quad_chain = []
{
   return compile( proto::deep_copy(
      ~(0.9f*_1[_1] - 0.8f*_1[_2] + _2)
   |= ~(0.9f*_1[_1] - 0.8f*_1[_2] + _2)
   //|= ~(0.9f*_1[_1] - 0.8f*_1[_2] + _2)
   //|= ~(0.9f*_1[_1] - 0.8f*_1[_2] + _2)
   ));
};

auto cross_wire = []
{
   return compile( proto::deep_copy(
      ~( (_2[_1],_3,_1[_1])  |=  (.9f*_1 + _2) | (.2f*_1) )
   ));
};

auto test_wire_around_box = []
{
   /*
   auto wire_around_prev_box = (_1 |= _2);
   print_ins_and_outs( wire_around_prev_box );
   auto wp = compile( wire_around_prev_box );
   std::cout << wp(2,1337) << std::endl;

   auto wire_around_succ_box = ( (_1,_1) |= _1);
   print_ins_and_outs( wire_around_succ_box );
   auto ws = compile( wire_around_succ_box );
   std::cout << ws(1337) << std::endl;
   */
};

auto sum_dirac = []( auto& proc )
{
   auto sum = std::get<0>(proc(1.f));
   for ( size_t n = 0; n < 100; ++n )
      sum += std::get<0>(proc(.0f));
   return sum;
};

auto make_simple_asm_inspectable_code = []( auto proc )
{
   asm volatile("nop");
   auto sum = std::get<0>(proc(1.f));
   for ( size_t n = 0; n < 100; ++n )  sum += std::get<0>(proc(.0f));
   asm volatile("nop");
   std::cout << sum << std::endl;
};

auto feed_dirac = []( auto proc )
{
   std::cout << proc(1.f) << std::endl;
   for ( size_t n = 0; n < 100; ++n )
      std::cout << proc(0.f) << std::endl;
};

auto x_wire = [ u1 = 0.f , u2 = 0.f ](float x) mutable
{
   auto u1_ = .9f * u2 + x;
   auto u2_ = .2f * u1;
   u1 = u1_;
   u2 = u2_;
   return std::make_tuple(u1,u2);
};

#include <benchmark/benchmark.h>

inline void escape(void* p) { asm volatile( "" :: "g"(p) : "memory" ); }

static void BM_cross_wire(benchmark::State& state)
{
   float x;
   auto f = cross_wire();
   while (state.KeepRunning())
   {
      escape(&x);
      asm volatile("nop");
      x = sum_dirac(f);
      asm volatile("nop");
   }
}
BENCHMARK(BM_cross_wire);

static void BM_x_wire(benchmark::State& state)
{
   float x;
   auto f = x_wire;
   while (state.KeepRunning())
   {
      escape(&x);
      asm volatile("nop");
      asm volatile("nop");
      x = sum_dirac(f);
      asm volatile("nop");
   }
}
BENCHMARK(BM_x_wire);

//BENCHMARK_MAIN();

int main()
{
   make_simple_asm_inspectable_code( cross_wire() );
   print_range( cross_wire() );

   input_delays  d;
   build_state<> b;

   //auto e = (_1 |= _1[_1]) |= (_1 |= _1[_1]);
   auto e = (_1,_1) |= (_1[_1]|_1) |= _1+_2*0;
   auto id = compile( e );
   std::cout << " ------ e ------" << std::endl;
   std::cout << id(1337) << std::endl;
   std::cout << id(42) << std::endl;
   std::cout << id(1) << std::endl;
   std::cout << id(1) << std::endl;
   std::cout << id(1) << std::endl;

   std::cout << d(e) << std::endl;
   std::cout << b(e) << std::endl;
   print_state( b( e ));

   //print_state( b( cross_wire() ));
   //compile( cross_wire() )(1.f,1.f);

//   std::cout << "--------------" << std::endl;
//
//   // TODO build state only for used wires in feedback!
//
//   std::cout << b( ~(_1[_1] + _2[_1] + _3[_3]) ) << std::endl;
//   std::cout << d( ~(_1[_1] + _2[_1] + _3[_3]) ) << std::endl;

   // TODO does not work yet
   auto fb = compile( ~~(_1[_1] + _2[_1] + _3) );
   //std::cout << d( ~~(_1[_1] + _2[_1] + _3) ) << std::endl;
   //std::cout << b( ~~(_1[_1] + _2[_1] + _3) ) << std::endl;
   std::cout << fb(2) << std::endl;
   std::cout << fb(1) << std::endl;
   std::cout << fb(0) << std::endl;

}


/*******************************************************************************
#      ____               __          __  _      _____ _       _               #
#     / __ \              \ \        / / | |    / ____| |     | |              #
#    | |  | |_ __   ___ _ __ \  /\  / /__| |__ | |  __| | ___ | |__   ___      #
#    | |  | | '_ \ / _ \ '_ \ \/  \/ / _ \ '_ \| | |_ | |/ _ \| '_ \ / _ \     #
#    | |__| | |_) |  __/ | | \  /\  /  __/ |_) | |__| | | (_) | |_) |  __/     #
#     \____/| .__/ \___|_| |_|\/  \/ \___|_.__/ \_____|_|\___/|_.__/ \___|     #
#           | |                                                                #
#           |_|                                                                #
#                                                                              #
#                                (c) 2011 by                                   #
#           University of Applied Sciences Northwestern Switzerland            #
#                     Institute of Geomatics Engineering                       #
#                           robert.wueest@fhnw.ch                              #
********************************************************************************
*     Licensed under MIT License. Read the file LICENSE for more information   *
*******************************************************************************/

//-------------------------------------------------------
// NOTE: this tool will be renamed to ogPostprocess soon
//-------------------------------------------------------

#include "ogprocess.h"
#include "errors.h"
#include <boost/asio.hpp>
#include "app/ProcessingSettings.h"
#include "geo/MercatorQuadtree.h"
#include "geo/CoordinateTransformation.h"
#include "geo/ElevationLayerSettings.h"
#include "string/FilenameUtils.h"
#include "string/StringUtils.h"
#include "geo/ImageLayerSettings.h"
#include "io/FileSystem.h"
#include "image/ImageLoader.h"
#include "image/ImageWriter.h"
#include "app/Logger.h"
#include "math/mathutils.h"
#include <iostream>
#include <boost/program_options.hpp>
#include <sstream>
#include <omp.h>
#include <app/QueueManager.h>
#include "hillshading.h"
#include <math/vec3.h>

namespace po = boost::program_options;

//-------------------------------------------------------------------
// Job-Struct
struct SJob
{
   int xx;
   int yy;
   int lod;
};

// -------------------------------------------------------------------
// -- Global variables
   bool bError = false;
   std::string sLayerPath;
   std::string sJobQueueFile;
   int iLayerMaxZoom = 0;
   int iLayerMinZoom = 0;
   std::string sAlgorithm;
   std::string sProcessHostName;
   bool bVerbose = false;
   bool bGenerateJobs = false;
   bool bOverrideQueue = false;
   bool bOverrideTiles = true;
   bool bLockEnabled = false;
   bool bNormalMaps = false;
   bool bBorders = false;
   bool bSlope = false;
   bool bColored = false;
   bool bTextured = false;
   bool bNoData = false;
   int iAmount = 256;
   int inputX = 768;
   int inputY = 768;
   int outputX = 256;
   int outputY = 256;
   double z_depth = 1.0;
   double azimut = 315;
   double altitude = 45;
   double sscale = 1;
   double slopeScale = 1;
   bool bJPEG = false;
   int iX = 0;
   int iY = 0;
   std::string sTempTileDir;
   std::string sTileDir;
   boost::shared_ptr<MercatorQuadtree> qQuadtree;
   int64 layerTileX0, layerTileY0, layerTileX1, layerTileY1;
   QueueManager _QueueManager = QueueManager();
   boost::shared_array<ImageObject> pTextures;
// -------------------------------------------------------------------

//  Job function (called every thread/compute node)
void ProcessJob(const SJob& job, int layerLod)
{
   //std::cout << sCurrentQuadcode << "\n";
   HSProcessChunk pData;
   pData.layerLod = layerLod;
   pData.dfXMax = -1e20;
   pData.dfYMax = -1e20;
   pData.dfXMin = 1e20;
   pData.dfYMin = 1e20;
   pData.data.AllocateImage(inputX, inputY, -9999.0f);
   std::string sParentQuad = qQuadtree->TileCoordToQuadkey(job.xx,job.yy,job.lod);
   sParentQuad = StringUtils::Left(sParentQuad,layerLod);
   int64 parentX,parentY;
   int parentLod;
   MercatorQuadtree::QuadKeyToTileCoord(sParentQuad,parentX, parentY,parentLod);
   for (int ty=-1;ty<=1;ty++)
   {
      for (int tx=-1;tx<=1;tx++)
      {
         //std::string sQuadcode = qQuadtree->TileCoordToQuadkey(job.xx+tx,job.yy+ty,job.lod);
         std::string sQuadcode = qQuadtree->TileCoordToQuadkey(parentX+tx, parentY+ty,parentLod);
         std::string sTilefile = ProcessingUtils::GetTilePath(sTempTileDir, ".raw" , parentLod, parentX+tx, parentY+ty);

         double sx0, sy1, sx1, sy0;
         qQuadtree->QuadKeyToMercatorCoord(sQuadcode, sx0, sy1, sx1, sy0);

         pData.dfXMax = math::Max<double>(pData.dfXMax, sx1);
         pData.dfYMax = math::Max<double>(pData.dfYMax, sy1);
         pData.dfXMin = math::Min<double>(pData.dfXMin, sx0);
         pData.dfYMin = math::Min<double>(pData.dfYMin, sy0);

         assert(sx0 < sx1);
         assert(sy0 < sy1);

         //std::cout << "   " << sTilefile << "\n";

         std::ifstream fin;
         fin.open(sTilefile.c_str(), std::ios::binary);
         int posX = (tx+1)*(inputX/3);
         int posY = (ty+1)*(inputY/3);
         int offX = 0;
         int offY = 0;
         if (fin.good())
         {
            while (!fin.eof())
            {
               float value;
               fin.read((char*)&(value), sizeof(float));
               if (!fin.eof())
               {
                  pData.data.SetValue(posX+offX, posY+offY, value);
               }
               offX++;
               if(offX % 256 == 0)
               {
                  offX = 0;
                  offY++;
               }

            }
         }
         fin.close();
      }
   }
   // Generate tile
   process_hillshading(sTileDir, pData, qQuadtree, job.xx, job.yy, job.lod, z_depth, azimut, altitude,sscale,slopeScale, bSlope, bNormalMaps, outputX, outputY, bOverrideTiles, bLockEnabled, bNoData, bJPEG, bColored, bTextured, pTextures);
}

//------------------------------------------------------------------------------------

void ConvertJobs(std::vector<QJob>& input, std::vector<SJob>& output)
{
   output.clear();
   for (size_t i=0;i<input.size();i++)
   {
      SJob job2;
      memcpy(&job2, input[i].data.get(), sizeof(SJob));
      output.push_back(job2);
   }
}

//------------------------------------------------------------------------------------

int main(int argc, char *argv[])
{
	   //---------------------------------------------------------------------------
   // init options:

   boost::shared_ptr<ProcessingSettings> qSettings =  ProcessingUtils::LoadAppSettings();

   if (!qSettings)
   {
      std::cout << "[" << sProcessHostName<< "] " << "Error in configuration! Check setup.xml\n";
      return ERROR_CONFIG;
   }

   sProcessHostName = boost::asio::ip::host_name();
   po::options_description desc("Program-Options");
   desc.add_options()
      ("layername", po::value<std::string>(), "name of layer to generate data")
      ("maxlod", po::value<int>(), "maximum lod which has to be generated previously using ogAddData")
      ("minlod", po::value<int>(), "minimum lod which has to be generated previously using ogAddData")
      ("generatejobs","[optional] create a jobqueue which can be used in every process")
      ("overridejobqueue","[optional] overrides existing queue file if exist (only when generatejobs is set!)")
      ("normalmaps", "[optional] generate normal maps")
      ("slope", "[optional] integrate slope to map")
      ("slopescale", po::value<double>(),"[optional] define slope scale default 1")
      ("numthreads", po::value<int>(), "[optional] force number of threads")
      ("amount", po::value<int>(), "[opional] define amount of jobs to be read for one process at the time")
      ("zdepth", po::value<double>(), "[opional] hillshading z factor")
      ("azimut", po::value<double>(), "[opional] hillshading azimut")
      ("altitude", po::value<double>(), "[opional] hillshading altitude")
      ("scale", po::value<double>(), "[opional] hillshading scale")
      ("nooverride", "[opional] overriding existing tiles disabled")
      ("enablelocking", "[opional] lock files to prevent concurrency on parallel processes")
      ("verbose", "[optional] verbose output")
      ("processborders", "[optional] process border tiles")
      ("nodata", "[optional] include nodata values")
	   ("colored", "[optional] color the heigths")
	   ("textured", "[optional] generic textured heights")
      ("jpg", "[optional] save files in compressed JPEG quality(78) instead of PNG")
      ;

   po::variables_map vm;
   try
   {
      po::store(po::parse_command_line(argc, argv, desc), vm);
      po::notify(vm);
   }
   catch (std::exception&)
   {
      bError = true;
   }

   if(vm.count("layername"))
   {
	  std::string sLayerName = vm["layername"].as<std::string>();
	  sLayerPath = FilenameUtils::DelimitPath(qSettings->GetPath()) + sLayerName;
      /*sLayerPath = vm["layerpath"].as<std::string>();
      if(!(sLayerPath.at(sLayerPath.length()-1) == '\\' || sLayerPath.at(sLayerPath.length()-1) == '/'))
         sLayerPath = sLayerPath + "/";*/
   }
   else
      bError = true;
   if(vm.count("maxlod"))
      iLayerMaxZoom = vm["maxlod"].as<int>();
   else
      bError = true;
   if(vm.count("minlod"))
      iLayerMinZoom = vm["minlod"].as<int>();
   else
      bError = true;
   int numThreads = 1;
   if(vm.count("numthreads"))
   {
      int n = vm["numthreads"].as<int>();
      if (n>0 && n<65)
      {
         std::cout << "[" << sProcessHostName<< "] " << "Forcing number of threads to " << n << "\n";
         omp_set_num_threads(n);
         numThreads = n;
      }
   }
   if(vm.count("amount"))
      iAmount = vm["amount"].as<int>();
   if(vm.count("zdepth"))
      z_depth = vm["zdepth"].as<double>();
   if(vm.count("azimut"))
      azimut = vm["azimut"].as<double>();
   if(vm.count("altitude"))
      altitude = vm["altitude"].as<double>();
   if(vm.count("scale"))
      sscale = vm["scale"].as<double>();
   if(vm.count("verbose"))
      bVerbose = true;
   if(vm.count("generatejobs"))
      bGenerateJobs = true;
    if(vm.count("normalmaps"))
      bNormalMaps = true;
    if(vm.count("slopescale"))
      slopeScale = vm["slopescale"].as<double>();
    if(vm.count("slope"))
      bSlope = true;
    if(vm.count("nodata"))
      bNoData = true;
   if(vm.count("overridejobqueue"))
      bOverrideQueue = true;
   if(vm.count("nooverride"))
      bOverrideTiles = false;
    if(vm.count("enablelocking"))
      bLockEnabled = true;
	if(vm.count("colored"))
      bColored = true;
   if(vm.count("processborders"))
      bBorders = true;
   if(vm.count("jpg"))
      bJPEG = true;
	if(vm.count("textured"))
   {
      bTextured = true;
         // Coloring textures
      pTextures = boost::shared_array<ImageObject>(new ImageObject[6]);
      ImageLoader::LoadFromDisk(Img::Format_PNG, "data/ground.png", Img::PixelFormat_RGBA, pTextures[0]);
      ImageLoader::LoadFromDisk(Img::Format_PNG, "data/grass.png", Img::PixelFormat_RGBA, pTextures[1]);
      ImageLoader::LoadFromDisk(Img::Format_PNG, "data/snow.png", Img::PixelFormat_RGBA, pTextures[2]);
      ImageLoader::LoadFromDisk(Img::Format_PNG, "data/rock.png", Img::PixelFormat_RGBA, pTextures[3]);
      ImageLoader::LoadFromDisk(Img::Format_PNG, "data/desert.png", Img::PixelFormat_RGBA, pTextures[4]);
      ImageLoader::LoadFromDisk(Img::Format_PNG, "data/water.png", Img::PixelFormat_RGBA, pTextures[5]);
   }

	   if(bError)
   {
      std::cout << "[" << sProcessHostName<< "] " << "Wrong parameters!\n";
      std::cout << desc << "\n";
      return ERROR_PARAMS;
   }
   //---------------------------------------------------------------------------
   // -- Beginn process
   sTempTileDir = sLayerPath + "/temp/tiles/";
   sTileDir = sLayerPath + "/tiles/";

   boost::shared_ptr<ImageLayerSettings> qImageLayerSettings = ImageLayerSettings::Load(sLayerPath);

   if (!qImageLayerSettings)
   {
      std::cout << "[" << sProcessHostName<< "] " << "Failed retrieving image layer settings! Make sure to create it using 'createlayer'.\n"<< std::flush;
      return ERROR_IMAGELAYERSETTINGS;
   }
   int layermaxlod = qImageLayerSettings->GetMaxLod();

   qImageLayerSettings->GetTileExtent(layerTileX0, layerTileY0, layerTileX1, layerTileY1);
   if (bVerbose)
   {
      std::cout << "\n" << "[" << sProcessHostName<< "] " << "Raw Image Layer:\n";
      std::cout << "   name = " << qImageLayerSettings->GetLayerName() << "\n";
      std::cout << "   maxlod = " << layermaxlod << "\n";
      std::cout << "   extent = " << layerTileX0 << ", " << layerTileY0 << ", " << layerTileX1 << ", " << layerTileY1 << "\n" << std::flush;
   }
   //---------------------------------------------------------------------------
   // -- performance measurement
   int tileCount = 0;
   clock_t t_0, t_1;
   t_0 = clock();

   //---------------------------------------------------------------------------
   // -- Generate job queue
   qQuadtree = boost::shared_ptr<MercatorQuadtree>(new MercatorQuadtree());
   sJobQueueFile = sTileDir + "jobqueue.jobs";
   if(bGenerateJobs)
   {
      if(iLayerMaxZoom > layermaxlod)
      {
         for(size_t ll = layermaxlod; ll <iLayerMaxZoom; ll++)
         {
            layerTileX0 = layerTileX0*2;
            layerTileX1 = layerTileX1*2;
            layerTileY0 = layerTileY0*2;
            layerTileY1 = layerTileY1*2;
         }
      }
	  else if(iLayerMaxZoom < layermaxlod)
      {
         for(size_t ll = layermaxlod; ll > iLayerMaxZoom; ll--)
         {
            layerTileX0 = math::Floor(layerTileX0/2.0);
            layerTileX1 = math::Floor(layerTileX1/2.0);
            layerTileY0 = math::Floor(layerTileY0/2.0);
            layerTileY1 = math::Floor(layerTileY1/2.0);
         }
      }
      else
      {
         iLayerMaxZoom = layermaxlod;

      }
      if(iLayerMinZoom == 0)
      {
         iLayerMinZoom = layermaxlod;
      }
      for(size_t lod = iLayerMaxZoom; lod >= iLayerMinZoom; lod--)
      {

         if(lod < iLayerMaxZoom)
         {
            // update tile extents
            layerTileX0 = math::Floor(layerTileX0/2.0);
            layerTileX1 = math::Floor(layerTileX1/2.0);
            layerTileY0 = math::Floor(layerTileY0/2.0);
            layerTileY1 = math::Floor(layerTileY1/2.0);
         }
         int64 width = layerTileX1-layerTileX0+1;
         int64 height = layerTileY1-layerTileY0+1;
         if ((width<3 || height<3) && !bBorders)
         {
            std::cout << "Extent is too small for hillshading processing skipping current and following LOD levels\n";
            return ERROR_ELVLAYERSETTINGS;
         }

         // Retrieve dataset extent in mercator coord:

         double xmin, ymin, xmax, ymax;
         std::string qc0 = qQuadtree->TileCoordToQuadkey(layerTileX0,layerTileY0,lod);
         std::string qc1 = qQuadtree->TileCoordToQuadkey(layerTileX1,layerTileY1,lod);

         double x00, y00, x10, y10;
         double x01, y01, x11, y11;
         qQuadtree->QuadKeyToMercatorCoord(qc0, x00,y00,x10,y10);
         qQuadtree->QuadKeyToMercatorCoord(qc1, x01,y01,x11,y11);

         xmin = x00;
         ymin = y11;
         xmax = x11;
         ymax = y00;
         if (bVerbose)
         {
            std::cout << "\n[" << sProcessHostName<< "] " << "Extent mercator:";
            std::cout << "   extent = " << xmin << ", " << ymin << ", " << xmax << ", " << ymax << "\n"<< std::flush;
         }

         if (!ProcessingUtils::init_gdal())
         {
            std::cout << "[" << sProcessHostName<< "] " << "Warning: gdal-data directory not found. Ouput may be wrong!\n"<< std::flush;
            return 1;
         }

         int idx = 0;
         std::cout << "[" << sProcessHostName<< "] " << " Generating jobs starting from (z, x, y) " << "(" << lod << ", " << layerTileX0+(bBorders ? 0 : 1) << ", " << layerTileY0+(bBorders ? 0 : 1) << ")\n"<< std::flush;
         for (int64 xx = layerTileX0+(bBorders ? 0 : 1); xx < layerTileX1+(bBorders ? 1 : 0); ++xx)
         {
            for (int64 yy = layerTileY0+(bBorders ? 0 : 1); yy < layerTileY1+(bBorders ? 1 : 0); ++yy)
            {
               QJob job;
               SJob sJob;
               sJob.lod = lod;
               sJob.xx = xx;
               sJob.yy = yy;
               job.data = boost::shared_array<char>(new char[sizeof(SJob)]);
               memcpy(job.data.get(), &sJob, sizeof(SJob));
               job.size = sizeof(SJob);
               _QueueManager.AddToJobQueue(sJobQueueFile, job, (bOverrideQueue && idx == 0)? false : true);
               idx++;
               iX = xx;
               iY = yy;
            }
         }
         _QueueManager.CommitJobQueue(sJobQueueFile);
         std::cout << "[" << sProcessHostName<< "] " << " Generated " << idx << " jobs ending with (z, x, y) " << "(" << lod << ", " << iX << ", " << iY << ")!\n"<< std::flush;
      }
   }
   //---------------------------------------------------------------------------
   // -- Process Jobs Queue
   else
   {
      if(!FileSystem::FileExists(sJobQueueFile))
      {
         std::cout << "[" << sProcessHostName<< "] " << "ERROR: Jobqueue file not found: " << sJobQueueFile << " use --generatejobs first...\n"<< std::flush;
         return ERROR_PARAMS;
      }
      std::vector<SJob> vecConverted;
      std::vector<QJob> jobs;
      std::cout << "[" << sProcessHostName<< "] >>>" << "start processing...\n"<< std::flush;
      do
      {
         jobs.clear();
         vecConverted.clear();
         jobs = _QueueManager.FetchJobList(sJobQueueFile, sizeof(SJob), iAmount,bVerbose);
         if(jobs.size() > 0)
         {
         ConvertJobs(jobs, vecConverted);
         SJob first, last;
         first = vecConverted[0];
         last = vecConverted[vecConverted.size()-1];
         clock_t subT0 = clock();
         clock_t subT1;
         std::cout << "--[" << sProcessHostName<< "] " << "  processing " << vecConverted.size() << " jobs\n       starting from (z, x, y) " << "(" << first.lod << ", " << first.xx << ", " << first.yy << ")\n"<< std::flush;
#ifndef _DEBUG
         std::cout << "..Processing parallel using " << numThreads << "\n";
               #pragma omp parallel shared(vecConverted, sTempTileDir, sTileDir, inputX, inputY, outputX, outputY)
               {
                  #pragma omp for
#endif
                  for(int index = 0; index < vecConverted.size(); index++)
                  {
                     ProcessJob(vecConverted[index],layermaxlod);
                     tileCount++;
                  }
#ifndef _DEBUG
               }
#endif
            subT1 = clock();
            double subTime=(double(subT1-subT0)/double(CLOCKS_PER_SEC));
            double subTps = vecConverted.size()/subTime;
            std::cout << "--[" << sProcessHostName<< "] " << "  processing average " << subTps << " tiles per second.\n";
            std::cout << "--[" << sProcessHostName<< "] " << "  processed " << vecConverted.size() << " jobs\n       terminating with (z, x, y) " << "(" << last.lod << ", " << last.xx << ", " << last.yy << ")\n"<< std::flush;
         }
      }while(jobs.size() >= iAmount);
   }
   t_1 = clock();
         double time=(double(t_1-t_0)/double(CLOCKS_PER_SEC));
         double tps = tileCount/time;
         std::cout << "[" << sProcessHostName<< "] <<<" << "finished processing "<< tileCount << " jobs at " << tps << " tiles pers second working for " << time << " seconds.\n"<< std::flush;
   return 0;
}


// Copyright Abel Sinkovics (abel@sinkovics.hu)  2012.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#define BOOST_MPL_LIMIT_STRING_SIZE 64
#define BOOST_METAPARSE_LIMIT_STRING_SIZE BOOST_MPL_LIMIT_STRING_SIZE

#include <boost/metaparse/grammar.hpp>

#include <boost/metaparse/string.hpp>
#include <boost/metaparse/build_parser.hpp>
#include <boost/metaparse/token.hpp>
#include <boost/metaparse/entire_input.hpp>
#include <boost/metaparse/int_.hpp>
#include <boost/metaparse/transform.hpp>

#include <boost/mpl/apply_wrap.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/back.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/fold.hpp>

using boost::mpl::apply_wrap1;
using boost::mpl::front;
using boost::mpl::back;
using boost::mpl::if_;
using boost::mpl::bool_;

using boost::metaparse::build_parser;
using boost::metaparse::token;
using boost::metaparse::entire_input;
using boost::metaparse::int_;
using boost::metaparse::grammar;
using boost::metaparse::transform;

#if BOOST_METAPARSE_STD < 2011
int main()
{
  std::cout << "Please use a compiler that supports constexpr" << std::endl;
}
#else

#ifdef _STR
#  error _STR already defined
#endif
#define _STR BOOST_METAPARSE_STRING

template <class T, char C>
struct is_c : bool_<T::type::value == C> {};

struct build_plus_impl
{
  template <class A, class B>
  class _plus
  {
  public:
    typedef _plus type;

    template <class T>
    T operator()(T t) const
    {
      return _left(t) + _right(t);
    }
  private:
    typename A::type _left;
    typename B::type _right;
  };

  template <class A, class B>
  class _minus
  {
  public:
    typedef _minus type;

    template <class T>
    T operator()(T t) const
    {
      return _left(t) - _right(t);
    }
  private:
    typename A::type _left;
    typename B::type _right;
  };

  template <class State, class C>
  struct apply :
    if_<
      typename is_c<front<C>, '+'>::type,
      _plus<State, typename back<C>::type>,
      _minus<State, typename back<C>::type>
    >
  {};
};

struct build_plus
{
  typedef build_plus type;

  template <class Seq>
  struct apply :
    boost::mpl::fold<
      typename back<Seq>::type,
      typename front<Seq>::type,
      build_plus_impl
    >
  {};
};

struct build_mult_impl
{
  template <class A, class B>
  class _mult
  {
  public:
    typedef _mult type;

    template <class T>
    T operator()(T t) const
    {
      return _left(t) * _right(t);
    }
  private:
    typename A::type _left;
    typename B::type _right;
  };

  template <class A, class B>
  class _div
  {
  public:
    typedef _div type;

    template <class T>
    T operator()(T t) const
    {
      return _left(t) / _right(t);
    }
  private:
    typename A::type _left;
    typename B::type _right;
  };

  template <class State, class C>
  struct apply :
    if_<
      typename is_c<front<C>, '*'>::type,
      _mult<State, typename back<C>::type>,
      _div<State, typename back<C>::type>
    >
  {};
};

struct build_mult
{
  typedef build_mult type;

  template <class Seq>
  struct apply :
    boost::mpl::fold<
      typename back<Seq>::type,
      typename front<Seq>::type,
      build_mult_impl
    >
  {};
};

struct build_value
{
  typedef build_value type;

  template <class V>
  struct apply
  {
    typedef apply type;

    template <class T>
    int operator()(T) const
    {
      return V::type::value;
    }
  };
};

struct build_arg
{
  typedef build_arg type;

  template <class>
  struct apply
  {
    typedef apply type;

    template <class T>
    T operator()(T t) const
    {
      return t;
    }
  };
};

struct keep_front
{
  typedef keep_front type;

  template <class Seq>
  struct apply : front<Seq> {};
};

typedef
  grammar<_STR("plus_exp")>
    ::import<_STR("int_token"), token<transform<int_, build_value>>>::type

    ::rule<_STR("ws ::= (' ' | '\n' | '\r' | '\t')*")>::type
    ::rule<_STR("plus_token ::= '+' ws"), keep_front>::type
    ::rule<_STR("minus_token ::= '-' ws"), keep_front>::type
    ::rule<_STR("mult_token ::= '*' ws"), keep_front>::type
    ::rule<_STR("div_token ::= '/' ws"), keep_front>::type
    ::rule<_STR("arg_token ::= '_' ws"), keep_front>::type

    ::rule<_STR("plus_exp ::= prod_exp ((plus_token | minus_token) prod_exp)*"), build_plus>::type
    ::rule<_STR("prod_exp ::= value_exp ((mult_token | div_token) value_exp)*"), build_mult>::type
    ::rule<_STR("value_exp ::= int_token | arg_exp")>::type
    ::rule<_STR("arg_exp ::= arg_token"), build_arg>::type
  g;

typedef build_parser<entire_input<g>> function_parser;

#ifdef LAMBDA
  #error LAMBDA already defined
#endif
#define LAMBDA(exp) apply_wrap1<function_parser, _STR(#exp)>::type

LAMBDA(13) f1;
LAMBDA(2 + 3) f2;
LAMBDA(2 * 3) f3;
LAMBDA(1+ 2*4-6/2) f4;
LAMBDA(2 * _) f5;

int main()
{
  using std::cout;
  using std::endl;

  cout
    << f1(11) << endl
    << f2(11) << endl
    << f3(11) << endl
    << f4(11) << endl
    << f5(11) << endl
    << f5(1.1) << endl
    ;
}

#endif


<<<<<<< HEAD
/*    Copyright (c) 2010-2018, Delft University of Technology
=======
/*    Copyright (c) 2010-2019, Delft University of Technology
>>>>>>> origin/master
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 *
 */

#include "Tudat/Astrodynamics/Aerodynamics/tabulatedAtmosphere.h"

#include <iostream>
#include <boost/make_shared.hpp>
#include "Tudat/InputOutput/matrixTextFileReader.h"

namespace tudat
{

namespace aerodynamics
{

using namespace interpolators;

//! Check uniqueness of input.
template< typename VariableType >
void checkVariableUniqueness( std::vector< VariableType > variables )
{
    // Sort variables
    std::sort( variables.begin( ), variables.end( ) );

    // Check uniqueness
    unsigned int numberOfUniqueElements = std::distance( variables.begin( ),
                                                         std::unique( variables.begin( ), variables.end( ) ) );

    // Give error in case of non-unique variables
    if ( numberOfUniqueElements != variables.size( ) )
    {
        throw std::runtime_error( "Error, in tabulated atmosphere. Duplicate entry in (in)dependent variables." );
    }
}

//! Function to create the interpolators based on the tabulated atmosphere files.
void TabulatedAtmosphere::createAtmosphereInterpolators( )
{
    // Check uniqueness
    checkVariableUniqueness< AtmosphereDependentVariables >( dependentVariables_ );
    checkVariableUniqueness< AtmosphereIndependentVariables >( independentVariables_ );

    // Retrieve number of dependent variables from user.
    unsigned int numberOfDependentVariables = dependentVariables_.size( );

    // Check that number of dependent variables does not exceed limit
    if ( numberOfDependentVariables > dependentVariablesDependency_.size( ) )
    {
        throw std::runtime_error( "Error, in tabulated atmosphere. Number of dependent variables exceeds current limit." );
    }

    // Check input consistency
    if ( independentVariables_.size( ) != 1 )
    {
        if ( atmosphereTableFile_.size( ) != numberOfDependentVariables )
        {
            throw std::runtime_error( "Error when creating tabulated atmosphere from file, "
                                      "number of specified dependent variables differs from file." );
        }

        // Retrieve number of independent variables from file.
        numberOfIndependentVariables_ = input_output::getNumberOfIndependentVariablesInCoefficientFile(
                    atmosphereTableFile_.at( 0 ) );

        // Check that number of independent variables does not exceed limit
        if ( ( numberOfIndependentVariables_ < 1 ) || ( numberOfIndependentVariables_ > 4 ) )
        {
            throw std::runtime_error( "Error when reading tabulated atmosphere from file, found " +
                                      std::to_string( numberOfIndependentVariables_ ) +
                                      " independent variables, up to 4 currently supported." );
        }

        // Check input consistency
        if ( independentVariables_.size( ) != numberOfIndependentVariables_ )
        {
            throw std::runtime_error( "Error when creating tabulated atmosphere from file, "
                                      "number of specified independent variables differs from file." );
        }
    }
    else
    {
        numberOfIndependentVariables_ = 1; // if only one independent variable is specified, only one file will
                                           // be provided, and it cannot be opened with the same function
    }

    // Get order of dependent variables
    for ( unsigned int i = 0; i < numberOfDependentVariables; i++ )
    {
        dependentVariablesDependency_.at( dependentVariables_.at( i ) ) = true;
        dependentVariableIndices_.at( dependentVariables_.at( i ) ) = i;
    }

    // Check that density, pressure and temperature are present
    if ( !( dependentVariablesDependency_.at( 0 ) || dependentVariablesDependency_.at( 1 ) ||
            dependentVariablesDependency_.at( 2 ) ) )
    {
        throw std::runtime_error( "Error, tabulated atmosphere must be initialized with at least "
                                  "density, pressure and temperature." );
    }

    // Assign values to default boundary handling methods
    if ( boundaryHandling_.empty( ) )
    {
        boundaryHandling_ = std::vector< BoundaryInterpolationType >( numberOfIndependentVariables_,
                                                                      use_boundary_value );
    }
    else
    {
        if ( boundaryHandling_.size( ) != numberOfIndependentVariables_ )
        {
            throw std::runtime_error( "Error, in tabulated atmosphere. Number of boundary handling methods provided does "
                                      "not match number of independent variables." );
        }
    }

    // Assign values to default extrapolation values
    if ( defaultExtrapolationValue_.empty( ) )
    {
        defaultExtrapolationValue_ = std::vector< std::vector< std::pair< double, double > > >(
                    numberOfDependentVariables, std::vector< std::pair< double, double > >(
                        numberOfIndependentVariables_, std::make_pair(
                            IdentityElement::getAdditionIdentity< double >( ),
                            IdentityElement::getAdditionIdentity< double >( ) ) ) );
    }
    else
    {
        if ( defaultExtrapolationValue_.size( ) != numberOfDependentVariables )
        {
            throw std::runtime_error( "Error, in tabulated atmosphere. Number of default extrapolation values provided "
                                      "does not match number of dependent variables." );
        }
    }

    // Create interpolators for variables requested by users, depending on the number of variables
    switch ( numberOfIndependentVariables_ )
    {
    case 1:
    {
        // Call approriate file reading function for 1 independent variables
        Eigen::MatrixXd tabulatedAtmosphereData = input_output::readMatrixFromFile( atmosphereTableFile_.at( 0 ), " \t", "%" );

        // Extract information on file size
        unsigned int numberOfColumnsInFile = tabulatedAtmosphereData.cols( );
        unsigned int numberOfRowsInFile = tabulatedAtmosphereData.rows( );

        // Check whether data is present in the file.
        if ( numberOfRowsInFile < 1 || numberOfColumnsInFile < 1 )
        {
            std::string errorMessage = "Error, in tabulated atmosphere. The atmosphere table file " +
                    atmosphereTableFile_.at( 0 ) + " is empty";
            throw std::runtime_error( errorMessage );
        }

        // Check whether number of dependent variables matches number of columns
        if ( numberOfDependentVariables != ( numberOfColumnsInFile - 1 ) )
        {
            throw std::runtime_error( "Error, in tabulated atmosphere. "
                                      "Number of specified dependent variables does not match file." );
        }

        // Assign sizes to vectors
        independentVariablesData_.resize( numberOfIndependentVariables_ );
        std::vector< std::vector< double > > dependentVariablesData;
        dependentVariablesData.resize( numberOfDependentVariables );

        // Extract variables from file
        for ( unsigned int i = 0; i < numberOfRowsInFile; i++ )
        {
            independentVariablesData_.at( 0 ).push_back( tabulatedAtmosphereData( i, 0 ) );
            for ( unsigned int j = 0; j < dependentVariablesDependency_.size( ); j++ )
            {
                if ( dependentVariablesDependency_.at( j ) )
                {
                    dependentVariablesData.at( dependentVariableIndices_.at( j ) ).push_back(
                                tabulatedAtmosphereData( i, dependentVariableIndices_.at( j ) + 1 ) );
                }
            }
        }

        // Create interpolators for density, pressure and temperature
        interpolatorForDensity_ = std::make_shared< CubicSplineInterpolatorDouble >(
                    independentVariablesData_.at( 0 ), dependentVariablesData.at( dependentVariableIndices_.at( 0 ) ),
                    huntingAlgorithm, boundaryHandling_.at( 0 ), defaultExtrapolationValue_.at( dependentVariableIndices_.at( 0 ) ).at( 0 ) );
        interpolatorForPressure_ = std::make_shared< CubicSplineInterpolatorDouble >(
                    independentVariablesData_.at( 0 ), dependentVariablesData.at( dependentVariableIndices_.at( 1 ) ),
                    huntingAlgorithm, boundaryHandling_.at( 0 ), defaultExtrapolationValue_.at( dependentVariableIndices_.at( 1 ) ).at( 0 ) );
        interpolatorForTemperature_ = std::make_shared< CubicSplineInterpolatorDouble >(
                    independentVariablesData_.at( 0 ), dependentVariablesData.at( dependentVariableIndices_.at( 2 ) ),
                    huntingAlgorithm, boundaryHandling_.at( 0 ), defaultExtrapolationValue_.at( dependentVariableIndices_.at( 2 ) ).at( 0 ) );

        // Create remaining interpolators, if requested by user
        if ( dependentVariablesDependency_.at( 3 ) )
        {
            interpolatorForGasConstant_ = std::make_shared< CubicSplineInterpolatorDouble >(
                        independentVariablesData_.at( 0 ), dependentVariablesData.at( dependentVariableIndices_.at( 3 ) ),
                        huntingAlgorithm, boundaryHandling_.at( 0 ), defaultExtrapolationValue_.at( dependentVariableIndices_.at( 3 ) ).at( 0 ) );
        }
        if ( dependentVariablesDependency_.at( 4 ) )
        {
            interpolatorForSpecificHeatRatio_ = std::make_shared< CubicSplineInterpolatorDouble >(
                        independentVariablesData_.at( 0 ), dependentVariablesData.at( dependentVariableIndices_.at( 4 ) ),
                        huntingAlgorithm, boundaryHandling_.at( 0 ), defaultExtrapolationValue_.at( dependentVariableIndices_.at( 4 ) ).at( 0 ) );
        }
        if ( dependentVariablesDependency_.at( 5 ) )
        {
            interpolatorForMolarMass_ = std::make_shared< CubicSplineInterpolatorDouble >(
                        independentVariablesData_.at( 0 ), dependentVariablesData.at( dependentVariableIndices_.at( 5 ) ),
                        huntingAlgorithm, boundaryHandling_.at( 0 ), defaultExtrapolationValue_.at( dependentVariableIndices_.at( 5 ) ).at( 0 ) );
        }
        break;
    }
    case 2:
    {
        createMultiDimensionalAtmosphereInterpolators< 2 >( );
        break;
    }
    case 3:
    {
        createMultiDimensionalAtmosphereInterpolators< 3 >( );
        break;
    }
    case 4:
    {
        createMultiDimensionalAtmosphereInterpolators< 4 >( );
        break;
    }
    default:
        throw std::runtime_error( "Error, multi-dimensional atmosphere with N>4 not yet implemented" );
    }
}

//! Create interpolators for specified dependent variables, taking into consideration the number
//! of independent variables (which is greater than one).
template< unsigned int NumberOfIndependentVariables >
void TabulatedAtmosphere::createMultiDimensionalAtmosphereInterpolators( )
{
    // Call approriate file reading function for N independent variables
    std::pair< std::vector< boost::multi_array< double, static_cast< size_t >( NumberOfIndependentVariables ) > >,
            std::vector< std::vector< double > > > tabulatedAtmosphereData;

    // Extract data
    tabulatedAtmosphereData = input_output::readTabulatedAtmosphere< NumberOfIndependentVariables >( atmosphereTableFile_ );

    // Assign independent variables
    independentVariablesData_ = tabulatedAtmosphereData.second;

    // Create interpolators for density, pressure and temperature
    interpolatorForDensity_ =
            std::make_shared< MultiLinearInterpolator< double, double, NumberOfIndependentVariables > >(
                independentVariablesData_, tabulatedAtmosphereData.first.at( dependentVariableIndices_.at( 0 ) ),
                huntingAlgorithm, boundaryHandling_, defaultExtrapolationValue_.at( dependentVariableIndices_.at( 0 ) ) );
    interpolatorForPressure_ =
            std::make_shared< MultiLinearInterpolator< double, double, NumberOfIndependentVariables > >(
                independentVariablesData_, tabulatedAtmosphereData.first.at( dependentVariableIndices_.at( 1 ) ),
                huntingAlgorithm, boundaryHandling_, defaultExtrapolationValue_.at( dependentVariableIndices_.at( 1 ) ) );
    interpolatorForTemperature_ =
            std::make_shared< MultiLinearInterpolator< double, double, NumberOfIndependentVariables > >(
                independentVariablesData_, tabulatedAtmosphereData.first.at( dependentVariableIndices_.at( 2 ) ),
                huntingAlgorithm, boundaryHandling_, defaultExtrapolationValue_.at( dependentVariableIndices_.at( 2 ) ) );

    // Create remaining interpolators, if requested by user
    if ( dependentVariablesDependency_.at( 3 ) )
    {
        interpolatorForGasConstant_ =
                std::make_shared< MultiLinearInterpolator< double, double, NumberOfIndependentVariables > >(
                    independentVariablesData_, tabulatedAtmosphereData.first.at( dependentVariableIndices_.at( 3 ) ),
                    huntingAlgorithm, boundaryHandling_, defaultExtrapolationValue_.at( dependentVariableIndices_.at( 3 ) ) );
    }
    if ( dependentVariablesDependency_.at( 4 ) )
    {
        interpolatorForSpecificHeatRatio_ =
                std::make_shared< MultiLinearInterpolator< double, double, NumberOfIndependentVariables > >(
                    independentVariablesData_, tabulatedAtmosphereData.first.at( dependentVariableIndices_.at( 4 ) ),
                    huntingAlgorithm, boundaryHandling_, defaultExtrapolationValue_.at( dependentVariableIndices_.at( 4 ) ) );
    }
    if ( dependentVariablesDependency_.at( 5 ) )
    {
        interpolatorForMolarMass_ =
                std::make_shared< MultiLinearInterpolator< double, double, NumberOfIndependentVariables > >(
                    independentVariablesData_, tabulatedAtmosphereData.first.at( dependentVariableIndices_.at( 5 ) ),
                    huntingAlgorithm, boundaryHandling_, defaultExtrapolationValue_.at( dependentVariableIndices_.at( 5 ) ) );
    }
}

} // namespace aerodynamics

} // namespace tudat