/*
  Copyright 2010 SINTEF ICT, Applied Mathematics.
  Copyright 2010 Statoil ASA

  This file is part of the Open Porous Media project (OPM).

  OPM is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  OPM is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with OPM.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
   Program to regularize cornerpoint grids

   Caveats:
   - Only grids with vertical pillars

   - CornerPointChopper can only chop along existing pillars. In case
     your asked-for horizontal resolution does not divide the initial
     number of pillars in x/y, you will not obtain a fully regular
     grid, but still easier numerically.

   - Be careful with non-flat top and bottom boundary.

*/

#include <config.h>

#include <opm/input/eclipse/Parser/Parser.hpp>
#include <opm/input/eclipse/Deck/Deck.hpp>

#include <opm/common/utility/platform_dependent/disable_warnings.h>

#include <dune/common/version.hh>

#include <dune/common/parallel/mpihelper.hh>

#include <opm/common/utility/platform_dependent/reenable_warnings.h>

#include <opm/input/eclipse/Units/Units.hpp>

#include <opm/upscaling/CornerpointChopper.hpp>
#include <opm/output/eclipse/EclipseGridInspector.hpp>

#include <opm/porsol/common/setupBoundaryConditions.hpp>

#include <opm/upscaling/SinglePhaseUpscaler.hpp>

#include <ctime>
#include <fstream>
#include <iomanip>
#include <ios>
#include <iostream>
#include <sstream>

#include <sys/utsname.h>

