//=======================================================================
// Copyright Baptiste Wicht 2011-2016.
// Distributed under the MIT License.
// (See accompanying file LICENSE or copy at
//  http://opensource.org/licenses/MIT)
//=======================================================================

#include <boost/optional.hpp>

#include "cpp_utils/assert.hpp"

#include "logging.hpp"
#include "Utils.hpp"
#include "PerfsTimer.hpp"
#include "Options.hpp"
#include "Platform.hpp"
#include "Type.hpp"
#include "FunctionContext.hpp"
#include "GlobalContext.hpp"
#include "Function.hpp"
#include "Variable.hpp"

#include "mtac/GlobalOptimizations.hpp"
#include "mtac/Quadruple.hpp"

#include "ltac/PeepholeOptimizer.hpp"
#include "ltac/Register.hpp"
#include "ltac/FloatRegister.hpp"
#include "ltac/Printer.hpp"
#include "ltac/Utils.hpp"
#include "ltac/LiveRegistersProblem.hpp"
#include "ltac/Instruction.hpp"

using namespace eddic;

namespace {

inline bool optimize_statement(ltac::Instruction& instruction){
    //SUB or ADD 0 has no effect
    if(instruction.op == ltac::Operator::ADD || instruction.op == ltac::Operator::SUB){
        if(ltac::is_reg(*instruction.arg1) && boost::get<int>(&*instruction.arg2)){
            auto value = boost::get<int>(*instruction.arg2);

            if(value == 0){
                return ltac::transform_to_nop(instruction);
            }
        }
    }

    if(instruction.op == ltac::Operator::MOV){
        //MOV reg, 0 can be transformed into XOR reg, reg
        if(ltac::is_reg(*instruction.arg1) && mtac::equals<int>(*instruction.arg2, 0)){
            instruction.op = ltac::Operator::XOR;
            instruction.arg2 = instruction.arg1;

            return true;
        }

        if(ltac::is_reg(*instruction.arg1) && ltac::is_reg(*instruction.arg2)){
            auto& reg1 = boost::get<ltac::Register>(*instruction.arg1);
            auto& reg2 = boost::get<ltac::Register>(*instruction.arg2);

            //MOV reg, reg is useless
            if(reg1 == reg2){
                return ltac::transform_to_nop(instruction);
            }
        }
    }

    if(instruction.op == ltac::Operator::ADD){
        //ADD reg, 1 can be transformed into INC reg
        if(ltac::is_reg(*instruction.arg1) && mtac::equals<int>(*instruction.arg2, 1)){
            instruction.op = ltac::Operator::INC;
            instruction.arg2.reset();

            return true;
        }

        //ADD reg, -1 can be transformed into DEC reg
        if(ltac::is_reg(*instruction.arg1) && mtac::equals<int>(*instruction.arg2, -1)){
            instruction.op = ltac::Operator::DEC;
            instruction.arg2.reset();

            return true;
        }
    }

    if(instruction.op == ltac::Operator::SUB){
        //SUB reg, 1 can be transformed into DEC reg
        if(ltac::is_reg(*instruction.arg1) && mtac::equals<int>(*instruction.arg2, 1)){
            instruction.op = ltac::Operator::DEC;
            instruction.arg2.reset();

            return true;
        }

        //SUB reg, -1 can be transformed into INC reg
        if(ltac::is_reg(*instruction.arg1) && mtac::equals<int>(*instruction.arg2, -1)){
            instruction.op = ltac::Operator::INC;
            instruction.arg2.reset();

            return true;
        }
    }

    if(instruction.op == ltac::Operator::MUL2 || instruction.op == ltac::Operator::MUL3){
        //Optimize multiplications with SHIFTs or LEAs
        if(ltac::is_reg(*instruction.arg1) && mtac::is<int>(*instruction.arg2)){
            int constant = boost::get<int>(*instruction.arg2);

            auto reg = boost::get<ltac::Register>(*instruction.arg1);

            if(isPowerOfTwo(constant)){
                instruction.op = ltac::Operator::SHIFT_LEFT;
                instruction.arg2 = powerOfTwo(constant);

                return true;
            }

            if(constant == 3){
                instruction.op = ltac::Operator::LEA;
                instruction.arg2 = ltac::Address(reg, reg, 2, 0);

                return true;
            }

            if(constant == 5){
                instruction.op = ltac::Operator::LEA;
                instruction.arg2 = ltac::Address(reg, reg, 4, 0);

                return true;
            }

            if(constant == 9){
                instruction.op = ltac::Operator::LEA;
                instruction.arg2 = ltac::Address(reg, reg, 8, 0);

                return true;
            }
        }
    }

    if(instruction.op == ltac::Operator::CMP_INT){
        //Optimize comparisons with 0 with or reg, reg
        if(ltac::is_reg(*instruction.arg1) && mtac::equals<int>(*instruction.arg2, 0)){
            instruction.op = ltac::Operator::OR;
            instruction.arg2 = instruction.arg1;

            return true;
        }
    }

    if(instruction.op == ltac::Operator::LEA){
        auto address = boost::get<ltac::Address>(*instruction.arg2);

        if(address.base_register && !address.scaled_register){
            if(!address.displacement){
                instruction.op = ltac::Operator::MOV;
                instruction.arg2 = address.base_register;

                return true;
            } else if(*address.displacement == 0){
                instruction.op = ltac::Operator::MOV;
                instruction.arg2 = address.base_register;

                return true;
            }
        }
    }

    return false;
}

inline bool multiple_statement_optimizations(ltac::Instruction& i1, ltac::Instruction& i2){
    //Statements after RET are dead
    if(i1.op == ltac::Operator::RET){
        return ltac::transform_to_nop(i2);
    }

    //Two following LEAVE are not useful
    if(i1.op == ltac::Operator::LEAVE && i2.op == ltac::Operator::LEAVE){
        return ltac::transform_to_nop(i2);
    }

    //Combine two ADD into one
    if(i1.op == ltac::Operator::ADD && i2.op == ltac::Operator::ADD){
        if(ltac::is_reg(*i1.arg1) && ltac::is_reg(*i2.arg1) && boost::get<int>(&*i1.arg2) && boost::get<int>(&*i2.arg2)){
            auto reg1 = boost::get<ltac::Register>(*i1.arg1);
            auto reg2 = boost::get<ltac::Register>(*i2.arg1);

            if(reg1 == reg2){
                i1.arg2 = boost::get<int>(*i1.arg2) + boost::get<int>(*i2.arg2);

                return ltac::transform_to_nop(i2);
            }
        }
    }

    //Combine two SUB into one
    if(i1.op == ltac::Operator::SUB && i2.op == ltac::Operator::SUB){
        if(ltac::is_reg(*i1.arg1) && ltac::is_reg(*i2.arg1) && boost::get<int>(&*i1.arg2) && boost::get<int>(&*i2.arg2)){
            auto reg1 = boost::get<ltac::Register>(*i1.arg1);
            auto reg2 = boost::get<ltac::Register>(*i2.arg1);

            if(reg1 == reg2){
                i1.arg2 = boost::get<int>(*i1.arg2) + boost::get<int>(*i2.arg2);

                return ltac::transform_to_nop(i2);
            }
        }
    }

    if(i1.op == ltac::Operator::MOV && i2.op == ltac::Operator::MOV){
        if(ltac::is_reg(*i1.arg1) && ltac::is_reg(*i1.arg2) && ltac::is_reg(*i2.arg1) && ltac::is_reg(*i2.arg2)){
            auto reg11 = boost::get<ltac::Register>(*i1.arg1);
            auto reg12 = boost::get<ltac::Register>(*i1.arg2);
            auto reg21 = boost::get<ltac::Register>(*i2.arg1);
            auto reg22 = boost::get<ltac::Register>(*i2.arg2);

            //cross MOV (ir4 = ir5, ir5 = ir4), keep only the first
            if (reg11 == reg22 && reg12 == reg21){
                return ltac::transform_to_nop(i2);
            }
        } else if(ltac::is_reg(*i1.arg1) && ltac::is_reg(*i2.arg2)){
            auto reg11 = boost::get<ltac::Register>(*i1.arg1);
            auto reg22 = boost::get<ltac::Register>(*i2.arg2);

            if(reg11 == reg22 && boost::get<ltac::Address>(&*i1.arg2) && boost::get<ltac::Address>(&*i2.arg1)){
                if(boost::get<ltac::Address>(*i1.arg2) == boost::get<ltac::Address>(*i2.arg1)){
                    return ltac::transform_to_nop(i2);
                }
            }
        } else if(ltac::is_reg(*i1.arg2) && ltac::is_reg(*i2.arg1)){
            auto reg12 = boost::get<ltac::Register>(*i1.arg2);
            auto reg21 = boost::get<ltac::Register>(*i2.arg1);

            if(reg12 == reg21 && boost::get<ltac::Address>(&*i1.arg1) && boost::get<ltac::Address>(&*i2.arg2)){
                if(boost::get<ltac::Address>(*i1.arg1) == boost::get<ltac::Address>(*i2.arg2)){
                    return ltac::transform_to_nop(i2);
                }
            }
        }
    }

    if(i1.op == ltac::Operator::MOV && i2.op == ltac::Operator::ADD){
        if(ltac::is_reg(*i1.arg1) && ltac::is_reg(*i2.arg1)){
            if(boost::get<ltac::Register>(*i1.arg1) == boost::get<ltac::Register>(*i2.arg1)){
                if(boost::get<ltac::Register>(&*i1.arg2) && boost::get<int>(&*i2.arg2)){
                    i2.op = ltac::Operator::LEA;
                    i2.arg2 = ltac::Address(boost::get<ltac::Register>(*i1.arg2), boost::get<int>(*i2.arg2));

                    return ltac::transform_to_nop(i1);
                } else if(boost::get<std::string>(&*i1.arg2) && boost::get<int>(&*i2.arg2)){
                    i2.op = ltac::Operator::LEA;
                    i2.arg2 = ltac::Address(boost::get<std::string>(*i1.arg2), boost::get<int>(*i2.arg2));

                    return ltac::transform_to_nop(i1);
                }
            }
        }
    }

    if(i1.op == ltac::Operator::POP && i2.op == ltac::Operator::PUSH){
        if(ltac::is_reg(*i1.arg1) && ltac::is_reg(*i2.arg1)){
            auto reg1 = boost::get<ltac::Register>(*i1.arg1);
            auto reg2 = boost::get<ltac::Register>(*i2.arg1);

            if(reg1 == reg2){
                i1.op = ltac::Operator::MOV;
                i1.arg2 = ltac::Address(ltac::SP, 0);

                return ltac::transform_to_nop(i2);
            }
        }
    }

    if(i1.op == ltac::Operator::PUSH && i2.op == ltac::Operator::POP){
        if(ltac::is_reg(*i1.arg1) && ltac::is_reg(*i2.arg1)){
            auto reg1 = boost::get<ltac::Register>(*i1.arg1);
            auto reg2 = boost::get<ltac::Register>(*i2.arg1);

            if(reg1 == reg2){
                ltac::transform_to_nop(i1);

                return ltac::transform_to_nop(i2);
            }
        }
    }

    return false;
}

inline bool multiple_statement_optimizations_second(ltac::Instruction& i1, ltac::Instruction& i2, Platform platform){
    if(i1.op == ltac::Operator::MOV && i2.op == ltac::Operator::MOV){
        if(ltac::is_reg(*i1.arg1) && ltac::is_reg(*i2.arg1) && ltac::is_reg(*i2.arg2)){
            auto reg11 = boost::get<ltac::Register>(*i1.arg1);
            auto reg21 = boost::get<ltac::Register>(*i2.arg1);
            auto reg22 = boost::get<ltac::Register>(*i2.arg2);

            bool possible = true;

            if(i1.size != tac::Size::DEFAULT){
                possible = false;
            } else if(auto* ptr = boost::get<ltac::Address>(&*i1.arg2)){
                if(ptr->base_register && boost::get<ltac::Register>(*ptr->base_register) == reg11){
                    possible = false;
                }

                if(ptr->scaled_register && boost::get<ltac::Register>(*ptr->scaled_register) == reg11){
                    possible = false;
                }
            }

            if(reg22 == reg11 && possible){
                auto descriptor = getPlatformDescriptor(platform);

                for(unsigned int i = 0; i < descriptor->numberOfIntParamRegisters(); ++i){
                    auto reg = ltac::Register(descriptor->int_param_register(i + 1));

                    if(reg21 == reg){
                        i2.arg2 = i1.arg2;

                        return true;
                    }
                }

                if(reg21 == ltac::Register(descriptor->int_return_register1())){
                    i2.arg2 = i1.arg2;

                    return true;
                }

                if(reg21 == ltac::Register(descriptor->int_return_register2())){
                    i2.arg2 = i1.arg2;

                    return true;
                }
            }
        }
    }

    if(i1.op == ltac::Operator::MOV && i2.op == ltac::Operator::PUSH){
        if(ltac::is_reg(*i1.arg1) && ltac::is_reg(*i2.arg1) && !ltac::is_float_reg(*i1.arg2)){
            auto reg11 = boost::get<ltac::Register>(*i1.arg1);
            auto reg21 = boost::get<ltac::Register>(*i2.arg1);

            if(reg11 == reg21){
                bool valid = true;

                if(auto* ptr = boost::get<ltac::Address>(&*i1.arg2)){
                    if(ptr->base_register && boost::get<ltac::Register>(*ptr->base_register) == reg11){
                        valid = false;
                    } else if(ptr->scaled_register && boost::get<ltac::Register>(*ptr->scaled_register) == reg11){
                        valid = false;
                    }
                }

                if(valid){
                    i2.arg1 = i1.arg2;

                    return true;
                }
            }
        }
    }

    return false;
}

inline bool is_nop(ltac::Instruction& instruction){
    return instruction.op == ltac::Operator::NOP;
}

bool basic_optimizations(mtac::Function& function, Platform platform){
    bool optimized = false;

    for(auto& bb : function){
        auto& statements = bb->l_statements;

        if(statements.empty()){
            continue;
        }

        if(statements.size() == 1){
            optimized |= optimize_statement(statements.front());

            continue;
        }

        auto it = statements.begin();
        auto end = statements.end() - 1;

        while(it != end){
            auto& s1 = *it;
            auto& s2 = *(it + 1);

            //Optimizations that looks at only one statement
            optimized |= optimize_statement(s1);
            optimized |= optimize_statement(s2);

            //Optimizations that looks at several statements at once
            optimized |= multiple_statement_optimizations(s1, s2);

            if(cpp_unlikely(is_nop(s1))){
                it = statements.erase(it);
                end = statements.end() - 1;

                optimized = true;

                continue;
            }

            ++it;
        }

        it = statements.begin();
        end = statements.end() - 1;

        while(it != end){
            auto& s1 = *it;
            auto& s2 = *(it + 1);

            optimized |= multiple_statement_optimizations_second(s1, s2, platform);

            ++it;
        }
    }

    return optimized;
}

bool constant_propagation(mtac::Function& function){
    bool optimized = false;

    for(auto& bb : function){
        std::unordered_map<ltac::Register, int> constants;

        for(auto& instruction : bb->l_statements){
            //Erase constant
            if(instruction.arg1 && ltac::is_reg(*instruction.arg1)){
                auto reg1 = boost::get<ltac::Register>(*instruction.arg1);

                constants.erase(reg1);
            }

            //Collect constants
            if(instruction.op == ltac::Operator::XOR){
                if(ltac::is_reg(*instruction.arg1) && ltac::is_reg(*instruction.arg2)){
                    auto reg1 = boost::get<ltac::Register>(*instruction.arg1);
                    auto reg2 = boost::get<ltac::Register>(*instruction.arg2);

                    if(reg1 == reg2){
                        constants[reg1] = 0;
                    }
                }
            } else if(instruction.op == ltac::Operator::MOV){
                if(ltac::is_reg(*instruction.arg1)){
                    if (auto* valuePtr = boost::get<int>(&*instruction.arg2)){
                        auto reg1 = boost::get<ltac::Register>(*instruction.arg1);
                        constants[reg1] = *valuePtr;
                    }
                }
            }

            //Optimize MOV
            if(instruction.op == ltac::Operator::MOV){
                if(ltac::is_reg(*instruction.arg2)){
                    auto reg2 = boost::get<ltac::Register>(*instruction.arg2);

                    if(constants.find(reg2) != constants.end()){
                        instruction.arg2 = constants[reg2];
                        optimized = true;
                    }
                }
            }
        }
    }

    return optimized;
}

void remove_reg(std::unordered_map<ltac::Register, ltac::Register>& copies, ltac::Register reg){
    auto it = copies.begin();
    auto end = copies.end();

    while(it != end){
        if(it->first == reg || it->second == reg){
            it = copies.erase(it);
            end = copies.end();
            continue;
        }

        ++it;
    }
}

bool copy_propagation(mtac::Function& function, Platform platform){
    auto descriptor = getPlatformDescriptor(platform);

    bool optimized = false;

    for(auto& bb : function){
        std::unordered_map<ltac::Register, ltac::Register> copies;

        for(auto& instruction : bb->l_statements){
            //Erase constant
            if(instruction.arg1 && ltac::is_reg(*instruction.arg1)){
                auto reg = boost::get<ltac::Register>(*instruction.arg1);

                remove_reg(copies, reg);
            }

            if(instruction.op == ltac::Operator::DIV){
                remove_reg(copies, ltac::Register(descriptor->a_register()));
                remove_reg(copies, ltac::Register(descriptor->d_register()));
            }

            //Collect copies
            if(instruction.op == ltac::Operator::MOV){
                if(ltac::is_reg(*instruction.arg1)){
                    if (auto* reg_ptr = boost::get<ltac::Register>(&*instruction.arg2)){
                        auto reg1 = boost::get<ltac::Register>(*instruction.arg1);
                        copies[reg1] = *reg_ptr;
                    }
                }
            }

            //Optimize MOV
            if(instruction.op == ltac::Operator::MOV && instruction.size == tac::Size::DEFAULT){
                if(ltac::is_reg(*instruction.arg2)){
                    auto reg2 = boost::get<ltac::Register>(*instruction.arg2);

                    if(copies.find(reg2) != copies.end()){
                        instruction.arg2 = copies[reg2];
                        optimized = true;
                    }
                }
            }
        }
    }

    return optimized;
}

typedef std::unordered_set<ltac::Register> RegisterUsage;

void add_param_registers(RegisterUsage& usage, Platform platform){
    auto descriptor = getPlatformDescriptor(platform);

    for(unsigned int i = 1; i <= descriptor->numberOfIntParamRegisters(); ++i){
        usage.insert(ltac::Register(descriptor->int_param_register(i)));
    }

    usage.insert(ltac::Register(descriptor->a_register()));
    usage.insert(ltac::Register(descriptor->d_register()));

    //Never optimize those registers
    usage.insert(ltac::SP);
    usage.insert(ltac::BP);
}

void add_escaped_registers(RegisterUsage& usage, mtac::Function& function, Platform platform){
    auto descriptor = getPlatformDescriptor(platform);

    if(function.definition().return_type() == STRING){
        usage.insert(ltac::Register(descriptor->int_return_register1()));
        usage.insert(ltac::Register(descriptor->int_return_register2()));
    } else if(function.definition().return_type() != VOID){
        usage.insert(ltac::Register(descriptor->int_return_register1()));
    }

    add_param_registers(usage, platform);
}

void collect_usage(RegisterUsage& usage, boost::optional<ltac::Argument>& arg){
    if(arg){
        if(ltac::is_reg(*arg)){
            auto reg1 = boost::get<ltac::Register>(*arg);
            usage.insert(reg1);
        }

        if(boost::get<ltac::Address>(&*arg)){
            auto address = boost::get<ltac::Address>(*arg);

            if(address.scaled_register){
                usage.insert(boost::get<ltac::Register>(*address.scaled_register));
            }

            if(address.base_register){
                usage.insert(boost::get<ltac::Register>(*address.base_register));
            }
        }
    }
}

RegisterUsage collect_register_usage(mtac::Function& function, Platform platform){
    RegisterUsage usage;
    add_escaped_registers(usage, function, platform);

    for(auto& bb : function){
        for(auto& instruction : bb->l_statements){
            collect_usage(usage, instruction.arg1);
            collect_usage(usage, instruction.arg2);
            collect_usage(usage, instruction.arg3);
        }
    }

    return usage;
}

ltac::Register get_free_reg(RegisterUsage& usage, Platform platform){
    auto descriptor = getPlatformDescriptor(platform);

    for(auto& reg : descriptor->symbolic_registers()){
        if(usage.find(ltac::Register(reg)) == usage.end()){
            return ltac::Register(reg);
        }
    }

    return ltac::SP;
}

template<typename T>
inline bool one_of(const T& value, const std::vector<T>& container){
    return std::find(container.begin(), container.end(), value) != container.end();
}

bool dead_code_elimination(mtac::Function& function){
    bool optimized = false;

    ltac::LiveRegistersProblem problem;
    auto results = mtac::data_flow(function, problem);

    for(auto& block : function){
        auto it = iterate(block->l_statements);

        while(it.has_next()){
            auto& instruction = *it;

            if(ltac::erase_result(instruction.op)){
                //OR is used for comparisons
                if(instruction.op == ltac::Operator::OR){
                    ++it;
                    continue;
                }

                if(auto* reg_ptr = boost::get<ltac::Register>(&*instruction.arg1)){
                    //SP is always live
                    if(*reg_ptr == ltac::SP){
                        ++it;
                        continue;
                    }

                    //Some statements (in ENTRY and EXIT) are not annotated, it is enough to ignore them
                    if(results->OUT_S.count(instruction.uid())){
                        auto& liveness = results->OUT_S[instruction.uid()].values();

                        if(liveness.find(*reg_ptr) == liveness.end()){
                            it.erase();
                            optimized=true;
                            continue;
                        }
                    }
                }
            }

            ++it;
        }
    }

    return optimized;
}

ltac::Operator get_cmov_op(ltac::Operator op){
    switch(op){
        case ltac::Operator::NE:
            return ltac::Operator::CMOVNE;
        case ltac::Operator::E:
            return ltac::Operator::CMOVE;
        case ltac::Operator::GE:
            return ltac::Operator::CMOVGE;
        case ltac::Operator::G:
            return ltac::Operator::CMOVG;
        case ltac::Operator::LE:
            return ltac::Operator::CMOVLE;
        case ltac::Operator::L:
            return ltac::Operator::CMOVL;
        case ltac::Operator::B:
            return ltac::Operator::CMOVB;
        case ltac::Operator::BE:
            return ltac::Operator::CMOVBE;
        case ltac::Operator::A:
            return ltac::Operator::CMOVA;
        case ltac::Operator::AE:
            return ltac::Operator::CMOVAE;
        default:
            cpp_unreachable("No cmov equivalent");
    }
}

template<typename BIt, typename It>
bool move_forward(BIt& bit, BIt& bend, It& it, It& end){
    if(it != end){
        ++it;

        if(it == end){
            return move_forward(bit, bend, it, end);
        }

        return true;
    }

    if(bit != bend){
        ++bit;

        if(bit == bend){
            return false;
        }

        auto& bb = *bit;

        it = bb->l_statements.begin();
        end = bb->l_statements.end();

        if(bb->l_statements.empty()){
            return move_forward(bit, bend, it, end);
        }

        return true;
    }

    return false;
}

bool conditional_move(mtac::Function& function, Platform platform){
    bool optimized = false;

    RegisterUsage usage = collect_register_usage(function, platform);

    auto free_reg = get_free_reg(usage, platform);
    if(free_reg == ltac::SP){
        return optimized;
    }

    auto bit = function.begin();
    auto bend = function.end();

    auto& bb = *bit;

    auto it = bb->l_statements.begin();
    auto end = bb->l_statements.end();

    while(true){
        if(it == end){
            move_forward(bit, bend, it, end);
        }

        auto& instruction = *it;

        if(instruction.op != ltac::Operator::CMP_INT){
            if(!move_forward(bit, bend, it, end)){
                return optimized;
            }

            continue;
        }

        auto temp_bit = bit;
        auto temp_it = it;
        auto temp_end = end;

        if(!move_forward(temp_bit, bend, temp_it, temp_end)){
            return optimized;
        }

        auto& jump_1 = *temp_it;
        if(jump_1.is_jump()){
            if(!move_forward(temp_bit, bend, temp_it, temp_end)){
                return optimized;
            }

            auto& mov_1 = *temp_it;

            if(mov_1.op != ltac::Operator::MOV){
                if(!move_forward(bit, bend, it, end)){
                    return optimized;
                }

                continue;
            }

            if(!move_forward(temp_bit, bend, temp_it, temp_end)){
                return optimized;
            }

            auto& jump_2 = *temp_it;

            if(jump_2.is_jump()){
                if(!move_forward(temp_bit, bend, temp_it, temp_end)){
                    return optimized;
                }

                if(temp_it->op == ltac::Operator::LABEL){
                    if(!move_forward(temp_bit, bend, temp_it, temp_end)){
                        return optimized;
                    }

                    auto& mov_2 = *temp_it;

                    if(mov_2.op != ltac::Operator::MOV){
                        if(!move_forward(bit, bend, it, end)){
                            return optimized;
                        }

                        continue;
                    }

                    if(!move_forward(temp_bit, bend, temp_it, temp_end)){
                        return optimized;
                    }

                    if(temp_it->op == ltac::Operator::LABEL){
                        if(ltac::is_reg(*mov_1.arg1) && ltac::is_reg(*mov_2.arg1)){
                            auto reg1 = boost::get<ltac::Register>(*mov_1.arg1);
                            auto reg2 = boost::get<ltac::Register>(*mov_2.arg1);

                            if(reg1 != reg2){
                                if(!move_forward(bit, bend, it, end)){
                                    return optimized;
                                }

                                continue;
                            }

                            auto cmov_op = get_cmov_op(jump_1.op);

                            move_forward(bit, bend, it, end);
                            *it = std::move(mov_1);
                            move_forward(bit, bend, it, end);
                            *it = ltac::Instruction(ltac::Operator::MOV, free_reg, *mov_2.arg2);
                            move_forward(bit, bend, it, end);
                            *it = ltac::Instruction(cmov_op, reg1, free_reg);
                            move_forward(bit, bend, it, end);

                            *it = ltac::Instruction(ltac::Operator::NOP);
                            move_forward(bit, bend, it, end);
                            *it = ltac::Instruction(ltac::Operator::NOP);
                            move_forward(bit, bend, it, end);
                            *it = ltac::Instruction(ltac::Operator::NOP);
                            move_forward(bit, bend, it, end);

                            optimized = true;
                            function.context->global()->stats().inc_counter("cmov_opt");
                        }
                    }
                }
            }
        }

        if(!move_forward(bit, bend, it, end)){
            return optimized;
        }
    }

    return optimized;
}

bool debug(const std::string& name, bool b, mtac::Function& function){
    if(log::enabled<Debug>()){
        if(b){
            LOG<Debug>("Peephole") << name << " returned true" << log::endl;

            //Print the function
            ltac::Printer printer;
            printer.print(function);
        } else {
            LOG<Debug>("Peephole") << name << " returned false" << log::endl;
        }
    }

    return b;
}

} //end of anonymous namespace

