/*
 * (C) Copyright 2013 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#include "atlas/trans/local/LegendreCacheCreatorLocal.h"

#include <cmath>
#include <sstream>
#include <string>

#include "eckit/types/FloatCompare.h"
#include "eckit/utils/MD5.h"

#include "atlas/grid.h"
#include "atlas/option.h"
#include "atlas/runtime/Exception.h"
#include "atlas/trans/Trans.h"
#include "atlas/trans/local/TransLocal.h"

namespace atlas {
namespace trans {

namespace {
static LegendreCacheCreatorBuilder<LegendreCacheCreatorLocal> builder("local");
}

namespace {

std::string truncate(const std::string& str) {
    const int trunc = std::min(10ul, str.size());
    return str.substr(0, trunc);
}

std::string hash(const Grid& grid) {
    eckit::MD5 h;

    StructuredGrid structured(grid);
    if (structured && not grid.projection()) {
        for (auto& y : structured.y()) {
            h.add(std::lround(y * 1.e8));
        }
    }
    else {
        grid.hash(h);
    }
    return truncate(h.digest());
}

std::string hash(const eckit::Configuration& config) {
    eckit::MD5 h;

    // Add options and other unique keys
    h << "flt" << config.getBool("flt", false);

    return truncate(h.digest());
}

}  // namespace

std::string LegendreCacheCreatorLocal::uid() const {
    if (unique_identifier_.empty()) {
        std::ostringstream stream;
        auto give_up = [&]() {
            // We cannot make more assumptions on reusability for different grids
            stream << "grid-" << hash(grid_);
        };
        stream << "local-T" << truncation_ << "-";
        StructuredGrid structured(grid_);
        if (grid_.projection()) {
            give_up();
        }
        else if (GaussianGrid(grid_)) {
            // Same cache for any global Gaussian grid
            stream << "GaussianN" << GaussianGrid(grid_).N();
        }
        else if (RegularLonLatGrid(grid_)) {
            // Same cache for any global regular grid
            auto g = RegularLonLatGrid(grid_);

            const double dy_2 = 90. / double(g.ny());
            bool shifted_lat  = eckit::types::is_approximately_equal(g.y().front(), 90. - dy_2) &&
                               eckit::types::is_approximately_equal(g.y().back(), -90. + dy_2);
            bool standard_lat = eckit::types::is_approximately_equal(g.y().front(), 90.) &&
                                eckit::types::is_approximately_equal(g.y().back(), -90.);

            if (standard_lat) {
                stream << "L"
                       << "-ny" << g.ny();
            }
            else if (shifted_lat) {
                stream << "S"
                       << "-ny" << g.ny();
            }
            else {  // I don't think we get here, but just in case, give up
                give_up();
            }
        }
        else if (RegularGrid(grid_) && structured.yspace().type() == "linear") {
            RectangularDomain domain(grid_.domain());
            ATLAS_ASSERT(domain);
            stream << "Regional";
            stream << "-south" << domain.ymin();
            stream << "-north" << domain.ymax();
            stream << "-ny" << structured.ny();
        }
        else {  // It gets too complicated, so let's not be smart
            give_up();
        }
        stream << "-OPT" << hash(config_);
        unique_identifier_ = stream.str();
    }
    return unique_identifier_;
}

LegendreCacheCreatorLocal::~LegendreCacheCreatorLocal() = default;

LegendreCacheCreatorLocal::LegendreCacheCreatorLocal(const Grid& grid, int truncation,
                                                     const eckit::Configuration& config):
    grid_(grid), truncation_(truncation), config_(config) {}

bool LegendreCacheCreatorLocal::supported() const {
    if (not StructuredGrid(grid_)) {
        return false;
    }
    if (grid_.projection()) {
        return false;
    }
    return true;
}

void LegendreCacheCreatorLocal::create(const std::string& path) const {
    Trans tmp(grid_, truncation_, config_ | option::type("local") | option::write_legendre(path));
}

Cache LegendreCacheCreatorLocal::create() const {
    util::Config export_legendre("export_legendre", true);
    Trans tmp(grid_, truncation_, config_ | option::type("local") | export_legendre);
    auto impl = dynamic_cast<const TransLocal*>(tmp.get());
    return impl->export_legendre_;
}

size_t LegendreCacheCreatorLocal::estimate() const {
    return size_t(truncation_ * truncation_ * truncation_) / 2 * sizeof(double);
}


}  // namespace trans
}  // namespace atlas
