/*
 * (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 "Healpix.h"

#include <cmath>
#include <iomanip>
#include <limits>
#include <ostream>

#include "atlas/grid/Spacing.h"
#include "atlas/grid/StructuredGrid.h"
#include "atlas/grid/detail/grid/GridBuilder.h"
#include "atlas/grid/detail/grid/Structured.h"
#include "atlas/grid/detail/spacing/CustomSpacing.h"
#include "atlas/util/Constants.h"

namespace atlas {
namespace grid {
namespace {  // anonymous

//---------------------------------------------------------------------------------------------------------------------


static class HealpixGridBuilder : GridBuilder {
public:
    HealpixGridBuilder() : GridBuilder( "healpix", {"^[Hh]([0-9]+)$"}, {"H<n>"} ) {}

    void print( std::ostream& os ) const override {
        os << std::left << std::setw( 20 ) << "H<n>"
           << "Healpix grid with <n>x<n> points for each of 12 tiles";
    }

    const Grid::Implementation* create( const std::string& name, const Grid::Config& config ) const override {
        int id;
        std::vector<std::string> matches;
        if ( match( name, matches, id ) ) {
            int N = std::stoi( matches[0] );
            return new detail::grid::Healpix( N );
        }
        return nullptr;
    }

    const Grid::Implementation* create( const Grid::Config& config ) const override {
        long N;
        config.get( "N", N );
        return new detail::grid::Healpix( N );
    }

    void force_link() {}

} healpix_builder_;

//---------------------------------------------------------------------------------------------------------------------

}  // namespace

namespace detail {
namespace grid {

Healpix::XSpace healpix_xspace( long N ) {
    std::vector<Spacing> vp( 4 * N - 1 );
    for ( int r = 1; r < N; r++ ) {
        double start      = 180. * M_1_PI * M_PI_4 / r;
        vp[r - 1]         = LinearSpacing( start, start + 360., 4 * r, false );
        vp[4 * N - r - 1] = LinearSpacing( start, start + 360., 4 * r, false );
    }

    double start = 180. * M_1_PI * M_PI_4 / N;
    for ( int r = N; r < 2 * N; r++ ) {
        double r_start    = start * ( 2. - ( r - N + 1 ) % 2 );
        vp[r - 1]         = LinearSpacing( r_start, r_start + 360., 4 * N, false );
        vp[4 * N - r - 1] = LinearSpacing( r_start, r_start + 360., 4 * N, false );
    }
    double r_start = start * ( 1 - ( N % 2 ? 1 : 0 ) );
    vp[2 * N - 1]  = LinearSpacing( r_start, r_start + 360., 4 * N, false );

    return vp;
}

Healpix::YSpace healpix_yspace( long N ) {
    constexpr double rad2deg = util::Constants::radiansToDegrees();
    std::vector<double> y( 4 * N - 1 );
    y[2 * N - 1] = 0.;
    for ( int r = 1; r < N; r++ ) {
        y[r - 1]         = 90. - rad2deg * std::acos( 1. - r * r / ( 3. * N * N ) );
        y[4 * N - 1 - r] = -y[r - 1];
    }
    for ( int r = N; r < 2 * N; r++ ) {
        y[r - 1]         = 90. - rad2deg * std::acos( ( 4. * N - 2. * r ) / ( 3. * N ) );
        y[4 * N - 1 - r] = -y[r - 1];
    }
    return new spacing::CustomSpacing( y.size(), y.data() );
}

Healpix::Healpix( long N ) :
    Structured( "H" + std::to_string( N ), healpix_xspace( N ), healpix_yspace( N ), Projection(), GlobalDomain() ) {}


Healpix::Config Healpix::meshgenerator() const {
    return util::Config( "type", "healpix" );
}
Healpix::Config Healpix::partitioner() const {
    return util::Config( "type", "equal_regions" );
}

}  // namespace grid
}  // namespace detail
}  // namespace grid
}  // namespace atlas