void eddic::ltac::optimize(mtac::Program& program, Platform platform){
    timing_timer timer(program.context->timing(), "peephole_optimization");

    for(auto& function : program.functions){
        if(log::enabled<Debug>()){
            LOG<Debug>("Peephole") << "Start optimizations on " << function.get_name() << log::endl;

            //Print the function
            ltac::Printer printer;
            printer.print(function);
        }

        bool optimized;
        do {
            optimized = false;

            optimized |= debug("Basic optimizations", basic_optimizations(function, platform), function);
            optimized |= debug("Constant propagation", constant_propagation(function), function);
            optimized |= debug("Copy propagation", copy_propagation(function, platform), function);
            optimized |= debug("Dead-Code Elimination", dead_code_elimination(function), function);
            optimized |= debug("Conditional move", conditional_move(function, platform), function);
        } while(optimized);
    }
}


#include <gflags/gflags.h>

#include <NN_Agent.h>
#include <random>
#include <chrono>
#include <thread>
#include <sstream>
#include <iostream>

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>

#include <boost/filesystem.hpp>
#include <boost/algorithm/string/predicate.hpp>

//Common flags
DEFINE_int32(train, 1, "Whether we want to train the net or generate new data");
DEFINE_int32(image_size, 256, "Size of the images (16, 32, 64, 128)");
DEFINE_int32(batch_size, 1, "Number of samples in one pass");
DEFINE_int32(generator_features, 64, "Number of features for the generator convolution layers");
DEFINE_int32(discriminator_features, 64, "Number of features for the discriminator convolution layers");
DEFINE_int32(n_resnet, 9, "Number of resnet blocks for the generator");
DEFINE_string(folder_A, "../Data/horse2zebra/trainA", "Folder with A images");
DEFINE_string(folder_B, "../Data/horse2zebra/trainB", "Folder with B images");
DEFINE_string(weights_gen_AtoB, "", "Weights file to load (generator A to B)");
DEFINE_string(weights_gen_BtoA, "", "Weights file to load (generator B to A)");