int main(int argc, char** argv)
try
{
   if (argc == 1) {
        std::cout << "Usage: cpregularize gridfilename=filename.grdecl [ires=5] [jres=5] [zres=5] " << std::endl;
        std::cout << "       [imin=] [imax=] [jmin=] [jmax=] [zmin=] [zmax=] " << std::endl;
        std::cout << "       [minperm=1e-9] " << std::endl;
        std::cout << "       [resultgrid=regularizedgrid.grdecl]" << std::endl;
        exit(1);
    }

    Dune::MPIHelper::instance(argc, argv);

    Opm::ParameterGroup param(argc, argv);
    std::string gridfilename = param.get<std::string>("gridfilename");
    Opm::CornerPointChopper ch(gridfilename);

    // The cells with i coordinate in [imin, imax) are included, similar for j.
    // The z limits may be changed inside the chopper to match actual min/max z.
    const int* dims = ch.dimensions();
    int imin = param.getDefault("imin", 0);
    int imax = param.getDefault("imax", dims[0]);
    int jmin = param.getDefault("jmin", 0);
    int jmax = param.getDefault("jmax", dims[1]);
    double zmin = param.getDefault("zmin", ch.zLimits().first);
    double zmax = param.getDefault("zmax", ch.zLimits().second);
    int ires = param.getDefault("ires", 1);
    int jres = param.getDefault("jres", 1);
    int zres = param.getDefault("zres", 1);

    std::string resultgrid = param.getDefault<std::string>("resultgrid", "regularizedgrid.grdecl");

    if (param.has("z_tolerance")) {
        std::cerr << "****** Warning: z_tolerance parameter is obsolete, use PINCH in deck input instead\n";
    }
    // Check for unused parameters (potential typos).
    if (param.anyUnused()) {
	std::cout << "*****     WARNING: Unused parameters:     *****\n";
	param.displayUsage();
    }

    // Check that we do not have any user input
    // that goes outside the coordinates described in
    // the cornerpoint file (runtime-exception will be thrown in case of error)
    // (ilen, jlen and zlen set to zero, does not apply here)
    ch.verifyInscribedShoebox(imin, 0, imax,
			      jmin, 0, jmax,
			      zmin, 0, zmax);


    // Storage for properties for regularized cells
    std::vector<double> poro;
    std::vector<double> permx;
    std::vector<double> permy;
    std::vector<double> permz;


    // Original x/y resolution in terms of coordinate values (not indices)
    Opm::Parser parser;
    auto deck = parser.parseFile(gridfilename); // TODO: REFACTOR!!!! it is stupid to parse this again
    Opm::EclipseGridInspector gridinspector(deck);
    std::array<double, 6> gridlimits=gridinspector.getGridLimits();
    double finegridxresolution = (gridlimits[1]-gridlimits[0])/dims[0];
    double finegridyresolution = (gridlimits[3]-gridlimits[2])/dims[1];

    // Construct mapping from coarse i and j indices to fine
    // and COORDS values for regularized pillars.
    std::vector<int> iidx_f, jidx_f;
    std::vector<double> newcoords_x;
    int finesprcoarse_i = floor(dims[0] / ires);
    int remainder_i = dims[0] - ires*finesprcoarse_i;
    for (int iidx_c=0; iidx_c < remainder_i+1; ++iidx_c) {
        iidx_f.push_back(iidx_c*(finesprcoarse_i + 1));  // Spread remainder evenly
    }
    for (int iidx_c=remainder_i + 1; iidx_c < ires; ++iidx_c) {
        iidx_f.push_back(iidx_c*finesprcoarse_i + remainder_i);
    }
    iidx_f.push_back(imax); // endpoint needed below

    int finesprcoarse_j = floor(dims[1] / jres);
    int remainder_j = dims[1] - jres*finesprcoarse_j;
    for (int jidx_c=0; jidx_c < remainder_j+1; ++jidx_c) {
        jidx_f.push_back(jidx_c*(finesprcoarse_j + 1)); // Spread remainder evenly
    }
    for (int jidx_c=remainder_j + 1; jidx_c < jres; ++jidx_c) {
        jidx_f.push_back(jidx_c*finesprcoarse_j + remainder_j);
    }
    jidx_f.push_back(jmax); // endpoint needed below

    // Construct new ZCORN for regular grid
    std::vector<double> zcorn_c;
    for (int zidx_c=0; zidx_c < zres; ++zidx_c) {
	zcorn_c.push_back(zmin + zidx_c * (zmax-zmin)/zres);
    }
    zcorn_c.push_back(zmax);



    // Run through the new regular grid to find its properties
    for (int zidx_c=0; zidx_c < zres; ++zidx_c) {
        for (int jidx_c=0; jidx_c < jres; ++jidx_c) {
            for (int iidx_c=0; iidx_c < ires; ++iidx_c) {
                ch.chop(iidx_f[iidx_c], iidx_f[iidx_c+1],
                        jidx_f[jidx_c], jidx_f[jidx_c+1],
                        zcorn_c[zidx_c], zcorn_c[zidx_c+1],
                        false);

                OPM_THROW(std::logic_error, "Sub-decks not are not implemented by opm-parser. Refactor the calling code!?");
		try {
		    auto subdeck = ch.subDeck();
                    double minperm = param.getDefault("minperm", 1e-9);
                    double minpermSI = Opm::unit::convert::from(minperm, Opm::prefix::milli*Opm::unit::darcy);
                    double residual_tolerance = param.getDefault("residual_tolerance", 1e-8);
                    double linsolver_verbosity = param.getDefault("linsolver_verbosity", 0);
                    double linsolver_type = param.getDefault("linsolver_type", 1);

		    Opm::SinglePhaseUpscaler upscaler;
		    upscaler.init(subdeck, Opm::SinglePhaseUpscaler::Fixed, minpermSI,
				  residual_tolerance, linsolver_verbosity, linsolver_type, false);

		    Opm::SinglePhaseUpscaler::permtensor_t upscaled_K = upscaler.upscaleSinglePhase();
		    upscaled_K *= (1.0/(Opm::prefix::milli*Opm::unit::darcy));
		    poro.push_back(upscaler.upscalePorosity());
		    permx.push_back(upscaled_K(0,0));
		    permy.push_back(upscaled_K(1,1));
		    permz.push_back(upscaled_K(2,2));
		}
		catch (...) {
		    std::cout << "Warning: Upscaling for cell failed to convert, values set to zero\n";
		    poro.push_back(0.0);
		    permx.push_back(0.0);
		    permy.push_back(0.0);
		    permz.push_back(0.0);
		}
	    }
        }
    }
    // Write regularized grid to outputfile
    std::ofstream out(resultgrid.c_str());
    if (!out) {
        std::cerr << "Could not open file " << resultgrid << "\n";
        throw std::runtime_error("Could not open output file.");
    }
    out << "SPECGRID\n" << ires << ' ' << jres << ' ' << zres
                << " 1 F\n/\n\n";

    out << "COORD\n";
    for (int j = 0; j <= jres; ++j) {
        for (int i = 0; i <= ires; ++i) {
	    out << finegridxresolution*iidx_f[i] << " " << finegridyresolution*jidx_f[j] << " " << zmin << " "
                << finegridxresolution*iidx_f[i] << " " << finegridyresolution*jidx_f[j] << " " << zmax << "\n";
        }
    }
    out << "/\n\n";

    /*
     Write ZCORN, that is the Z-coordinates along the pillars, specifying
     the eight corners of each cell. Each corner is specified for each
     cell, even though it is the same corner that is used in other
     cells.

     We loop over corners in each grid cell, directions: z, y, x (x innermost).
     The code here *IS* redundant, but the grid is also very redundant
     for a grid that is really regular..
   */
    out << "ZCORN\n";
    double zlen = zmax-zmin;
    for (int zidx=0; zidx < zres; ++zidx) {
	for (int j = 0; j < jres; ++j) {
	    for (int i = 0; i < ires; ++i) {
		out << zlen/zres*zidx << "  " << zlen/zres*zidx << "  ";
	    }
	    out << "\n";
	    for (int i = 0; i < ires; ++i) {
		out << zlen/zres*zidx << "  " << zlen/zres*zidx << "  ";
	    }
	}
	for (int j = 0; j < jres; ++j) {
	    for (int i = 0; i < ires; ++i) {
		out << zlen/zres*(zidx+1) << "  " << zlen/zres*(zidx+1) << "  ";
	    }
	    out << "\n";
	    for (int i = 0; i < ires; ++i) {
		out << zlen/zres*(zidx+1) << "  " << zlen/zres*(zidx+1) << "  ";
	    }
	}
    }
    out << "/\n\n";

    out << "PORO\n";
    for (size_t idx=0; idx < (size_t)poro.size(); ++idx) {
	out << poro[idx] << std::endl;
    }
    out << "/\n\n";

    out << "PERMX\n";
    for (size_t idx=0; idx < (size_t)permx.size(); ++idx) {
	out << permx[idx] << std::endl;
    }
    out << "/\n\n";

    out << "PERMY\n\n";
    for (size_t idx=0; idx < (size_t)permy.size(); ++idx) {
	out << permy[idx] << std::endl;
    }
    out << "/\n\n";

    out << "PERMZ\n\n";
    for (size_t idx=0; idx < (size_t)permz.size(); ++idx) {
	out << permz[idx] << std::endl;
    }
    out << "/\n";

    out.close();
}
catch (const std::exception &e) {
    std::cerr << "Program threw an exception: " << e.what() << "\n";
    throw;
}
