/*
 * (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 <cmath>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>


#include "eckit/config/Resource.h"
#include "eckit/filesystem/PathName.h"
#include "eckit/log/Bytes.h"
#include "eckit/log/JSON.h"
#include "eckit/log/Log.h"
#include "eckit/types/FloatCompare.h"

#include "atlas/grid.h"
#include "atlas/grid/detail/grid/GridBuilder.h"
#include "atlas/grid/detail/grid/GridFactory.h"
#include "atlas/projection/detail/ProjectionFactory.h"
#include "atlas/runtime/AtlasTool.h"
#include "atlas/util/NormaliseLongitude.h"

namespace atlas {

template <typename Value>
class FixedFormat {
public:
    using value_type = Value;
    FixedFormat( value_type x, long precision ) : x_( x ), precision_( precision > 0 ? precision : 20 ) {}
    void print( std::ostream& out ) const {
        for ( long precision = 0; precision <= precision_; ++precision ) {
            if ( is_precision( precision ) || precision == precision_ ) {
                out << std::setprecision( precision );
                out << std::fixed << x_;
                break;
            }
        }
    }

    bool is_precision( long precision ) const {
        std::stringstream ss;
        ss << std::setprecision( precision );
        ss << std::fixed << x_;
        value_type _x;
        ss >> _x;
        return std::abs( x_ - _x ) < 1.e-20;
    }

    friend std::ostream& operator<<( std::ostream& out, const FixedFormat& This ) {
        This.print( out );
        return out;
    }

private:
    float x_;
    long precision_;
};

FixedFormat<double> fixed_format( double x, long precision ) {
    return FixedFormat<double>( x, precision );
}
FixedFormat<float> fixed_format( float x, long precision ) {
    return FixedFormat<float>( x, precision );
}

}  // namespace atlas

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

struct AtlasGrids : public atlas::AtlasTool {
    bool serial() override { return true; }
    int execute( const Args& args ) override;
    std::string briefDescription() override { return "Catalogue of available built-in grids"; }
    std::string usage() override { return name() + " <grid> [OPTION]... [--help,-h]"; }
    std::string longDescription() override {
        return "Catalogue of available built-in grids\n"
               "\n"
               "       Browse catalogue of grids\n"
               "\n"
               "       GRID: unique identifier for grid \n"
               "           Example values: N80, F40, O24, L32\n";
    }

    AtlasGrids( int argc, char** argv ) : AtlasTool( argc, argv ) {
        add_option(
            new SimpleOption<bool>( "list", "List all grids. The names are possible values for the <grid> argument" ) );
        add_option( new SimpleOption<bool>( "info", "List information about <grid>" ) );
        add_option( new SimpleOption<bool>( "json", "Export json" ) );
        add_option( new SimpleOption<bool>( "rtable", "Export IFS rtable" ) );
        add_option( new SimpleOption<bool>( "check", "Check grid" ) );
        add_option( new SimpleOption<bool>( "check-uid", "Check grid uid required" ) );
        add_option( new SimpleOption<bool>( "check-boundingbox", "Check grid bounding_box(n,w,s,e) required" ) );
        add_option( new SimpleOption<long>( "precision", "Precision used for float output" ) );
        add_option(
            new SimpleOption<bool>( "approximate-resolution", "Approximate resolution in degrees (North-South)" ) );
    }
};

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

int AtlasGrids::execute( const Args& args ) {
    using namespace atlas;

    std::string key = args.count() ? args( 0 ) : "";

    bool info = false;
    args.get( "info", info );

    bool json = false;
    args.get( "json", json );

    bool rtable = false;
    args.get( "rtable", rtable );

    bool check = false;
    args.get( "check", check );

    bool check_uid = false;
    args.get( "check-uid", check_uid );

    bool check_bbox = false;
    args.get( "check-boundingbox", check_bbox );

    bool list = false;
    args.get( "list", list );

    bool approximate_resolution = false;
    args.get( "approximate-resolution", approximate_resolution );

    bool do_run = list || ( !key.empty() && ( info || json || rtable || check || approximate_resolution ) );

    if ( !key.empty() && !do_run ) {
        Log::error() << "Option wrong or missing after '" << key << "'" << std::endl;
    }

    if ( list ) {
        Log::info() << "usage: atlas-grids <grid> [OPTION]... [--help]\n" << std::endl;
        Log::info() << "\n";
        Log::info() << "Available grid types:" << std::endl;
        for ( auto b : grid::GridBuilder::typeRegistry() ) {
            Log::info() << "  -- " << b.second->type() << '\n';
        }
        Log::info() << "\n";
        Log::info() << "Available named grids:" << std::endl;

        size_t maxlen = 0;
        for ( auto b : grid::GridBuilder::nameRegistry() ) {
            for ( const auto& name : b.second->names() ) {
                maxlen = std::max( maxlen, name.size() );
            }
        }

        for ( auto b : grid::GridBuilder::nameRegistry() ) {
            int c = 0;
            for ( const auto& name : b.second->names() ) {
                if ( c == 0 ) {
                    Log::info() << "  -- " << std::left << std::setw( maxlen + 8 ) << name;
                    if ( !b.second->type().empty() ) {
                        Log::info() << "type: " << b.second->type();
                    }
                    Log::info() << std::endl;
                }
                else {
                    Log::info() << "     " << std::left << std::setw( maxlen + 8 ) << name << std::endl;
                }
                c++;
            }
        }
    }

    if ( !key.empty() ) {
        eckit::PathName path{key};
        Grid grid = path.exists() ? Grid( Grid::Spec{path} ) : Grid( key );

        if ( !grid ) {
            return failed();
        }

        if ( info ) {
            Log::info() << "Grid " << key << std::endl;
            Log::info() << "   name:                               " << grid.name() << std::endl;
            Log::info() << "   uid:                                " << grid.uid() << std::endl;
            if ( auto gaussian = GaussianGrid( grid ) ) {
                Log::info() << "   Gaussian N number:                  " << gaussian.N() << std::endl;
            }
            Log::info() << "   number of points:                   " << grid.size() << std::endl;

            Log::info() << "   memory footprint of grid:           " << eckit::Bytes( grid.footprint() ) << std::endl;


            size_t memsize = grid.size() * sizeof( double );

            Log::info() << "   memory footprint per field (dp):    " << eckit::Bytes( memsize ) << std::endl;

            if ( auto structuredgrid = StructuredGrid( grid ) ) {
                if ( not grid.projection() ) {
                    double deg, km;

                    Log::info() << "   number of latitudes (N-S):          " << structuredgrid.ny() << std::endl;
                    Log::info() << "   number of longitudes (max):         " << structuredgrid.nxmax() << std::endl;

                    deg = ( structuredgrid.y().front() - structuredgrid.y().back() ) / ( structuredgrid.ny() - 1 );
                    km  = deg * 40075. / 360.;
                    Log::info() << "   approximate resolution N-S:         " << std::setw( 10 ) << std::fixed << deg
                                << " deg   " << km << " km " << std::endl;

                    deg = 360. / static_cast<double>( structuredgrid.nx( structuredgrid.ny() / 2 ) );
                    km  = deg * 40075. / 360.;
                    Log::info() << "   approximate resolution E-W equator: " << std::setw( 10 ) << std::fixed << deg
                                << " deg   " << km << " km " << std::endl;

                    deg = 360. * std::cos( structuredgrid.y( structuredgrid.ny() / 4 ) * M_PI / 180. ) /
                          static_cast<double>( structuredgrid.nx( structuredgrid.ny() / 4 ) );
                    km = deg * 40075. / 360.;
                    Log::info() << "   approximate resolution E-W midlat:  " << std::setw( 10 ) << std::fixed << deg
                                << " deg   " << km << " km " << std::endl;

                    deg = 360. * std::cos( structuredgrid.y().front() * M_PI / 180. ) /
                          static_cast<double>( structuredgrid.nx().front() );
                    km = deg * 40075. / 360.;


                    Log::info() << "   approximate resolution E-W pole:    " << std::setw( 10 ) << std::fixed << deg
                                << " deg   " << km << " km " << std::endl;

                    Log::info() << "   spectral truncation -- linear:      " << structuredgrid.ny() - 1 << std::endl;
                    Log::info() << "   spectral truncation -- quadratic:   "
                                << static_cast<int>( std::floor( 2. / 3. * structuredgrid.ny() + 0.5 ) ) - 1
                                << std::endl;
                    Log::info() << "   spectral truncation -- cubic:       "
                                << static_cast<int>( std::floor( 0.5 * structuredgrid.ny() + 0.5 ) ) - 1 << std::endl;
                }

                auto precision = Log::info().precision( 3 );
                if ( grid.projection().units() == "meters" ) {
                    Log::info() << "   x : [ " << std::setw( 10 ) << std::fixed << structuredgrid.xspace().min() / 1000.
                                << " , " << std::setw( 10 ) << std::fixed << structuredgrid.xspace().max() / 1000.
                                << " ] km" << std::endl;
                    Log::info() << "   y : [ " << std::setw( 10 ) << std::fixed << structuredgrid.yspace().min() / 1000.
                                << " , " << std::setw( 10 ) << std::fixed << structuredgrid.yspace().max() / 1000.
                                << " ] km" << std::endl;
                    if ( structuredgrid.xspace().nxmax() == structuredgrid.xspace().nxmin() ) {
                        Log::info() << "   dx : " << structuredgrid.xspace().dx()[0] / 1000. << " km" << std::endl;
                    }
                    Log::info() << "   dy : " << std::abs( structuredgrid.y( 1 ) - structuredgrid.y( 0 ) ) / 1000.
                                << " km" << std::endl;
                    Log::info() << "   lonlat(centre)    : "
                                << grid.projection().lonlat(
                                       {0.5 * ( structuredgrid.xspace().max() + structuredgrid.xspace().min() ),
                                        0.5 * ( structuredgrid.yspace().max() + structuredgrid.yspace().min() )} )
                                << std::endl;
                    Log::info() << "   lonlat(xmin,ymax) : "
                                << grid.projection().lonlat(
                                       {structuredgrid.xspace().min(), structuredgrid.yspace().max()} )
                                << std::endl;
                    Log::info() << "   lonlat(xmin,ymin) : "
                                << grid.projection().lonlat(
                                       {structuredgrid.xspace().min(), structuredgrid.yspace().min()} )
                                << std::endl;
                    Log::info() << "   lonlat(xmax,ymin) : "
                                << grid.projection().lonlat(
                                       {structuredgrid.xspace().max(), structuredgrid.yspace().min()} )
                                << std::endl;
                    Log::info() << "   lonlat(xmax,ymax) : "
                                << grid.projection().lonlat(
                                       {structuredgrid.xspace().max(), structuredgrid.yspace().max()} )
                                << std::endl;
                }
                if ( grid.projection().units() == "degrees" ) {
                    Log::info() << "   x : [ " << std::setw( 10 ) << std::fixed << structuredgrid.xspace().min()
                                << " , " << std::setw( 10 ) << std::fixed << structuredgrid.xspace().max() << " ] deg"
                                << std::endl;
                    Log::info() << "   y : [ " << std::setw( 10 ) << std::fixed << structuredgrid.yspace().min()
                                << " , " << std::setw( 10 ) << std::fixed << structuredgrid.yspace().max() << " ] deg"
                                << std::endl;
                }
                auto it = grid.lonlat().begin();
                Log::info() << "   lonlat(first)     : " << *it << std::endl;
                it += grid.size() - 1;
                Log::info() << "   lonlat(last)      : " << *it << std::endl;

                if ( auto bb = grid.lonlatBoundingBox() ) {
                    Log::info() << "   bounding_box(n,w,s,e) : { " << bb.north() << ", " << bb.west() << ", "
                                << bb.south() << ", " << bb.east() << " }" << std::endl;
                }

                Log::info().precision( precision );
            }
        }

        if ( approximate_resolution ) {
            if ( auto structuredgrid = StructuredGrid( grid ) ) {
                if ( structuredgrid.domain().global() ) {
                    auto deg = ( structuredgrid.y().front() - structuredgrid.y().back() ) / ( structuredgrid.ny() - 1 );

                    long precision = -1;
                    args.get( "precision", precision );
                    Log::info() << fixed_format( deg, precision ) << std::endl;
                }
                else {
                    ATLAS_NOTIMPLEMENTED;
                }
            }
            else {
                ATLAS_NOTIMPLEMENTED;
            }
        }

        if ( json ) {
            std::stringstream stream;
            eckit::JSON js( stream );
            js.precision( 16 );
            js << grid.spec();
            std::cout << stream.str() << std::endl;
        }

        if ( rtable ) {
            if ( auto structuredgrid = StructuredGrid( grid ) ) {
                std::stringstream stream;
                stream << "&NAMRGRI\n";
                for ( idx_t j = 0; j < structuredgrid.ny(); ++j ) {
                    stream << " NRGRI(" << std::setfill( '0' ) << std::setw( 5 ) << 1 + j << ")=" << std::setfill( ' ' )
                           << std::setw( 5 ) << structuredgrid.nx( j ) << ",\n";
                }
                stream << "/" << std::flush;
                std::cout << stream.str() << std::endl;
            }
        }

        if ( check ) {
            bool check_failed = false;
            Log::Channel out;
            out.setStream( Log::error() );

            eckit::PathName path{key};
            if ( not path.exists() ) {
                out << "Check failed:  " << key << " is not a file" << std::endl;
                return failed();
            }

            util::Config config_check;
            if ( not util::Config{path}.get( "check", config_check ) ) {
                out << "Check failed:  no \"check\" section in " << key << std::endl;
                return failed();
            }

            bool strict = config_check.getBool( "strict", true );

            idx_t size;
            if ( config_check.get( "size", size ) ) {
                if ( grid.size() != size ) {
                    out << "Check failed: grid size " << grid.size() << " expected to be " << size << std::endl;
                    check_failed = true;
                }
            }
            else {
                Log::warning() << "Check for size skipped" << std::endl;
            }

            std::string uid;
            if ( config_check.get( "uid", uid ) ) {
                if ( uid == "ignore" ) {
                    out << "WARNING: ignoring uid explicitly" << std::endl;
                }
                else if ( grid.uid() != uid ) {
                    out << "Check failed: grid uid " << grid.uid() << " expected to be " << uid << std::endl;
                    check_failed = true;
                }
            }
            else if ( check_uid && uid.empty() ) {
                out << "Check failed: grid uid " << grid.uid() << " was not encoded in the check" << std::endl;
                check_failed = true;
            }
            else {
                Log::warning() << "Check for uid skipped" << std::endl;
            }


            auto equal       = []( double a, double b ) { return eckit::types::is_approximately_equal( a, b, 5.e-4 ); };
            auto point_equal = [&equal]( const PointLonLat& a, const PointLonLat& b ) -> bool {
                return equal( a.lon(), b.lon() ) && equal( a.lat(), b.lat() );
            };
            auto difference_normalised = []( double a, double ref ) {
                util::NormaliseLongitude normalised{ref - 180};
                return normalised( a ) - ref;
            };
            auto point_equal_normalised = [&]( const PointLonLat& a, const PointLonLat& b ) -> bool {
                return equal( a.lat(), b.lat() ) && equal( difference_normalised( a.lon(), b.lon() ), 0. );
            };
            auto equal_normalised = [&]( double a, double b ) { return equal( difference_normalised( a, b ), 0. ); };

            auto check_lonlat = [&]( const std::string& key, std::function<PointLonLat()>&& get_lonlat ) {
                std::vector<double> lonlat_config;
                if ( config_check.get( key, lonlat_config ) ) {
                    PointLonLat lonlat_check = {lonlat_config.data()};
                    PointLonLat lonlat       = get_lonlat();
                    if ( not point_equal_normalised( lonlat, lonlat_check ) ) {
                        out << std::setprecision( 4 ) << std::fixed << "Check failed: " << key << " " << lonlat
                            << " expected to be " << lonlat_check;
                        out << " ( normalised difference: {"
                            << difference_normalised( lonlat.lon(), lonlat_check.lon() ) << ","
                            << lonlat.lat() - lonlat_check.lat() << "} )" << std::endl;
                        check_failed = true;
                    }
                    else if ( strict && not point_equal( lonlat, lonlat_check ) ) {
                        out << std::setprecision( 4 ) << std::fixed << "Check failed: " << key << " " << lonlat
                            << " expected to be " << lonlat_check;
                        out << " ( normalised difference: {"
                            << difference_normalised( lonlat.lon(), lonlat_check.lon() ) << ","
                            << lonlat.lat() - lonlat_check.lat() << "} )" << std::endl;
                        check_failed = true;
                    }
                }
                else {
                    Log::warning() << "Check for " << key << " skipped" << std::endl;
                }
            };

            check_lonlat( "lonlat(first)", [&]() { return grid.lonlat().front(); } );
            check_lonlat( "lonlat(last)", [&]() { return grid.lonlat().back(); } );

            std::vector<double> bbox;
            if ( config_check.get( "bounding_box(n,w,s,e)", bbox ) && bbox.size() == 4 ) {
                auto bb = grid.lonlatBoundingBox();
                if ( !bb ) {
                    check_failed = true;
                    out << "Check failed: cannot calculate bounding box for " << grid.spec() << std::endl;
                }
                else {
                    bool any_value_failed = false;
                    if ( !equal( bb.north(), bbox[0] ) ) {
                        any_value_failed = true;
                        out << "Check failed: n=" << bb.north() << " expected to be " << bbox[0] << std::endl;
                    }
                    if ( !equal_normalised( bb.west(), bbox[1] ) ) {
                        any_value_failed = true;
                        out << "Check failed: w=" << bb.west() << " expected to be " << bbox[1]
                            << " ( normalised difference : " << difference_normalised( bb.west(), bbox[1] ) << " )"
                            << std::endl;
                    }
                    else if ( strict && not equal( bb.west(), bbox[1] ) ) {
                        out << "Check failed: w=" << bb.west() << " expected to be " << bbox[1]
                            << " ( normalised difference : " << difference_normalised( bb.west(), bbox[1] ) << " )"
                            << std::endl;
                    }
                    if ( !equal( bb.south(), bbox[2] ) ) {
                        any_value_failed = true;
                        out << "Check failed: s=" << bb.south() << " expected to be " << bbox[2] << std::endl;
                    }
                    if ( !equal_normalised( bb.east(), bbox[3] ) ) {
                        any_value_failed = true;
                        out << "Check failed: e=" << bb.east() << " expected to be " << bbox[3]
                            << " ( normalised difference : " << difference_normalised( bb.east(), bbox[3] ) << " )"
                            << std::endl;
                    }
                    else if ( strict && not equal( bb.east(), bbox[3] ) ) {
                        any_value_failed = true;
                        out << "Check failed: e=" << bb.east() << " expected to be " << bbox[3]
                            << " ( normalised difference : " << difference_normalised( bb.east(), bbox[3] ) << " )"
                            << std::endl;
                    }
                    if ( any_value_failed ) {
                        check_failed = true;
                        out << "Check failed: bounding_box(n,w,s,e) [" << bb.north() << ", " << bb.west() << ", "
                            << bb.south() << ", " << bb.east() << "] expected to be [" << bbox[0] << ", " << bbox[1]
                            << ", " << bbox[2] << ", " << bbox[3] << "]" << std::endl;
                    }
                }
            }
            else if ( check_bbox && bbox.size() != 4 ) {
                out << "Check failed: grid bounding_box(n,w,s,e) " << grid.lonlatBoundingBox()
                    << " was not encoded in the check" << std::endl;
                check_failed = true;
            }
            else {
                Log::warning() << "Check for bounding_box(n,w,s,e) skipped" << std::endl;
            }

            auto rel_equal = []( double a, double b ) { return std::abs( ( a - b ) / a ) < 1.e-6; };

            double xmin;
            if ( config_check.get( "xmin", xmin ) ) {
                if ( !rel_equal( RectangularDomain( grid.domain() ).xmin(), xmin ) ) {
                    auto precision = out.precision( 2 );
                    out << "Check failed: grid xmin " << std::fixed << RectangularDomain( grid.domain() ).xmin()
                        << " expected to be " << std::fixed << xmin << std::endl;
                    out.precision( precision );
                    check_failed = true;
                }
            }
            else {
                Log::warning() << "Check for xmin skipped" << std::endl;
            }

            double ymin;
            if ( config_check.get( "ymin", ymin ) ) {
                if ( !rel_equal( RectangularDomain( grid.domain() ).ymin(), ymin ) ) {
                    auto precision = out.precision( 2 );
                    out << "Check failed: grid ymin " << std::fixed << RectangularDomain( grid.domain() ).ymin()
                        << " expected to be " << std::fixed << ymin << std::endl;
                    out.precision( precision );
                    check_failed = true;
                }
            }
            else {
                Log::warning() << "Check for ymin skipped" << std::endl;
            }

            if ( projection::ProjectionFactory::has( "proj" ) ) {
                std::string proj_str;
                if ( config_check.get( "proj", proj_str ) ) {
                    Projection proj( util::Config( "type", "proj" ) | util::Config( "proj", proj_str ) );
                    auto check_proj = [&]( const PointLonLat& lonlat, const PointLonLat& proj_lonlat,
                                           const std::string& point = "" ) {
                        if ( not point_equal( lonlat, proj_lonlat ) ) {
                            if ( point_equal_normalised( lonlat, proj_lonlat ) ) {
                                Log::warning()
                                    << "WARNING: Projection of " << point
                                    << " grid point is different from Proj only due to different normalisation: "
                                    << lonlat << " != " << proj_lonlat << std::endl;
                            }
                            else {
                                Log::info() << "Check failed: Projection of " << point
                                            << " grid point is different from Proj: " << lonlat << " != " << proj_lonlat
                                            << " normalised difference = {" << std::fixed << std::setprecision( 12 )
                                            << difference_normalised( lonlat.lon(), proj_lonlat.lon() ) << ","
                                            << lonlat.lat() - proj_lonlat.lat() << "}" << std::endl;
                                check_failed = true;
                            }
                        }
                    };
                    check_proj( grid.lonlat().front(), proj.lonlat( grid.xy().front() ), "first" );
                    check_proj( grid.lonlat().back(), proj.lonlat( grid.xy().back() ), "last" );
                }
            }

            auto check_roundtrip = [&]( const PointLonLat& point, bool strict = false ) {
                using eckit::types::is_approximately_equal;
                const auto projection       = grid.projection();
                PointLonLat point_roundtrip = projection.lonlat( projection.xy( point ) );

                bool roundtrip = is_approximately_equal( point_roundtrip.lat(), point.lat(), 1.e-6 ) &&
                                 is_approximately_equal( point_roundtrip.lon(), point.lon(), 1.e-6 );
                constexpr util::NormaliseLongitude normalised;
                bool normalised_roundtrip =
                    is_approximately_equal( point_roundtrip.lat(), point.lat(), 1.e-6 ) &&
                    is_approximately_equal( normalised( point_roundtrip.lon() ), normalised( point.lon() ) );

                if ( not normalised_roundtrip ) {
                    check_failed = true;
                    Log::info() << "Check failed: Roundtrip of point " << point
                                << " failed, even with normalisation: " << point_roundtrip << " != " << point
                                << " normalised difference = {" << std::fixed << std::setprecision( 12 )
                                << difference_normalised( point_roundtrip.lon(), point.lon() ) << ","
                                << point_roundtrip.lat() - point.lat() << "}" << std::endl;
                }
                if ( strict && not roundtrip ) {
                    check_failed = true;
                    Log::info() << "Check failed: Roundtrip of point " << point << " failed";
                    if ( normalised_roundtrip ) {
                        Log::info() << " but was OK with normalisation";
                    }
                    Log::info() << ": " << point_roundtrip << " != " << point << " normalised difference = {"
                                << std::fixed << std::setprecision( 12 )
                                << difference_normalised( point_roundtrip.lon(), point.lon() ) << ","
                                << point_roundtrip.lat() - point.lat() << "}" << std::endl;
                }
            };

            std::vector<util::Config> roundtrip;
            if ( config_check.get( "roundtrip", roundtrip ) ) {
                for ( auto& entry : roundtrip ) {
                    std::vector<double> lonlat;
                    entry.get( "lonlat", lonlat );
                    check_roundtrip( {lonlat[0], lonlat[1]}, strict );
                }
            }


            if ( check_failed ) {
                return failed();
            }
            Log::info() << "SUCCESS: All checks passed" << std::endl;
        }
    }
    return success();
}

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

int main( int argc, char** argv ) {
    AtlasGrids tool( argc, argv );
    return tool.start();
}