//Training flags
DEFINE_string(solver_generator, "solver_generator.prototxt", "Solver file for generators");
DEFINE_string(solver_discriminator, "solver_discriminator.prototxt", "Solver file for discriminators");
DEFINE_string(logfile, "log.csv", "File to log the losses during training");
DEFINE_int32(max_pool_size, 50, "Maximum size of the memory pool. 0 to deactivate it");
DEFINE_double(lambda, 10.0, "Weight of the cyclic loss");
DEFINE_int32(start_epoch, 0, "Starting epoch number");
DEFINE_int32(end_epoch, 100, "Ending epoch number");
DEFINE_string(snapshot_gen_AtoB, "", "Snapshot file to resume training (generator A to B)");
DEFINE_string(snapshot_gen_BtoA, "", "Snapshot file to resume training (generator B to A)");
DEFINE_string(snapshot_discr_A, "", "Snapshot file to resume training (discriminator A)");
DEFINE_string(snapshot_discr_B, "", "Snapshot file to resume training (discriminator B)");
DEFINE_string(weights_discr_A, "", "Weights file to load (discriminator A)");
DEFINE_string(weights_discr_B, "", "Weights file to load (discriminator B)");

//Testing flags
DEFINE_string(output_folder_A, "../launch_files/testA/", "Output base folder for A transformed images");
DEFINE_string(output_folder_B, "../launch_files/testB/", "Output base folder for B transformed images");

/**
* \brief Load all the images from a folder into a vector
* \param folder Path of the folder containing the images
* \param destination Destination vector. The images are pushed at the end
* \param image_size Desired size of the images
*/
void LoadImagesFromFolder(const std::string &folder, std::vector<std::vector<float> > *destination, const int &image_size)
{
	for (boost::filesystem::directory_entry &file : boost::filesystem::directory_iterator(boost::filesystem::path(folder)))
	{
		if (boost::ends_with(file.path().string(), ".jpg") || boost::ends_with(file.path().string(), ".png") || boost::ends_with(file.path().string(), ".jpeg") || boost::ends_with(file.path().string(), ".bmp"))
		{
			cv::Mat img = cv::imread(file.path().string(), CV_LOAD_IMAGE_COLOR);
			cv::Mat resized_image;

			cv::resize(img, resized_image, cv::Size(image_size, image_size));

			cv::Mat img_float;
			resized_image.convertTo(img_float, CV_32F);
			img_float /= 127.5f;
			img_float -= cv::Scalar(1.0f, 1.0f, 1.0f);

			std::vector<cv::Mat> channels;
			cv::split(img_float, channels);

			std::vector<float> current_image;
			current_image.reserve(image_size * image_size * img_float.channels());

			for (int i = 0; i < img_float.channels(); ++i)
			{
				current_image.insert(current_image.end(), (float*)channels[i].datastart, (float*)channels[i].dataend);
			}
			destination->push_back(current_image);
		}
	}
}

/**
* \brief Convert network raw output into an image
* \param data Raw network output
* \param batch_size Number of images in the data
* \param image_size Size of the images
*/
cv::Mat BatchDataToImage(const std::vector<float> &data, const int &batch_size, const int &image_size)
{
	int number_image_per_line = std::ceil(std::sqrtf(batch_size));
	cv::Mat output = cv::Mat::zeros(cv::Size(number_image_per_line * image_size, number_image_per_line * image_size), CV_32FC3);

	for (int i = 0; i < batch_size; ++i)
	{
		std::vector<cv::Mat> channels;
		for (int c = 0; c < 3; ++c)
		{
			cv::Mat channel = cv::Mat(cv::Size(image_size, image_size), CV_32FC1);
			std::memcpy((float*)channel.data, data.data() + i * 3 * image_size * image_size + c * image_size * image_size, image_size * image_size * sizeof(float));
			channels.push_back(channel);
		}
		cv::Mat current_image;
		cv::merge(channels, current_image);

		current_image += cv::Scalar(1.0f, 1.0f, 1.0f);
		current_image *= 127.5f;

		current_image.copyTo(output(cv::Rect((i % number_image_per_line) * image_size, (i / number_image_per_line) * image_size, image_size, image_size)));
	}

	return output;
}

