#include "jsonWriters.h"

namespace porting {

template<typename T>
auto writePressures(const result::State<T>* state) {
    auto nodes = ordered_json::array();
    auto const& pressures = state->getPressures();
    for (auto& [key, pressure] : pressures) {
        nodes.push_back({{"pressure", pressure}});
    }
    return nodes;
}

template<typename T>
auto writeChannels(const arch::Network<T>* network, const result::State<T>* state) {
    auto channels_json = ordered_json::array();

    auto const& flowRates = state->getFlowRates();
    auto const& mixturePositions = state->getMixturePositions();

    auto const& channels = network->getChannels();
    auto const& flowRatePumps = network->getFlowRatePumps();
    auto const& pressurePumps = network->getPressurePumps();
    auto const& membranes = network->getMembranes();
    auto const& tanks = network->getTanks();
    auto edgeCount = channels.size() + flowRatePumps.size() + pressurePumps.size() + membranes.size() + tanks.size();

    // since edge ids should be continuous, iterate over number of total edges;
    // not all edges necessarily have a flowrate (membranes, tanks) or a mixturePosition
    for (size_t i = 0; i < edgeCount; ++i) {
        auto channel = ordered_json::object();
        if (flowRates.count(i)) {
            channel["flowRate"] = flowRates.at(i);
        }
        if (mixturePositions.count(i)) {
            channel["mixturePositions"] = ordered_json::array();
            for (auto& position : mixturePositions.at(i)) {
                channel["mixturePositions"].push_back({
                    {"mixture", position.mixtureId},
                    {"start", position.position1},
                    {"end", position.position2}
                });
            }
        }
        channels_json.push_back(channel);
    }
    return channels_json;
}

template<typename T>
auto writeModules(const result::State<T>* state) {      
    auto modules = ordered_json::array();
    auto const& vtkFiles = state->getVtkFiles();
    for (auto& [key, vtkFile] : vtkFiles) {
        auto module = ordered_json::object();
        module["id"] = key;
        module["vtkFile"] = vtkFile;
        modules.push_back(module);
    }
    return modules;
}

template<typename T>
auto writeDroplets(const result::State<T>* state, const sim::AbstractDroplet<T>* simulation) {      
    auto Droplets = ordered_json::array();
    for (auto& [key, dropletPosition] : state->getDropletPositions()) {
        //dropletPosition
        auto Droplet = ordered_json::object();

        //state
        Droplet["id"] = key;
        Droplet["fluid"] = simulation->getDroplet(key)->readFluid()->getId();
        Droplet["volume"] = simulation->getDroplet(key)->getVolume();

        //boundaries
        Droplet["boundaries"] = ordered_json::array();
        for(auto& boundary : dropletPosition.boundaries) {
            Droplet["boundaries"].push_back({
                {"volumeTowards1", boundary.isVolumeTowardsNodeA()},
                {"position", {
                    {"channel", boundary.readChannelPosition().getChannel()->getId()},
                    {"position", boundary.readChannelPosition().getPosition()}}
                }
            });
        }

        //channels
        Droplet["channels"] = ordered_json::array();
        for (auto const& channelId : dropletPosition.channelIds) {
            Droplet["channels"].push_back(channelId);
        }
        Droplets.push_back(Droplet);
    }
    return Droplets;
}

template<typename T>
auto writeFluids(const sim::Simulation<T>* simulation) {      
    auto Fluids = ordered_json::array();
    auto const& simFluids = simulation->readFluids();
    for (size_t i=0; i<simFluids.size(); ++i) {
        auto Fluid = ordered_json::object();
        auto& simFluid = simFluids.at(i);
        Fluid["id"] = simFluid->getId();
        Fluid["name"] = simFluid->getName();
        Fluid["density"] = simFluid->getDensity();
        Fluid["viscosity"] = simFluid->getViscosity();
        Fluids.push_back(Fluid);
    }
    return Fluids;
}

template<typename T>
auto writeMixtures (const sim::AbstractConcentration<T>* simulation) {
    auto Mixtures = ordered_json::array();
    auto const& simMixtures = simulation->readMixtures();
    for (size_t i=0; i<simMixtures.size(); ++i) {
        auto Mixture = ordered_json::object();
        auto& simMixture = simMixtures.at(i);
        for(auto& [key, specie] : simMixture->getSpecies()) {
            Mixture["species"].push_back(specie->getId());
            Mixture["concentrations"].push_back(simMixture->getConcentrationOfSpecie(specie));
        }
        Mixtures.push_back(Mixture);
    }
    return Mixtures;
}

template<typename T>
std::string writeSimType(const sim::Simulation<T>* simulation) {      
    if(simulation->getType() == sim::Type::Hybrid) {
        return("Hybrid");
    } else if (simulation->getType() == sim::Type::CFD) {
        return("CFD");
    }
    return("Abstract");
}

template<typename T>
std::string writeSimPlatform(const sim::Simulation<T>* simulation) {      
    if(simulation->getPlatform() == sim::Platform::Droplet) {
        return("Droplet");
    } else if (simulation->getPlatform() == sim::Platform::Concentration) {
        return("Concentration");
    } else if (simulation->getPlatform() == sim::Platform::Membrane) {
        return("Membrane");
    }
    return("Continuous");
}

template<typename T>
void writeMixtures (json& jsonString, const result::State<T>* state, const sim::AbstractConcentration<T>* simulation) {
    auto mixturePositions = json::array();
    for (auto& [key, mixturePosition] : state->getMixturePositions()) {
        // mixture object
        auto position = json::object();

        // species
        position["species"] = json::array();
        for(auto& specie : mixturePosition.mixture->getSpecies()) {
            mixturePosition["species"].push_back(specie.getId());
        }

        // concentrations
        position["concentrations"] = json::array();
        for(auto& concentration : mixturePosition.mixture->getConcentrations()) {
            mixturePosition["concentrations"].push_back(concentration);
        }

        //boundaries
        position["boundaries"] = json::array();
        for(auto& boundary : mixturePosition.boundaries()) {
            position["boundaries"].push_back({
                {
                    {"channelId", boundary.getChannel()},
                    {"position1", boundary.getPosition1()},
                    {"position2", boundary.getPosition2()}
                }
            });
        }

        mixturePositions.push_back(position);
    }
    jsonString["result"].push_back({"mixturePositions", mixturePositions});
}

}   // namespace porting