/*
 * Freely-propagating flame
 * ========================
 *
 * C++ demo program to compute flame speeds using GRI-Mech.
 *
 * Usage:
 *
 *     flamespeed [equivalence_ratio] [refine_grid] [loglevel]
 *
 * Requires: cantera >= 3.2
 *
 * .. tags:: C++, combustion, 1D flow, premixed flame, flame speed, saving output
 */

// This file is part of Cantera. See License.txt in the top-level directory or
// at https://cantera.org/license.txt for license and copyright information.

#include "cantera/onedim.h"
#include "cantera/oneD/DomainFactory.h"
#include "cantera/base/stringUtils.h"
#include <fstream>

using namespace Cantera;
using fmt::print;

int flamespeed(double phi, bool refine_grid, int loglevel)
{
    try {
        auto sol = newSolution("gri30.yaml", "gri30", "mixture-averaged");
        auto gas = sol->thermo();
        double temp = 300.0; // K
        double pressure = 1.0*OneAtm; //atm
        double uin = 0.3; //m/sec

        size_t nsp = gas->nSpecies();
        vector<double> x(nsp, 0.0);

        gas->setEquivalenceRatio(phi, "CH4", "O2:0.21,N2:0.79");
        gas->setState_TP(temp, pressure);
        gas->getMoleFractions(x.data());

        double rho_in = gas->density();

        vector<double> yin(nsp);
        gas->getMassFractions(&yin[0]);

        gas->equilibrate("HP");
        vector<double> yout(nsp);
        gas->getMassFractions(&yout[0]);
        double rho_out = gas->density();
        double Tad = gas->temperature();
        print("phi = {}, Tad = {}\n", phi, Tad);

        //=============  build each domain ========================

        //-------- step 1: create the flow -------------

        auto flow = newDomain<Flow1D>("gas-flow", sol, "flow");
        flow->setFreeFlow();

        // create an initial grid
        int nz = 6;
        double lz = 0.1;
        vector<double> z(nz);
        double dz = lz/((double)(nz-1));
        for (int iz = 0; iz < nz; iz++) {
            z[iz] = ((double)iz)*dz;
        }

        flow->setupGrid(nz, &z[0]);
        flow->setSteadyTolerances(1e-5, 1e-11);

        //------- step 2: create the inlet  -----------------------

        auto inlet = newDomain<Inlet1D>("inlet", sol);

        inlet->setMoleFractions(x.data());
        double mdot = uin * rho_in;
        inlet->setMdot(mdot);
        inlet->setTemperature(temp);

        //------- step 3: create the outlet  ---------------------

        auto outlet = newDomain<Outlet1D>("outlet", sol);

        //=================== create the container and insert the domains =====

        vector<shared_ptr<Domain1D>> domains { inlet, flow, outlet };
        Sim1D flame(domains);

        //----------- Supply initial guess----------------------

        vector<double> locs{0.0, 0.3, 0.7, 1.0};
        vector<double> value;

        double uout = inlet->mdot()/rho_out;
        value = {uin, uin, uout, uout};
        flow->setProfile("velocity", locs, value);
        value = {temp, temp, Tad, Tad};
        flow->setProfile("T", locs, value);

        for (size_t i=0; i<nsp; i++) {
            value = {yin[i], yin[i], yout[i], yout[i]};
            flow->setProfile(gas->speciesName(i), locs, value);
        }

        flame.show();

        double ratio = 10.0;
        double slope = 0.08;
        double curve = 0.1;

        flow->setRefineCriteria(ratio,slope,curve);

        // Save initial guess to container file

        // Solution is saved in HDF5 or YAML file format
        string fileName;
        if (usesHDF5()) {
            // Cantera is compiled with native HDF5 support
            fileName = "flamespeed.h5";
        } else {
            fileName = "flamespeed.yaml";
        }
        flame.save(fileName, "initial-guess", "Initial guess", true);

        // Solve freely propagating flame

        // Linearly interpolate to find location where this temperature would
        // exist. The temperature at this location will then be fixed for
        // remainder of calculation.
        flame.setFixedTemperature(0.5 * (temp + Tad));
        flow->setEnergyEnabled(true);

        flame.solve(loglevel,refine_grid);
        double flameSpeed_mix = flow->values("velocity")[0];
        print("Flame speed with mixture-averaged transport: {} m/s\n", flameSpeed_mix);
        flame.save(fileName, "mix", "Solution with mixture-averaged transport", true);

        // now switch to multicomponent transport
        flow->setTransportModel("multicomponent");
        flame.solve(loglevel, refine_grid);
        double flameSpeed_multi = flow->values("velocity")[0];
        print("Flame speed with multicomponent transport: {} m/s\n", flameSpeed_multi);
        flame.save(fileName, "multi", "Solution with multicomponent transport", true);

        // now enable Soret diffusion
        flow->enableSoret(true);
        flame.solve(loglevel, refine_grid);
        double flameSpeed_full = flow->values("velocity")[0];
        print("Flame speed with multicomponent transport + Soret: {} m/s\n",
              flameSpeed_full);
        flame.save(fileName, "soret",
                   "Solution with mixture-averaged transport and Soret", true);

        auto zvec = flow->grid();
        auto Tvec = flow->values("T");
        auto COvec = flow->values("CO");
        auto CO2vec = flow->values("CO2");
        auto Uvec = flow->values("velocity");

        print("\n{:9s}\t{:8s}\t{:5s}\t{:7s}\n",
              "z (m)", "T (K)", "U (m/s)", "Y(CO)");
        for (size_t n = 0; n < flow->nPoints(); n++) {
            print("{:9.6f}\t{:8.3f}\t{:5.3f}\t{:7.5f}\n",
                  zvec[n], Tvec[n], Uvec[n], COvec[n]);
        }

        print("\nAdiabatic flame temperature from equilibrium is: {}\n", Tad);
        print("Flame speed for phi={} is {} m/s.\n", phi, Uvec[0]);

        std::ofstream outfile("flamespeed.csv", std::ios::trunc);
        outfile << "  Grid,   Temperature,   Uvec,   CO,    CO2\n";
        for (size_t n = 0; n < flow->nPoints(); n++) {
            print(outfile, " {:11.3e}, {:11.3e}, {:11.3e}, {:11.3e}, {:11.3e}\n",
                  zvec[n], Tvec[n], Uvec[n], COvec[n], CO2vec[n]);
        }
    } catch (CanteraError& err) {
        std::cerr << err.what() << std::endl;
        std::cerr << "program terminating." << std::endl;
        return -1;
    }
    return 0;
}

int main(int argc, char** argv)
{
    double phi;
    int loglevel = 1;
    bool refine_grid = true;
    if (argc >= 2) {
        phi = fpValue(argv[1]);
    } else {
        print("Enter phi: ");
        std::cin >> phi;
    }
    if (argc >= 3) {
        refine_grid = bool(std::stoi(argv[2]));
    }
    if (argc >= 4) {
        loglevel = std::stoi(argv[3]);
    }
    return flamespeed(phi, refine_grid, loglevel);
}