std::string ConvolutionLayer(const std::string &name, const std::string &bottom, const std::string &top, const int &n_features, const int &size, const int &stride, const int &pad, const bool train, const bool bias)
{
	std::stringstream layer_code;

	layer_code
		<< "layer {" << "\n"
		<< "\t" << "name: \"" << name << "\"" << "\n"
		<< "\t" << "type: \"Convolution\"" << "\n"
		<< "\t" << "bottom: \"" << bottom << "\"" << "\n"
		<< "\t" << "top: \"" << top << "\"" << "\n"
		<< "\t" << "convolution_param {" << "\n"
		<< "\t" << "\t" << "num_output: " << n_features << "\n"
		<< "\t" << "\t" << "kernel_size: " << size << "\n"
		<< "\t" << "\t" << "stride: " << stride << "\n"
		<< "\t" << "\t" << "pad: " << pad << "\n";
	if (bias)
	{
		layer_code
			<< "\t" << "\t" << "bias_term: true" << "\n";
	}
	else
	{
		layer_code
			<< "\t" << "\t" << "bias_term: false" << "\n";
	}

	if (train)
	{
		layer_code
			<< "\t" << "\t" << "weight_filler {" << "\n"
			<< "\t" << "\t" << "\t" << "type: \"gaussian\"" << "\n"
			<< "\t" << "\t" << "\t" << "std: 0.02" << "\n"
			<< "\t" << "\t" << "}" << "\n";
		if (bias)
		{
			layer_code
				<< "\t" << "\t" << "bias_filler {" << "\n"
				<< "\t" << "\t" << "\t" << "type: \"constant\"" << "\n"
				<< "\t" << "\t" << "\t" << "value: 0" << "\n"
				<< "\t" << "\t" << "}" << "\n";
		}
	}
	layer_code
		<< "\t" << "}" << "\n"
		<< "}" << "\n"
		<< "\n";

	return layer_code.str();
}

//Deconvolution implemented as NN-upsampling + convolution to avoid checkboard artifacts
std::string DeconvolutionLayer(const std::string &name, const std::string &bottom, const std::string &top, const int &input_features, const int &output_features, const int &size, const int &stride, const int &pad, const bool train, const bool bias)
{
	std::stringstream layer_code;

	layer_code
		<< "layer {" << "\n"
		<< "\t" << "name: \"" << name + "_upsample" << "\"" << "\n"
		<< "\t" << "type: \"Deconvolution\"" << "\n"
		<< "\t" << "bottom: \"" << bottom << "\"" << "\n"
		<< "\t" << "top: \"" << bottom + "_upsampled" << "\"" << "\n"
		<< "\t" << "convolution_param {" << "\n"
		<< "\t" << "\t" << "num_output: " << input_features << "\n"
		<< "\t" << "\t" << "group: " << input_features << "\n"
		<< "\t" << "\t" << "kernel_size: " << 2 << "\n"
		<< "\t" << "\t" << "stride: " << 2 << "\n"
		<< "\t" << "\t" << "pad: " << 0 << "\n"
		<< "\t" << "\t" << "weight_filler {" << "\n"
		<< "\t" << "\t" << "\t" << "type: \"constant\"" << "\n"
		<< "\t" << "\t" << "\t" << "value: 1" << "\n"
		<< "\t" << "\t" << "}" << "\n"
		<< "\t" << "\t" << "bias_term: false" << "\n"
		<< "\t" << "}" << "\n"
		<< "\t" << "param {" << "\n"
		<< "\t" << "\t" << "lr_mult: " << 0 << "\n"
		<< "\t" << "\t" << "decay_mult: " << 0 << "\n"
		<< "\t" << "}" << "\n"
		<< "}" << "\n"
		<< "\n";

	layer_code
		<< ConvolutionLayer(name + "_conv", bottom + "_upsampled", top, output_features, size, stride, pad, train, bias);

	return layer_code.str();
}

std::string BatchNormLayer(const std::string &name, const std::string &bottom, const std::string &top)
{
	std::stringstream layer_code;

	layer_code
		<< "layer {" << "\n"
		<< "\t" << "name: \"" << name << "\"" << "\n"
		<< "\t" << "type: \"BatchNorm\"" << "\n"
		<< "\t" << "bottom: \"" << bottom << "\"" << "\n"
		<< "\t" << "top: \"" << top << "\"" << "\n"
		<< "\t" << "batch_norm_param {" << "\n"
		<< "\t" << "\t" << "use_global_stats: false" << "\n"
		<< "\t" << "}" << "\n"
		<< "}" << "\n"
		<< "\n";

	return layer_code.str();
}

std::string ScaleLayer(const std::string &name, const std::string &bottom, const std::string &top, const bool use_bias, const bool constant)
{
	std::stringstream layer_code;

	layer_code
		<< "layer {" << "\n"
		<< "\t" << "name: \"" << name << "\"" << "\n"
		<< "\t" << "type: \"Scale\"" << "\n"
		<< "\t" << "bottom: \"" << bottom << "\"" << "\n"
		<< "\t" << "top: \"" << top << "\"" << "\n"
		<< "\t" << "scale_param {" << "\n";
	if (!constant)
	{
		layer_code
			<< "\t" << "\t" << "filler {" << "\n"
			<< "\t" << "\t" << "\t" << "type: \"gaussian\"" << "\n"
			<< "\t" << "\t" << "\t" << "std: 0.02" << "\n"
			<< "\t" << "\t" << "\t" << "mean: 1.0" << "\n"
			<< "\t" << "\t" << "}" << "\n";
	}
	if (use_bias)
	{
		layer_code
			<< "\t" << "\t" << "bias_term: true" << "\n";

	}
	else
	{
		layer_code
			<< "\t" << "\t" << "bias_term: false" << "\n";
	}
	layer_code
		<< "\t" << "}" << "\n";
	if (constant)
	{
		layer_code
			<< "\t" << "param {" << "\n"
			<< "\t" << "\t" << "lr_mult: " << 0 << "\n"
			<< "\t" << "\t" << "decay_mult: " << 0 << "\n"
			<< "\t" << "}" << "\n";

		if (use_bias)
		{
			layer_code
				<< "\t" << "param {" << "\n"
				<< "\t" << "\t" << "lr_mult: " << 0 << "\n"
				<< "\t" << "\t" << "decay_mult: " << 0 << "\n"
				<< "\t" << "}" << "\n";
		}
	}

	layer_code
		<< "}" << "\n"
		<< "\n";

	return layer_code.str();
}

std::string ReLULayer(const std::string &name, const std::string &bottom, const std::string &top, const float &negative_slope = 0.0f)
{
	std::stringstream layer_code;

	layer_code
		<< "layer {" << "\n"
		<< "\t" << "name: \"" << name << "\"" << "\n"
		<< "\t" << "type: \"ReLU\"" << "\n"
		<< "\t" << "bottom: \"" << bottom << "\"" << "\n"
		<< "\t" << "top: \"" << top << "\"" << "\n";
	if (negative_slope != 0.0f)
	{
		layer_code
			<< "\t" << "relu_param {" << "\n"
			<< "\t" << "\t" << "negative_slope: " << negative_slope << "\n"
			<< "\t" << "}" << "\n";
	}
	layer_code
		<< "}" << "\n"
		<< "\n";

	return layer_code.str();
}

std::string SumLayer(const std::string &name, const std::vector<std::string> &bottoms, const std::string &top)
{
	std::stringstream layer_code;

	layer_code
		<< "layer {" << "\n"
		<< "\t" << "name: \"" << name << "\"" << "\n"
		<< "\t" << "type: \"Eltwise\"" << "\n";
	for (int i = 0; i < bottoms.size(); ++i)
	{
		layer_code
			<< "\t" << "bottom: \"" << bottoms[i] << "\"" << "\n";
	}
	layer_code
		<< "\t" << "top: \"" << top << "\"" << "\n"
		<< "\t" << "eltwise_param {" << "\n"
		<< "\t" << "\t" << "operation: SUM" << "\n"
		<< "\t" << "}" << "\n"
		<< "}" << "\n"
		<< "\n";

	return layer_code.str();
}

std::string ResnetBlock(const std::string &name, const std::string &bottom, const std::string &top, const int &input_features, const int &output_features, const bool train)
{
	std::stringstream block_code;

	block_code
		<< ConvolutionLayer(name + "_Conv1", bottom, bottom + "_Conv1", input_features, 3, 1, 1, train, false)
		<< BatchNormLayer(name + "_BN1", bottom + "_Conv1", bottom + "_Conv1")
		<< ScaleLayer(name + "_Scale1", bottom + "_Conv1", bottom + "_Conv1", true, false)
		<< ReLULayer(name + "_ReLU1", bottom + "_Conv1", bottom + "_Conv1", 0.2f)
		<< ConvolutionLayer(name + "_Conv2", bottom + "_Conv1", bottom + "_Conv2", output_features, 3, 1, 1, train, false)
		<< BatchNormLayer(name + "_BN2", bottom + "_Conv2", bottom + "_Conv2")
		<< ScaleLayer(name + "_Scale2", bottom + "_Conv2", bottom + "_Conv2", true, false);

	std::vector<std::string> sum_bottoms;
	sum_bottoms.push_back(bottom + "_Conv2");

	if (input_features != output_features)
	{
		block_code
			<< ConvolutionLayer(name + "_Conv3", bottom, bottom + "_Conv3", output_features, 1, 1, 0, train, false)
			<< BatchNormLayer(name + "_BN3", bottom + "_Conv3", bottom + "_Conv3")
			<< ScaleLayer(name + "_Scale3", bottom + "_Conv3", bottom + "_Conv3", true, false);
		sum_bottoms.push_back(bottom + "_Conv3");
	}
	else
	{
		sum_bottoms.push_back(bottom);
	}

	block_code
		<< SumLayer(name + "_Sum", sum_bottoms, top)
		<< ReLULayer(name + "_ReLU2", top, top, 0.2f);

	return block_code.str();
}

std::string ReshapeLayer(const std::string &name, const std::string &bottom, const std::string &top, const std::vector<int> &output_shape)
{
	std::stringstream layer_code;

	layer_code
		<< "layer {" << "\n"
		<< "\t" << "name: \"" << name << "\"" << "\n"
		<< "\t" << "type: \"Reshape\"" << "\n"
		<< "\t" << "bottom: \"" << bottom << "\"" << "\n"
		<< "\t" << "top: \"" << top << "\"" << "\n"
		<< "\t" << "reshape_param {" << "\n"
		<< "\t" << "\t" << "shape {" << "\n";
	for (int i = 0; i < output_shape.size(); ++i)
	{
		layer_code
			<< "\t" << "\t" << "\t" << "dim: " << output_shape[i] << "\n";
	}
	layer_code
		<< "\t" << "\t" << "}" << "\n"
		<< "\t" << "}" << "\n"
		<< "}" << "\n"
		<< "\n";

	return layer_code.str();
}

/**
* \brief Create generator prototxt and save it in current directory as "generator.prototxt"
* \param train True if the net is for training, false otherwise
* \param batch_size Size of one batch in images
* \param image_size Size of the images
* \param n_features Number of features in the first convolution
* \param n_resnet Number of resnet block between the encoder and the decoder
*/
void CreateGeneratorPrototxt(const bool train, const int &batch_size, const int &image_size, const int &n_features, const int &n_resnet)
{
	std::ofstream prototxt("generator.prototxt");

	//Net header
	prototxt
		<< "name: \"Generator\"" << "\n"
		<< "force_backward: true" << "\n"
		//<< "debug_info: true" << "\n"
		<< "\n";

	//Input layer
	prototxt
		<< "layer {" << "\n"
		<< "\t" << "name: \"Input\"" << "\n"
		<< "\t" << "type: \"Input\"" << "\n"
		<< "\t" << "top: \"generator_input\"" << "\n"
		<< "\t" << "input_param {" << "\n"
		<< "\t" << "\t" << "shape {" << "\n"
		<< "\t" << "\t" << "\t" << "dim: " << batch_size << "\n"
		<< "\t" << "\t" << "\t" << "dim: " << 3 << "\n"
		<< "\t" << "\t" << "\t" << "dim: " << image_size << "\n"
		<< "\t" << "\t" << "\t" << "dim: " << image_size << "\n"
		<< "\t" << "\t" << "}" << "\n"
		<< "\t" << "}" << "\n"
		<< "}" << "\n"
		<< "\n";

	//Encoder
	prototxt
		<< ConvolutionLayer("Generator_Conv1", "generator_input", "generator_Conv1", n_features, 7, 1, 3, train, false)
		<< "#We don't use the computed mean and std at test time because" << "\n"
		<< "#it's calculated from both real and fake distribution which" << "\n"
		<< "#is not what we want. As we use batch of size 1, it should" << "\n"
		<< "#be equivalent to instance normalization." << "\n"
		<< BatchNormLayer("Generator_BN1", "generator_Conv1", "generator_Conv1")
		<< ScaleLayer("Generator_Scale1", "generator_Conv1", "generator_Conv1", true, false)
		<< ReLULayer("Generator_ReLU1", "generator_Conv1", "generator_Conv1", 0.2f)
		<< ConvolutionLayer("Generator_Conv2", "generator_Conv1", "generator_Conv2", 2 * n_features, 3, 2, 1, train, false)
		<< BatchNormLayer("Generator_BN2", "generator_Conv2", "generator_Conv2")
		<< ScaleLayer("Generator_Scale2", "generator_Conv2", "generator_Conv2", true, false)
		<< ReLULayer("Generator_ReLU2", "generator_Conv2", "generator_Conv2", 0.2f)
		<< ConvolutionLayer("Generator_Conv3", "generator_Conv2", "generator_Conv3", 4 * n_features, 3, 2, 1, train, false)
		<< BatchNormLayer("Generator_BN3", "generator_Conv3", "generator_Conv3")
		<< ScaleLayer("Generator_Scale3", "generator_Conv3", "generator_Conv3", true, false)
		<< ReLULayer("Generator_ReLU3", "generator_Conv3", "generator_Conv3", 0.2f);

	std::string current_bottom = "generator_Conv3";

	//Transformer
	for (int i = 0; i < n_resnet; ++i)
	{
		prototxt
			<< ResnetBlock("Generator_ResnetBlock_" + std::to_string(i), current_bottom, "output_resnet_" + std::to_string(i), 4 * n_features, 4 * n_features, train);
		current_bottom = "output_resnet_" + std::to_string(i);
	}

	//Decoder
	prototxt
		<< DeconvolutionLayer("Generator_Deconv1", current_bottom, "generator_Deconv1", 4 * n_features, 2 * n_features, 3, 1, 1, train, false)
		<< BatchNormLayer("Generator_BN4", "generator_Deconv1", "generator_Deconv1")
		<< ScaleLayer("Generator_Scale4", "generator_Deconv1", "generator_Deconv1", true, false)
		<< ReLULayer("Generator_ReLU4", "generator_Deconv1", "generator_Deconv1", 0.2f)
		<< DeconvolutionLayer("Generator_Deconv2", "generator_Deconv1", "generator_Deconv2", 2 * n_features, n_features, 3, 1, 1, train, false)
		<< BatchNormLayer("Generator_BN5", "generator_Deconv2", "generator_Deconv2")
		<< ScaleLayer("Generator_Scale5", "generator_Deconv2", "generator_Deconv2", true, false)
		<< ReLULayer("Generator_ReLU5", "generator_Deconv2", "generator_Deconv2", 0.2f)
		<< ConvolutionLayer("Generator_Conv4", "generator_Deconv2", "generator_pre_output", 3, 7, 1, 3, train, false);
	prototxt
		<< "layer {" << "\n"
		<< "\t" << "name: \"Generator_tanH_output\"" << "\n"
		<< "\t" << "type: \"TanH\"" << "\n"
		<< "\t" << "bottom: \"generator_pre_output\"" << "\n"
		<< "\t" << "top: \"generator_output\"" << "\n"
		<< "}" << "\n"
		<< "\n";

	//Cyclic loss (L1-Loss built up with caffe layers)
	if (train)
	{
		prototxt
			<< "layer {" << "\n"
			<< "\t" << "name: \"Input_cyclic\"" << "\n"
			<< "\t" << "type: \"Input\"" << "\n"
			<< "\t" << "top: \"cyclic_input\"" << "\n"
			<< "\t" << "input_param {" << "\n"
			<< "\t" << "\t" << "shape {" << "\n"
			<< "\t" << "\t" << "\t" << "dim: " << batch_size << "\n"
			<< "\t" << "\t" << "\t" << "dim: " << 3 << "\n"
			<< "\t" << "\t" << "\t" << "dim: " << image_size << "\n"
			<< "\t" << "\t" << "\t" << "dim: " << image_size << "\n"
			<< "\t" << "\t" << "}" << "\n"
			<< "\t" << "}" << "\n"
			<< "}" << "\n"
			<< "\n";

		//L1-Loss
		prototxt
			<< "layer {" << "\n"
			<< "\t" << "name: \"Cyclic_loss_diff\"" << "\n"
			<< "\t" << "type: \"Eltwise\"" << "\n"
			<< "\t" << "bottom: \"generator_output\"" << "\n"
			<< "\t" << "bottom: \"cyclic_input\"" << "\n"
			<< "\t" << "top: \"cyclic_diff\"" << "\n"
			<< "\t" << "eltwise_param " << "{" << "\n"
			<< "\t" << "\t" << "operation: SUM" << "\n"
			<< "\t" << "\t" << "coeff: " << 1 << "\n"
			<< "\t" << "\t" << "coeff: " << -1 << "\n"
			<< "\t" << "}" << "\n"
			<< "}" << "\n"
			<< "\n";

		prototxt
			<< "layer {" << "\n"
			<< "\t" << "name: \"Cyclic_loss_reduction_1\"" << "\n"
			<< "\t" << "type: \"Reduction\"" << "\n"
			<< "\t" << "bottom: \"cyclic_diff\"" << "\n"
			<< "\t" << "top: \"summed_diff\"" << "\n"
			<< "\t" << "reduction_param " << "{" << "\n"
			<< "\t" << "\t" << "operation: ASUM" << "\n"
			<< "\t" << "\t" << "axis: " << 1 << "\n"
			<< "\t" << "}" << "\n"
			<< "}" << "\n"
			<< "\n";

		prototxt
			<< "layer {" << "\n"
			<< "\t" << "name: \"Cyclic_loss_scale\"" << "\n"
			<< "\t" << "type: \"Scale\"" << "\n"
			<< "\t" << "bottom: \"summed_diff\"" << "\n"
			<< "\t" << "top: \"scaled_diff\"" << "\n"
			<< "\t" << "scale_param " << "{" << "\n"
			<< "\t" << "\t" << "filler {" << "\n"
			<< "\t" << "\t" << "\t" << "type: \"constant\"" << "\n"
			<< "\t" << "\t" << "\t" << "value: " << 1.0f / (batch_size * 3 * image_size * image_size) << "\n"
			<< "\t" << "\t" << "}" << "\n"
			<< "\t" << "\t" << "axis: 0" << "\n"
			<< "\t" << "\t" << "bias_term: false" << "\n"
			<< "\t" << "}" << "\n"
			<< "\t" << "param " << "{" << "\n"
			<< "\t" << "\t" << "lr_mult: 0" << "\n"
			<< "\t" << "\t" << "decay_mult: 0" << "\n"
			<< "\t" << "}" << "\n"
			<< "}" << "\n"
			<< "\n";

		prototxt
			<< "layer {" << "\n"
			<< "\t" << "name: \"Cyclic_loss\"" << "\n"
			<< "\t" << "type: \"Reduction\"" << "\n"
			<< "\t" << "bottom: \"scaled_diff\"" << "\n"
			<< "\t" << "top: \"cyclic_loss\"" << "\n"
			<< "\t" << "reduction_param " << "{" << "\n"
			<< "\t" << "\t" << "operation: SUM" << "\n"
			<< "\t" << "}" << "\n"
			<< "\t" << "loss_weight: 1" << "\n"
			<< "}" << "\n"
			<< "\n";
	}

	prototxt.close();
}

/**
* \brief Create the discriminator prototxt and save it in current directory as "discriminator.prototxt"
* \param train True if the net is for training, false otherwise
* \param batch_size Size of one batch in images
* \param image_size Size of the images
* \param n_features Number of features in the first convolution
*/
void CreateDiscriminatorPrototxt(const bool train, const int &batch_size, const int &image_size, const int &n_features)
{
	std::ofstream prototxt("discriminator.prototxt");

	//Net header
	prototxt
		<< "name: \"Discriminator\"" << "\n"
		<< "force_backward: true" << "\n"
		//<< "debug_info: true" << "\n"
		<< "\n";

	//Input layer
	prototxt
		<< "layer {" << "\n"
		<< "\t" << "name: \"Input\"" << "\n"
		<< "\t" << "type: \"Input\"" << "\n"
		<< "\t" << "top: \"images_input\"" << "\n"
		<< "\t" << "top: \"labels_input\"" << "\n"
		<< "\t" << "input_param {" << "\n"
		<< "\t" << "\t" << "shape {" << "\n"
		<< "\t" << "\t" << "\t" << "dim: " << batch_size << "\n"
		<< "\t" << "\t" << "\t" << "dim: " << 3 << "\n"
		<< "\t" << "\t" << "\t" << "dim: " << image_size << "\n"
		<< "\t" << "\t" << "\t" << "dim: " << image_size << "\n"
		<< "\t" << "\t" << "}" << "\n"
		<< "\t" << "\t" << "shape {" << "\n"
		<< "\t" << "\t" << "\t" << "dim: " << batch_size << "\n"
		<< "\t" << "\t" << "\t" << "dim: " << 1 << "\n"
		<< "\t" << "\t" << "\t" << "dim: " << image_size / 8 << "\n"
		<< "\t" << "\t" << "\t" << "dim: " << image_size / 8 << "\n"
		<< "\t" << "\t" << "}" << "\n"
		<< "\t" << "}" << "\n"
		<< "}" << "\n"
		<< "\n";

	prototxt
		<< ConvolutionLayer("Discriminator_Conv1", "images_input", "Discriminator_Conv1", n_features, 4, 2, 1, train, false)
		<< "#We don't use the computed mean and std at test time because" << "\n"
		<< "#it's calculated from both real and fake distribution which" << "\n"
		<< "#is not what we want. As we use batch of size 1, it should" << "\n"
		<< "#be equivalent to instance normalization." << "\n"
		<< BatchNormLayer("Discriminator_BN1", "Discriminator_Conv1", "Discriminator_Conv1")
		<< ScaleLayer("Discriminator_Scale1", "Discriminator_Conv1", "Discriminator_Conv1", true, false)
		<< ReLULayer("Discriminator_ReLU1", "Discriminator_Conv1", "Discriminator_Conv1", 0.2f)
		<< ConvolutionLayer("Discriminator_Conv2", "Discriminator_Conv1", "Discriminator_Conv2", 2 * n_features, 4, 2, 1, train, false)
		<< BatchNormLayer("Discriminator_BN2", "Discriminator_Conv2", "Discriminator_Conv2")
		<< ScaleLayer("Discriminator_Scale2", "Discriminator_Conv2", "Discriminator_Conv2", true, false)
		<< ReLULayer("Discriminator_ReLU2", "Discriminator_Conv2", "Discriminator_Conv2", 0.2f)
		<< ConvolutionLayer("Discriminator_Conv3", "Discriminator_Conv2", "Discriminator_Conv3", 4 * n_features, 4, 2, 1, train, false)
		<< BatchNormLayer("Discriminator_BN3", "Discriminator_Conv3", "Discriminator_Conv3")
		<< ScaleLayer("Discriminator_Scale3", "Discriminator_Conv3", "Discriminator_Conv3", true, false)
		<< ReLULayer("Discriminator_ReLU3", "Discriminator_Conv3", "Discriminator_Conv3", 0.2f)
		<< ConvolutionLayer("Discriminator_Conv4", "Discriminator_Conv3", "Discriminator_Conv4", 8 * n_features, 3, 1, 1, train, false)
		<< BatchNormLayer("Discriminator_BN4", "Discriminator_Conv4", "Discriminator_Conv4")
		<< ScaleLayer("Discriminator_Scale4", "Discriminator_Conv4", "Discriminator_Conv4", true, false)
		<< ReLULayer("Discriminator_ReLU4", "Discriminator_Conv4", "Discriminator_Conv4", 0.2f);

	prototxt
		<< ConvolutionLayer("Discriminator_Conv5", "Discriminator_Conv4", "Discriminator_Output", 1, 1, 1, 0, train, false);

	prototxt
		<< ReshapeLayer("Reshape_input", "Discriminator_Output", "Discriminator_Reshaped_Output", std::vector<int>({ { -1 } }));

	prototxt
		<< ReshapeLayer("Reshape_labels", "labels_input", "labels_reshaped_input", std::vector<int>({ { -1 } }));

	prototxt
		<< "layer {" << "\n"
		<< "\t" << "name: \"Discriminator_loss\"" << "\n"
		<< "\t" << "type: \"EuclideanLoss\"" << "\n"
		<< "\t" << "bottom: \"Discriminator_Reshaped_Output\"" << "\n"
		<< "\t" << "bottom: \"labels_reshaped_input\"" << "\n"
		<< "\t" << "top: \"loss\"" << "\n"
		<< "\t" << "loss_weight: 1" << "\n"
		<< "}" << "\n"
		<< "\n";

	prototxt.close();
}

int main(int argc, char** argv)
{
#ifdef CPU_ONLY
	caffe::Caffe::set_mode(caffe::Caffe::CPU);
#else
	caffe::Caffe::set_mode(caffe::Caffe::GPU);
#endif

	gflags::ParseCommandLineFlags(&argc, &argv, true);
	google::InitGoogleLogging(argv[0]);

	//In test mode we don't need all the caffe stuff
	if (!FLAGS_train)
	{
		for (int i = 0; i < google::NUM_SEVERITIES; ++i)
		{
			google::SetLogDestination(i, "");
		}
	}
	else
	{
		google::LogToStderr();
	}

	std::mt19937 random_gen = std::mt19937(std::chrono::high_resolution_clock::now().time_since_epoch().count());

	CreateGeneratorPrototxt(FLAGS_train, FLAGS_batch_size, FLAGS_image_size, FLAGS_generator_features, FLAGS_n_resnet);

	//Read images and store data
	std::vector<std::vector<float> > images_A;
	std::vector<std::vector<float> > images_B;

	boost::filesystem::path folder_data_A = FLAGS_folder_A;
	boost::filesystem::path folder_data_B = FLAGS_folder_B;

	std::cout << "Reading data ..." << std::endl;

	if (boost::filesystem::is_empty(folder_data_A))
	{
		std::cerr << "Error, data folder A not found or empty." << std::endl;
		return -1;
	}

	LoadImagesFromFolder(folder_data_A.string(), &images_A, FLAGS_image_size);

	if (boost::filesystem::is_empty(folder_data_B))
	{
		std::cerr << "Error, data folder B not found or empty." << std::endl;
		return -1;
	}

	LoadImagesFromFolder(folder_data_B.string(), &images_B, FLAGS_image_size);

	//Train
	if (FLAGS_train)
	{
		CreateDiscriminatorPrototxt(FLAGS_train, FLAGS_batch_size, FLAGS_image_size, FLAGS_discriminator_features);

		NN_Agent nets(FLAGS_solver_generator, FLAGS_solver_discriminator, FLAGS_logfile, FLAGS_max_pool_size,
					  FLAGS_lambda, FLAGS_snapshot_gen_AtoB, FLAGS_snapshot_gen_BtoA, FLAGS_snapshot_discr_A,
					  FLAGS_snapshot_discr_B, FLAGS_weights_gen_AtoB, FLAGS_weights_gen_BtoA,
					  FLAGS_weights_discr_A, FLAGS_weights_discr_B);

		std::cout << "Networks ready." << std::endl;

		std::vector<std::vector<float> > generator_input_test_A;
		std::vector<std::vector<float> > generator_input_test_B;
		std::vector<cv::Mat> test_images_A;
		std::vector<cv::Mat> test_images_B;

		for (int i = 0; i < 4; ++i)
		{
			generator_input_test_A.push_back(images_A[i]);
			test_images_A.push_back(BatchDataToImage(images_A[i], 1, FLAGS_image_size));
			generator_input_test_B.push_back(images_B[i]);
			test_images_B.push_back(BatchDataToImage(images_B[i], 1, FLAGS_image_size));
		}

		int number_of_batch_in_epoch = std::min(images_A.size(), images_B.size()) / FLAGS_batch_size;

		std::cout << "One epoch is: " << number_of_batch_in_epoch << " iterations." << std::endl;

		//Number of epochs
		for (int epoch = FLAGS_start_epoch; epoch < FLAGS_end_epoch; ++epoch)
		{
			//Transform test data
			std::vector<cv::Mat> image_transformed_A;
			std::vector<cv::Mat> image_transformed_B;
			cv::Mat image_transformation_A = cv::Mat::zeros(cv::Size(4 * FLAGS_image_size, 2 * FLAGS_image_size), CV_32FC3);
			for (int i = 0; i < generator_input_test_A.size(); ++i)
			{
				std::vector<float> transformed_A = nets.GeneratorTransform(generator_input_test_A[i], true);
				BatchDataToImage(transformed_A, 1, FLAGS_image_size).copyTo(image_transformation_A(cv::Rect((2 * (i % 2) + 1)*FLAGS_image_size, i / 2 * FLAGS_image_size, FLAGS_image_size, FLAGS_image_size)));
				test_images_A[i].copyTo(image_transformation_A(cv::Rect((2 * (i % 2))*FLAGS_image_size, i / 2 * FLAGS_image_size, FLAGS_image_size, FLAGS_image_size)));
			}
			cv::Mat image_transformation_B = cv::Mat::zeros(cv::Size(4 * FLAGS_image_size, 2 * FLAGS_image_size), CV_32FC3);
			for (int i = 0; i < generator_input_test_B.size(); ++i)
			{
				std::vector<float> transformed_B = nets.GeneratorTransform(generator_input_test_B[i], false);
				BatchDataToImage(transformed_B, 1, FLAGS_image_size).copyTo(image_transformation_B(cv::Rect((2 * (i % 2) + 1)*FLAGS_image_size, i / 2 * FLAGS_image_size, FLAGS_image_size, FLAGS_image_size)));
				test_images_B[i].copyTo(image_transformation_B(cv::Rect((2 * (i % 2))*FLAGS_image_size, i / 2 * FLAGS_image_size, FLAGS_image_size, FLAGS_image_size)));
			}

			cv::imwrite("A_to_B_epoch_" + std::to_string(epoch) + ".png", image_transformation_A);
			cv::imwrite("B_to_A_epoch_" + std::to_string(epoch) + ".png", image_transformation_B);

			//Shuffle the indices of the images
			std::vector<int> indices_A;
			std::vector<int> indices_B;
			indices_A.reserve(images_A.size());
			indices_B.reserve(images_B.size());

			for (int i = 0; i < images_A.size(); ++i)
			{
				indices_A.push_back(i);
			}
			for (int i = 0; i < images_B.size(); ++i)
			{
				indices_B.push_back(i);
			}

			std::random_shuffle(indices_A.begin(), indices_A.end());
			std::random_shuffle(indices_B.begin(), indices_B.end());

			for (int batch = 0; batch < number_of_batch_in_epoch; ++batch)
			{
				std::vector<float> batch_data_A;
				std::vector<float> batch_data_B;

				for (int i = 0; i < FLAGS_batch_size; ++i)
				{
					batch_data_A.insert(batch_data_A.end(), images_A[indices_A[FLAGS_batch_size * batch + i]].begin(), images_A[indices_A[FLAGS_batch_size * batch + i]].end());
					batch_data_B.insert(batch_data_B.end(), images_B[indices_B[FLAGS_batch_size * batch + i]].begin(), images_B[indices_B[FLAGS_batch_size * batch + i]].end());
				}

				nets.Train(batch_data_A, batch_data_B);
			}
		}

		nets.Snapshot();

		std::vector<cv::Mat> image_transformed_A;
		std::vector<cv::Mat> image_transformed_B;
		cv::Mat image_transformation_A = cv::Mat::zeros(cv::Size(4 * FLAGS_image_size, 2 * FLAGS_image_size), CV_32FC3);
		for (int i = 0; i < generator_input_test_A.size(); ++i)
		{
			std::vector<float> transformed_A = nets.GeneratorTransform(generator_input_test_A[i], true);
			BatchDataToImage(transformed_A, 1, FLAGS_image_size).copyTo(image_transformation_A(cv::Rect((2 * (i % 2) + 1)*FLAGS_image_size, i / 2 * FLAGS_image_size, FLAGS_image_size, FLAGS_image_size)));
			test_images_A[i].copyTo(image_transformation_A(cv::Rect((2 * (i % 2))*FLAGS_image_size, i / 2 * FLAGS_image_size, FLAGS_image_size, FLAGS_image_size)));
		}
		cv::Mat image_transformation_B = cv::Mat::zeros(cv::Size(4 * FLAGS_image_size, 2 * FLAGS_image_size), CV_32FC3);
		for (int i = 0; i < generator_input_test_B.size(); ++i)
		{
			std::vector<float> transformed_B = nets.GeneratorTransform(generator_input_test_B[i], false);
			BatchDataToImage(transformed_B, 1, FLAGS_image_size).copyTo(image_transformation_B(cv::Rect((2 * (i % 2) + 1)*FLAGS_image_size, i / 2 * FLAGS_image_size, FLAGS_image_size, FLAGS_image_size)));
			test_images_B[i].copyTo(image_transformation_B(cv::Rect((2 * (i % 2))*FLAGS_image_size, i / 2 * FLAGS_image_size, FLAGS_image_size, FLAGS_image_size)));
		}

		cv::imwrite("A_to_B.png", image_transformation_A);
		cv::imwrite("B_to_A.png", image_transformation_B);

		return 0;
	}
	//Test
	else
	{
		NN_Agent nets("generator.prototxt", FLAGS_weights_gen_AtoB, FLAGS_weights_gen_BtoA);

		int index = 0;

		while (index < images_A.size())
		{
			std::vector<float> batch_A;
			for (int i = 0; i < std::min(FLAGS_batch_size, (int)(images_A.size() - index)); ++i)
			{
				batch_A.insert(batch_A.end(), images_A[index + i].begin(), images_A[index + i].end());
			}

			cv::Mat batch_input = BatchDataToImage(batch_A, FLAGS_batch_size, FLAGS_image_size);
			cv::Mat batch_output = BatchDataToImage(nets.GeneratorTransform(batch_A, true), FLAGS_batch_size, FLAGS_image_size);

			int number_image_per_line = std::ceil(std::sqrtf(FLAGS_batch_size));

			for (int i = 0; i < std::min(FLAGS_batch_size, (int)(images_A.size() - index)); ++i)
			{
				cv::Mat output = cv::Mat(FLAGS_image_size, 2 * FLAGS_image_size, CV_32FC3);
				batch_input(cv::Rect((i % number_image_per_line) * FLAGS_image_size, (i / number_image_per_line) * FLAGS_image_size, FLAGS_image_size, FLAGS_image_size)).copyTo(output(cv::Rect(0, 0, FLAGS_image_size, FLAGS_image_size)));
				batch_output(cv::Rect((i % number_image_per_line) * FLAGS_image_size, (i / number_image_per_line) * FLAGS_image_size, FLAGS_image_size, FLAGS_image_size)).copyTo(output(cv::Rect(FLAGS_image_size, 0, FLAGS_image_size, FLAGS_image_size)));
				cv::imwrite(FLAGS_output_folder_A + std::to_string(index + i) + ".png", output);
			}
			index += FLAGS_batch_size;
		}

		index = 0;
		while (index < images_B.size())
		{
			std::vector<float> batch_B;
			for (int i = 0; i < std::min(FLAGS_batch_size, (int)(images_B.size() - index)); ++i)
			{
				batch_B.insert(batch_B.end(), images_B[index + i].begin(), images_B[index + i].end());
			}

			cv::Mat batch_input = BatchDataToImage(batch_B, FLAGS_batch_size, FLAGS_image_size);
			cv::Mat batch_output = BatchDataToImage(nets.GeneratorTransform(batch_B, false), FLAGS_batch_size, FLAGS_image_size);

			int number_image_per_line = std::ceil(std::sqrtf(FLAGS_batch_size));

			for (int i = 0; i < std::min(FLAGS_batch_size, (int)(images_B.size() - index)); ++i)
			{
				cv::Mat output = cv::Mat(FLAGS_image_size, 2 * FLAGS_image_size, CV_32FC3);
				batch_input(cv::Rect((i % number_image_per_line) * FLAGS_image_size, (i / number_image_per_line) * FLAGS_image_size, FLAGS_image_size, FLAGS_image_size)).copyTo(output(cv::Rect(0, 0, FLAGS_image_size, FLAGS_image_size)));
				batch_output(cv::Rect((i % number_image_per_line) * FLAGS_image_size, (i / number_image_per_line) * FLAGS_image_size, FLAGS_image_size, FLAGS_image_size)).copyTo(output(cv::Rect(FLAGS_image_size, 0, FLAGS_image_size, FLAGS_image_size)));
				cv::imwrite(FLAGS_output_folder_B + std::to_string(index + i) + ".png", output);
			}
			index += FLAGS_batch_size;
		}

		return 0;
	}
}


///////////////////////////////////////////////////////////////////////////////
// Copyright Christopher Kormanyos 2014.
// Copyright John Maddock 2014.
// Copyright Paul Bristow 2014.
// 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)
//

// Implement a specialization of std::complex<> for *anything* that
// is defined as BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE.

#ifndef _BOOST_CSTDFLOAT_COMPLEX_STD_2014_02_15_HPP_
  #define _BOOST_CSTDFLOAT_COMPLEX_STD_2014_02_15_HPP_

  #if defined(__GNUC__)
  #pragma GCC system_header
  #endif

  #include <complex>
  #include <boost/math/constants/constants.hpp>

  namespace std
  {
    // Forward declarations.
    template<class float_type>
    class complex;

    template<>
    class complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>;

    inline BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE real(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE imag(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);

    inline BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE abs (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE arg (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE norm(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> conj (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> proj (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> polar(const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE&,
                                                                      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE& = 0);

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> sqrt (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> sin  (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> cos  (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> tan  (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> asin (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> acos (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> atan (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> exp  (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> log  (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> log10(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> pow  (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&,
                                                                      int);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> pow  (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&,
                                                                      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> pow  (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&,
                                                                      const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> pow  (const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE&,
                                                                      const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> sinh (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> cosh (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> tanh (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> asinh(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> acosh(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> atanh(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);

    template<class char_type, class traits_type>
    inline std::basic_ostream<char_type, traits_type>& operator<<(std::basic_ostream<char_type, traits_type>&, const std::complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);

    template<class char_type, class traits_type>
    inline std::basic_istream<char_type, traits_type>& operator>>(std::basic_istream<char_type, traits_type>&, std::complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>&);

    // Template specialization of the complex class.
    template<>
    class complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>
    {
    public:
      typedef BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE value_type;

      explicit complex(const complex<float>&);
      explicit complex(const complex<double>&);
      explicit complex(const complex<long double>&);

      #if defined(BOOST_NO_CXX11_CONSTEXPR)
      complex(const value_type& r = value_type(),
              const value_type& i = value_type()) : re(r),
                                                    im(i) { }

      template<typename X>
      complex(const complex<X>& x) : re(x.real()),
                                     im(x.imag()) { }

      const value_type& real() const { return re; }
      const value_type& imag() const { return im; }

      value_type& real() { return re; }
      value_type& imag() { return im; }
      #else
      BOOST_CONSTEXPR complex(const value_type& r = value_type(),
                              const value_type& i = value_type()) : re(r),
                                                                    im(i) { }

      template<typename X>
      BOOST_CONSTEXPR complex(const complex<X>& x) : re(x.real()),
                                                     im(x.imag()) { }

      value_type real() const { return re; }
      value_type imag() const { return im; }
      #endif

      void real(value_type r) { re = r; }
      void imag(value_type i) { im = i; }

      complex<value_type>& operator=(const value_type& v)
      {
        re = v;
        im = value_type(0);
        return *this;
      }

      complex<value_type>& operator+=(const value_type& v)
      {
        re += v;
        return *this;
      }

      complex<value_type>& operator-=(const value_type& v)
      {
        re -= v;
        return *this;
      }

      complex<value_type>& operator*=(const value_type& v)
      {
        re *= v;
        im *= v;
        return *this;
      }

      complex<value_type>& operator/=(const value_type& v)
      {
        re /= v;
        im /= v;
        return *this;
      }

      template<typename X>
      complex<value_type>& operator=(const complex<X>& x)
      {
        re = x.real();
        im = x.imag();
        return *this;
      }

      template<typename X>
      complex<value_type>& operator+=(const complex<X>& x)
      {
        re += x.real();
        im += x.imag();
        return *this;
      }

      template<typename X>
      complex<value_type>& operator-=(const complex<X>& x)
      {
        re -= x.real();
        im -= x.imag();
        return *this;
      }

      template<typename X>
      complex<value_type>& operator*=(const complex<X>& x)
      {
        const value_type tmp_real = (re * x.real()) - (im * x.imag());
        im = (re * x.imag()) + (im * x.real());
        re = tmp_real;
        return *this;
      }

      template<typename X>
      complex<value_type>& operator/=(const complex<X>& x)
      {
        const value_type tmp_real = (re * x.real()) + (im * x.imag());
        const value_type the_norm = std::norm(x);
        im = ((im * x.real()) - (re * x.imag())) / the_norm;
        re = tmp_real / the_norm;
        return *this;
      }

      private:
        value_type re;
        value_type im;
    };

    // Constructors from built-in complex representation of floating-point types.
    complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::complex(const complex<float>& f)        : re(BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE( f.real())), im(BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE( f.imag())) { }
    complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::complex(const complex<double>& d)       : re(BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE( d.real())), im(BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE( d.imag())) { }
    complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::complex(const complex<long double>& ld) : re(BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(ld.real())), im(BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(ld.imag())) { }
  } // namespace std

  namespace boost { namespace math { namespace cstdfloat { namespace detail {
  template<class float_type> std::complex<float_type> multiply_by_i(const std::complex<float_type>& x)
  {
    // Multiply x (in C) by I (the imaginary component), and return the result.
    return std::complex<float_type>(-x.imag(), x.real());
  }
  } } } } // boost::math::cstdfloat::detail

  namespace std
  {
    // ISO/IEC 14882:2011, Section 26.4.7, specific values.
    inline BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE real(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x) { return x.real(); }
    inline BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE imag(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x) { return x.imag(); }

    inline BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE abs (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x) { using std::sqrt;  return sqrt ((real(x) * real(x)) + (imag(x) * imag(x))); }
    inline BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE arg (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x) { using std::atan2; return atan2(x.imag(), x.real()); }
    inline BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE norm(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x) { return (real(x) * real(x)) + (imag(x) * imag(x)); }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> conj (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x) { return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(x.real(), -x.imag()); }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> proj (const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE m = (std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::max)();
      if ((x.real() > m)
        || (x.real() < -m)
        || (x.imag() > m)
        || (x.imag() < -m))
      {
        // We have an infinity, return a normalized infinity, respecting the sign of the imaginary part:
         return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::infinity(), x.imag() < 0 ? -0 : 0);
      }
      return x;
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> polar(const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE& rho,
                                                                      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE& theta)
    {
      using std::sin;
      using std::cos;

      return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(rho * cos(theta), rho * sin(theta));
    }

    // Global add, sub, mul, div.
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator+(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& u, const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& v) { return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(u.real() + v.real(), u.imag() + v.imag()); }
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator-(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& u, const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& v) { return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(u.real() - v.real(), u.imag() - v.imag()); }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator*(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& u, const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& v)
    {
      return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>((u.real() * v.real()) - (u.imag() * v.imag()),
                                                                  (u.real() * v.imag()) + (u.imag() * v.real()));
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator/(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& u, const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& v)
    {
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE the_norm = std::norm(v);

      return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(((u.real() * v.real()) + (u.imag() * v.imag())) / the_norm,
                                                                  ((u.imag() * v.real()) - (u.real() * v.imag())) / the_norm);
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator+(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& u, const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE& v) { return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(u.real() + v, u.imag()); }
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator-(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& u, const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE& v) { return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(u.real() - v, u.imag()); }
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator*(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& u, const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE& v) { return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(u.real() * v, u.imag() * v); }
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator/(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& u, const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE& v) { return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(u.real() / v, u.imag() / v); }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator+(const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE& u, const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& v) { return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(u + v.real(),     v.imag()); }
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator-(const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE& u, const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& v) { return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(u - v.real(),    -v.imag()); }
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator*(const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE& u, const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& v) { return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(u * v.real(), u * v.imag()); }
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator/(const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE& u, const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& v) { const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE v_norm = norm(v); return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>((u * v.real()) / v_norm, (-u * v.imag()) / v_norm); }

    // Unary plus / minus.
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator+(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& u) { return u; }
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> operator-(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& u) { return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(-u.real(), -u.imag()); }

    // Equality and inequality.
    inline bool operator==(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x, const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& y) { return ((x.real() == y.real()) && (x.imag() == y.imag())); }
    inline bool operator==(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x, const         BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE&  y) { return ((x.real() == y)        && (x.imag() == BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(0))); }
    inline bool operator==(const         BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE&  x, const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& y) { return ((x        == y.real()) && (y.imag() == BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(0))); }
    inline bool operator!=(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x, const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& y) { return ((x.real() != y.real()) || (x.imag() != y.imag())); }
    inline bool operator!=(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x, const         BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE&  y) { return ((x.real() != y)        || (x.imag() != BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(0))); }
    inline bool operator!=(const         BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE&  x, const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& y) { return ((x        != y.real()) || (y.imag() != BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(0))); }

    // ISO/IEC 14882:2011, Section 26.4.8, transcendentals.
    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> sqrt(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      using std::fabs;
      using std::sqrt;

      // Compute sqrt(x) for x in C:
      // sqrt(x) = (s       , xi / 2s) : for xr > 0,
      //           (|xi| / 2s, +-s)    : for xr < 0,
      //           (sqrt(xi), sqrt(xi) : for xr = 0,
      // where s = sqrt{ [ |xr| + sqrt(xr^2 + xi^2) ] / 2 },
      // and the +- sign is the same as the sign of xi.

      if(x.real() > 0)
      {
        const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE s = sqrt((fabs(x.real()) + std::abs(x)) / 2);

        return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(s, x.imag() / (s * 2));
      }
      else if(x.real() < 0)
      {
        const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE s = sqrt((fabs(x.real()) + std::abs(x)) / 2);

        const bool imag_is_neg = (x.imag() < 0);

        return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(fabs(x.imag()) / (s * 2), (imag_is_neg ? -s : s));
      }
      else
      {
        const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE sqrt_xi_half = sqrt(x.imag() / 2);

        return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(sqrt_xi_half, sqrt_xi_half);
      }
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> sin(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      using std::sin;
      using std::cos;
      using std::exp;

      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE sin_x  = sin (x.real());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE cos_x  = cos (x.real());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE exp_yp = exp (x.imag());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE exp_ym = BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1) / exp_yp;
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE sinh_y = (exp_yp - exp_ym) / 2;
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE cosh_y = (exp_yp + exp_ym) / 2;

      return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(sin_x * cosh_y, cos_x * sinh_y);
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> cos(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      using std::sin;
      using std::cos;
      using std::exp;

      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE sin_x  = sin (x.real());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE cos_x  = cos (x.real());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE exp_yp = exp (x.imag());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE exp_ym = BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1) / exp_yp;
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE sinh_y = (exp_yp - exp_ym) / 2;
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE cosh_y = (exp_yp + exp_ym) / 2;

      return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(cos_x * cosh_y, -(sin_x * sinh_y));
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> tan(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      using std::sin;
      using std::cos;
      using std::exp;

      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE sin_x  = sin (x.real());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE cos_x  = cos (x.real());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE exp_yp = exp (x.imag());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE exp_ym = BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1) / exp_yp;
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE sinh_y = (exp_yp - exp_ym) / 2;
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE cosh_y = (exp_yp + exp_ym) / 2;

      return (  complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(sin_x * cosh_y,  cos_x * sinh_y)
              / complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(cos_x * cosh_y, -sin_x * sinh_y));
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> asin(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      return -boost::math::cstdfloat::detail::multiply_by_i(std::log(boost::math::cstdfloat::detail::multiply_by_i(x) + std::sqrt(BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1) - (x * x))));
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> acos(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      return boost::math::constants::half_pi<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>() - std::asin(x);
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> atan(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> izz = boost::math::cstdfloat::detail::multiply_by_i(x);

      return boost::math::cstdfloat::detail::multiply_by_i(std::log(BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1) - izz) - std::log(BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1) + izz)) / 2;
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> exp(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      using std::exp;

      return std::polar(exp(x.real()), x.imag());
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> log(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      using std::atan2;
      using std::log;

      return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(log(std::norm(x)) / 2, atan2(x.imag(), x.real()));
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> log10(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      return std::log(x) / boost::math::constants::ln_ten<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>();
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> pow(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x,
                                                                    int p)
    {
      const bool re_isneg  = (x.real() < 0);
      const bool re_isnan  = (x.real() != x.real());
      const bool re_isinf  = ((!re_isneg) ? bool(+x.real() > (std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::max)())
                                          : bool(-x.real() > (std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::max)()));

      const bool im_isneg  = (x.imag() < 0);
      const bool im_isnan  = (x.imag() != x.imag());
      const bool im_isinf  = ((!im_isneg) ? bool(+x.imag() > (std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::max)())
                                          : bool(-x.imag() > (std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::max)()));

      if(re_isnan || im_isnan) { return x; }

      if(re_isinf || im_isinf)
      {
        return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::quiet_NaN(),
                                                                    std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::quiet_NaN());
      }

      if(p < 0)
      {
        if(std::abs(x) < (std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::min)())
        {
          return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::infinity(),
                                                                      std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::infinity());
        }
        else
        {
          return BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1) / std::pow(x, -p);
        }
      }

      if(p == 0)
      {
        return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1));
      }
      else
      {
        if(p == 1) { return x; }

        if(std::abs(x) > (std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::max)())
        {
          const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE re = (re_isneg ? -std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::infinity()
                                                                           : +std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::infinity());

          const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE im = (im_isneg ? -std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::infinity()
                                                                           : +std::numeric_limits<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>::infinity());

          return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(re, im);
        }

        if     (p == 2) { return  (x * x); }
        else if(p == 3) { return ((x * x) * x); }
        else if(p == 4) { const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> x2 = (x * x); return (x2 * x2); }
        else
        {
          // The variable xn stores the binary powers of x.
          complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> result(((p % 2) != 0) ? x : complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1)));
          complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> xn    (x);

          int p2 = p;

          while((p2 /= 2) != 0)
          {
            // Square xn for each binary power.
            xn *= xn;

            const bool has_binary_power = ((p2 % 2) != 0);

            if(has_binary_power)
            {
              // Multiply the result with each binary power contained in the exponent.
              result *= xn;
            }
          }

          return result;
        }
      }
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> pow(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x,
                                                                    const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE& a)
    {
      return std::exp(a * std::log(x));
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> pow(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x,
                                                                    const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& a)
    {
      return std::exp(a * std::log(x));
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> pow(const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE& x,
                                                                    const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& a)
    {
      return std::exp(a * std::log(x));
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> sinh(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      using std::sin;
      using std::cos;
      using std::exp;

      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE sin_y  = sin (x.imag());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE cos_y  = cos (x.imag());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE exp_xp = exp (x.real());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE exp_xm = BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1) / exp_xp;
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE sinh_x = (exp_xp - exp_xm) / 2;
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE cosh_x = (exp_xp + exp_xm) / 2;

      return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(cos_y * sinh_x, cosh_x * sin_y);
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> cosh(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      using std::sin;
      using std::cos;
      using std::exp;

      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE sin_y  = sin (x.imag());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE cos_y  = cos (x.imag());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE exp_xp = exp (x.real());
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE exp_xm = BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1) / exp_xp;
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE sinh_x = (exp_xp - exp_xm) / 2;
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE cosh_x = (exp_xp + exp_xm) / 2;

      return complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(cos_y * cosh_x, sin_y * sinh_x);
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> tanh(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> ex_plus  = std::exp(x);
      const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> ex_minus = BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1) / ex_plus;

      return (ex_plus - ex_minus) / (ex_plus + ex_minus);
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> asinh(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      return std::log(x + std::sqrt((x * x) + BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1)));
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> acosh(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      const BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE my_one(1);

      const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> zp(x.real() + my_one, x.imag());
      const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> zm(x.real() - my_one, x.imag());

      return std::log(x + (zp * std::sqrt(zm / zp)));
    }

    inline complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE> atanh(const complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      return (std::log(BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1) + x) - std::log(BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE(1) - x)) / 2.0;
    }

    template<class char_type, class traits_type>
    inline std::basic_ostream<char_type, traits_type>& operator<<(std::basic_ostream<char_type, traits_type>& os, const std::complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      std::basic_ostringstream<char_type, traits_type> ostr;

      ostr.flags(os.flags());
      ostr.imbue(os.getloc());
      ostr.precision(os.precision());

      ostr << char_type('(')
           << x.real()
           << char_type(',')
           << x.imag()
           << char_type(')');

      return (os << ostr.str());
    }

    template<class char_type, class traits_type>
    inline std::basic_istream<char_type, traits_type>& operator>>(std::basic_istream<char_type, traits_type>& is, std::complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>& x)
    {
      BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE rx;
      BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE ix;

      char_type the_char;

      static_cast<void>(is >> the_char);

      if(the_char == static_cast<char_type>('('))
      {
        static_cast<void>(is >> rx >> the_char);

        if(the_char == static_cast<char_type>(','))
        {
          static_cast<void>(is >> ix >> the_char);

          if(the_char == static_cast<char_type>(')'))
          {
            x = complex<BOOST_CSTDFLOAT_EXTENDED_COMPLEX_FLOAT_TYPE>(rx, ix);
          }
          else
          {
            is.setstate(ios_base::failbit);
          }
        }
        else if(the_char == static_cast<char_type>(')'))
        {
          x = rx;
        }
        else
        {
          is.setstate(ios_base::failbit);
        }
      }
      else
      {
        static_cast<void>(is.putback(the_char));

        static_cast<void>(is >> rx);

        x = rx;
      }

      return is;
    }
  } // namespace std

#endif // _BOOST_CSTDFLOAT_COMPLEX_STD_2014_02_15_HPP_


// Boost.Geometry

// Copyright (c) 2018, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

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

#ifndef BOOST_GEOMETRY_PROJECTIONS_CONSTANTS_HPP
#define BOOST_GEOMETRY_PROJECTIONS_CONSTANTS_HPP

#include <boost/geometry/util/math.hpp>
#include <boost/math/constants/constants.hpp>

namespace boost { namespace geometry { namespace projections
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail
{

template <typename T>
inline T fourth_pi() { return T(0.78539816339744830961566084581988); }
template <typename T>
inline T third_pi() { return boost::math::constants::third_pi<T>(); }
template <typename T>
inline T half_pi() { return boost::math::constants::half_pi<T>(); }
template <typename T>
inline T pi() { return boost::math::constants::pi<T>(); }
template <typename T>
inline T one_and_half_pi() { return T(4.7123889803846898576939650749193); }
template <typename T>
inline T two_pi() { return boost::math::constants::two_pi<T>(); }
template <typename T>
inline T two_and_half_pi() { return T(7.8539816339744830961566084581988); }

template <typename T>
inline T two_div_pi() { return boost::math::constants::two_div_pi<T>(); }
template <typename T>
inline T half_pi_sqr() { return T(2.4674011002723396547086227499689); }
template <typename T>
inline T pi_sqr() { return boost::math::constants::pi_sqr<T>(); }

template <typename T>
inline T sixth() { return boost::math::constants::sixth<T>(); }
template <typename T>
inline T third() { return boost::math::constants::third<T>(); }
template <typename T>
inline T two_thirds() { return boost::math::constants::two_thirds<T>(); }

} // namespace detail
#endif // DOXYGEN_NO_DETAIL

}}} // namespace boost::geometry::projections
#endif // BOOST_GEOMETRY_PROJECTIONS_IMPL_PROJECTS_HPP