/*
 * (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 <algorithm>
#include <cmath>
#include <iomanip>
#include <limits>
#include <memory>
#include <numeric>
#include <vector>

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

#include "atlas/array.h"
#include "atlas/array/ArrayView.h"
#include "atlas/array/MakeView.h"
#include "atlas/field/Field.h"
#include "atlas/grid/Distribution.h"
#include "atlas/grid/Partitioner.h"
#include "atlas/grid/StructuredGrid.h"
#include "atlas/library/config.h"
#include "atlas/mesh/ElementType.h"
#include "atlas/mesh/Elements.h"
#include "atlas/mesh/HybridElements.h"
#include "atlas/mesh/Mesh.h"
#include "atlas/mesh/Nodes.h"
#include "atlas/meshgenerator/detail/MeshGeneratorFactory.h"
#include "atlas/meshgenerator/detail/StructuredMeshGenerator.h"
#include "atlas/parallel/mpi/mpi.h"
#include "atlas/runtime/Log.h"
#include "atlas/runtime/Trace.h"
#include "atlas/util/CoordinateEnums.h"

#define DEBUG_OUTPUT 0

using namespace atlas::array;
using atlas::Mesh;
using Topology = atlas::mesh::Nodes::Topology;

namespace atlas {
namespace meshgenerator {

namespace {
static double to_rad = M_PI / 180.;
static double to_deg = 180. * M_1_PI;
}  // namespace

struct Region {
    int north;
    int south;
    std::unique_ptr<array::Array> elems;
    int ntriags;
    int nquads;
    int nnodes;
    std::vector<idx_t> lat_begin;
    std::vector<idx_t> lat_end;
    std::vector<idx_t> nb_lat_elems;
};

StructuredMeshGenerator::StructuredMeshGenerator( const eckit::Parametrisation& p ) {
    configure_defaults();

    bool include_pole;
    if ( p.get( "include_pole", include_pole ) ) {
        options.set( "include_pole", include_pole );
    }

    bool patch_pole;
    if ( p.get( "patch_pole", patch_pole ) ) {
        options.set( "patch_pole", patch_pole );
    }

    bool patch_quads;
    if ( p.get( "patch_quads", patch_quads ) ) {
        options.set( "patch_quads", patch_quads );
    }

    bool unique_pole;
    if ( p.get( "unique_pole", unique_pole ) ) {
        options.set( "unique_pole", unique_pole );
    }

    bool force_include_pole;
    if ( p.get( "force_include_north_pole", force_include_pole ) ) {
        options.set( "force_include_north_pole", force_include_pole );
    }
    if ( p.get( "force_include_south_pole", force_include_pole ) ) {
        options.set( "force_include_south_pole", force_include_pole );
    }

    bool three_dimensional;
    if ( p.get( "three_dimensional", three_dimensional ) || p.get( "3d", three_dimensional ) ) {
        options.set( "3d", three_dimensional );
    }

    size_t nb_parts;
    if ( p.get( "nb_parts", nb_parts ) ) {
        options.set( "nb_parts", nb_parts );
    }

    size_t part;
    if ( p.get( "part", part ) ) {
        options.set( "part", part );
    }

    double angle;
    if ( p.get( "angle", angle ) ) {
        options.set( "angle", angle );
    }

    bool triangulate;
    if ( p.get( "triangulate", triangulate ) ) {
        options.set( "triangulate", triangulate );
    }

    bool ghost_at_end;
    if ( p.get( "ghost_at_end", ghost_at_end ) ) {
        options.set( "ghost_at_end", ghost_at_end );
    }

    std::string partitioner;
    if ( grid::Partitioner::exists( "trans" ) ) {
        partitioner = "trans";
    }
    else {
        partitioner = "equal_regions";
    }
    options.set( "partitioner", partitioner );

    if ( p.get( "partitioner", partitioner ) ) {
        if ( not grid::Partitioner::exists( partitioner ) ) {
            Log::warning() << "Atlas does not have support for partitioner " << partitioner << ". "
                           << "Defaulting to use partitioner EqualRegions" << std::endl;
            partitioner = "equal_regions";
        }
        options.set( "partitioner", partitioner );
    }
}

void StructuredMeshGenerator::configure_defaults() {
    // This option creates a point at the pole when true
    options.set( "include_pole", false );

    // This option sets the part that will be generated
    options.set( "patch_pole", true );

    // This option disregards multiple poles in grid (e.g. lonlat up to poles) and
    // connects elements
    // to the first node only. Note this option will only be looked at in case
    // other option
    // "3d"==true
    options.set( "unique_pole", true );

    // This option creates elements that connect east to west at greenwich
    // meridian
    // when true, instead of creating periodic ghost-points at east boundary when
    // false
    options.set( "3d", false );

    // This option sets number of parts the mesh will be split in
    options.set( "nb_parts", mpi::size() );

    // This option sets the part that will be generated
    options.set( "part", mpi::rank() );

    // Experimental option. This is what makes tripolar grid patched with quads
    options.set( "patch_quads", false );

    // This option sets the maximum angle deviation for a quadrilateral element
    // angle = 30  -->  minimises number of triangles
    // angle = 0   -->  maximises number of triangles
    options.set( "angle", 0. );

    options.set( "triangulate", false );

    options.set( "ghost_at_end", true );
}

void StructuredMeshGenerator::generate( const Grid& grid, Mesh& mesh ) const {
    ATLAS_ASSERT( !mesh.generated() );

    const StructuredGrid rg = StructuredGrid( grid );
    if ( !rg ) {
        throw_Exception( "Structured can only work with a Structured grid", Here() );
    }

    idx_t nb_parts = options.getInt( "nb_parts" );

    std::string partitioner_type = "equal_regions";
    options.get( "partitioner", partitioner_type );

    if ( partitioner_type == "trans" ) {
        if ( rg.ny() % 2 == 1 ) {
            partitioner_type = "equal_regions";  // Odd number of latitudes
        }
        if ( nb_parts != mpi::size() ) {
            partitioner_type = "equal_regions";
        }
    }
    if ( nb_parts == 1 ) {
        partitioner_type = "serial";
    }

    grid::Partitioner partitioner( partitioner_type, nb_parts );
    grid::Distribution distribution( partitioner.partition( grid ) );
    generate( grid, distribution, mesh );
}

void StructuredMeshGenerator::hash( eckit::Hash& h ) const {
    h.add( "Structured" );
    options.hash( h );
}

void StructuredMeshGenerator::generate( const Grid& grid, const grid::Distribution& distribution, Mesh& mesh ) const {
    ATLAS_TRACE();

    const StructuredGrid rg = StructuredGrid( grid );
    if ( !rg ) {
        throw_Exception( "Grid could not be cast to a Structured", Here() );
    }

    ATLAS_ASSERT( !mesh.generated() );

    if ( grid.size() != idx_t( distribution.size() ) ) {
        std::stringstream msg;
        msg << "Number of points in grid (" << grid.size()
            << ") different from "
               "number of points in grid distribution ("
            << distribution.size() << ")";
        throw_AssertionFailed( msg.str(), Here() );
    }

    idx_t mypart = options.getInt( "part" );

// show distribution
#if DEBUG_OUTPUT
    int inode                       = 0;
    const atlas::vector<int>& parts = distribution;
    Log::info() << "Partition : " << std::endl;
    for ( size_t ilat = 0; ilat < rg.ny(); ilat++ ) {
        for ( size_t ilon = 0; ilon < rg.nx( ilat ); ilon++ ) {
            Log::info() << std::setw( 3 ) << distribution.partition( inode );
            inode++;
        }
        Log::info() << std::endl;
    }
#endif

    // clone some grid properties
    setGrid( mesh, rg, distribution );

    Region region;
    generate_region( rg, distribution, mypart, region );

    generate_mesh( rg, distribution, region, mesh );
}

void StructuredMeshGenerator::generate_region( const StructuredGrid& rg, const grid::Distribution& distribution,
                                               int mypart, Region& region ) const {
    ATLAS_TRACE();

    double max_angle        = options.getDouble( "angle" );
    bool triangulate_quads  = options.getBool( "triangulate" );
    bool three_dimensional  = options.getBool( "3d" );
    bool has_north_pole     = eckit::types::is_approximately_equal( rg.y().front(), 90. );
    bool has_south_pole     = eckit::types::is_approximately_equal( rg.y().back(), -90. );
    bool unique_pole        = options.getBool( "unique_pole" ) && three_dimensional && has_north_pole && has_south_pole;
    bool periodic_east_west = rg.periodic();

    int n;
    /*
Find min and max latitudes used by this part.
*/
    n               = 0;
    idx_t lat_north = -1;
    for ( idx_t jlat = 0; jlat < rg.ny(); ++jlat ) {
        for ( idx_t jlon = 0; jlon < rg.nx( jlat ); ++jlon ) {
            if ( distribution.partition( n ) == mypart ) {
                lat_north = jlat;
                goto end_north;
            }
            ++n;
        }
    }
end_north:

    n               = rg.size() - 1;
    idx_t lat_south = -1;
    for ( idx_t jlat = rg.ny() - 1; jlat >= 0; --jlat ) {
        for ( idx_t jlon = rg.nx( jlat ) - 1; jlon >= 0; --jlon ) {
            if ( distribution.partition( n ) == mypart ) {
                lat_south = jlat;
                goto end_south;
            }
            --n;
        }
    }
end_south:

    std::vector<idx_t> offset( rg.ny(), 0 );

    n = 0;
    for ( idx_t jlat = 0; jlat < rg.ny(); ++jlat ) {
        offset.at( jlat ) = n;
        n += rg.nx( jlat );
    }

    /*
We need to connect to next region
*/
    if ( lat_north - 1 >= 0 && rg.nx( lat_north - 1 ) > 0 ) {
        --lat_north;
    }
    if ( idx_t( lat_south + 1 ) <= rg.ny() - 1 && rg.nx( lat_south + 1 ) > 0 ) {
        ++lat_south;
    }
    region.lat_begin.resize( rg.ny(), -1 );
    region.lat_end.resize( rg.ny(), -1 );
    region.nb_lat_elems.resize( rg.ny(), 0 );
    region.north = lat_north;
    region.south = lat_south;

    array::ArrayShape shape = array::make_shape( region.south - region.north, 4 * rg.nxmax(), 4 );

    region.elems.reset( array::Array::create<int>( shape ) );

    int nelems     = 0;
    region.nquads  = 0;
    region.ntriags = 0;

    array::ArrayView<int, 3> elemview = array::make_view<int, 3>( *region.elems );
    elemview.assign( -1 );

    for ( idx_t jlat = lat_north; jlat < lat_south; ++jlat ) {
        //    std::stringstream filename; filename << "/tmp/debug/"<<jlat;

        idx_t ilat, latN, latS;
        idx_t ipN1, ipN2, ipS1, ipS2;
        double xN1, xN2, yN, xS1, xS2, yS;
        double dN1S2, dS1N2;  // dN2S2;
        bool try_make_triangle_up, try_make_triangle_down, try_make_quad;
        bool add_triag, add_quad;

        ilat = jlat - region.north;

        auto lat_elems_view = elemview.slice( ilat, Range::all(), Range::all() );

        latN = jlat;
        latS = jlat + 1;
        yN   = rg.y( latN );
        yS   = rg.y( latS );

        idx_t beginN, beginS, endN, endS;

        beginN = 0;
        endN   = rg.nx( latN ) - ( periodic_east_west ? 0 : 1 );
        if ( eckit::types::is_approximately_equal( yN, 90. ) && unique_pole ) {
            endN = beginN;
        }

        beginS = 0;
        endS   = rg.nx( latS ) - ( periodic_east_west ? 0 : 1 );
        if ( eckit::types::is_approximately_equal( yS, -90. ) && unique_pole ) {
            endS = beginS;
        }

        ipN1 = beginN;
        ipS1 = beginS;
        ipN2 = std::min( ipN1 + 1, endN );
        ipS2 = std::min( ipS1 + 1, endS );

        idx_t jelem = 0;
        int pE      = distribution.partition( offset.at( latN ) );

#if DEBUG_OUTPUT
        Log::info() << "=================\n";
        Log::info() << "latN, latS : " << latN << ", " << latS << '\n';
#endif

        while ( true ) {
            if ( ipN1 == endN && ipS1 == endS ) {
                break;
            }

#if DEBUG_OUTPUT
            Log::info() << "-------\n";
#endif

            // ATLAS_ASSERT(offset.at(latN)+ipN1 < parts.size());
            // ATLAS_ASSERT(offset.at(latS)+ipS1 < parts.size());

            int pN1, pS1, pN2, pS2;
            if ( ipN1 != rg.nx( latN ) ) {
                pN1 = distribution.partition( offset.at( latN ) + ipN1 );
            }
            else {
                pN1 = distribution.partition( offset.at( latN ) );
            }
            if ( ipS1 != rg.nx( latS ) ) {
                pS1 = distribution.partition( offset.at( latS ) + ipS1 );
            }
            else {
                pS1 = distribution.partition( offset.at( latS ) );
            }

            if ( ipN2 == rg.nx( latN ) ) {
                pN2 = distribution.partition( offset.at( latN ) );
            }
            else {
                pN2 = distribution.partition( offset.at( latN ) + ipN2 );
            }
            if ( ipS2 == rg.nx( latS ) ) {
                pS2 = distribution.partition( offset.at( latS ) );
            }
            else {
                pS2 = distribution.partition( offset.at( latS ) + ipS2 );
            }

            // Log::info()  << ipN1 << "("<<pN1<<") " << ipN2 <<"("<<pN2<<")" <<  std::endl;
            // Log::info()  << ipS1 << "("<<pS2<<") " << ipS2 <<"("<<pS2<<")" <<  std::endl;

#if DEBUG_OUTPUT
            Log::info() << ipN1 << "(" << pN1 << ") " << ipN2 << "(" << pN2 << ")" << std::endl;
            Log::info() << ipS1 << "(" << pS2 << ") " << ipS2 << "(" << pS2 << ")" << std::endl;
#endif

            xN1 = rg.x( ipN1, latN ) * to_rad;
            xN2 = rg.x( ipN2, latN ) * to_rad;
            xS1 = rg.x( ipS1, latS ) * to_rad;
            xS2 = rg.x( ipS2, latS ) * to_rad;

#if DEBUG_OUTPUT
            Log::info() << "-------\n";
#endif
            // Log::info()  << "  access  " <<
            // region.elems.stride(0)*(jlat-region.north) +
            // region.elems.stride(1)*jelem + 5 << std::endl;
            //      Log::info()  << ipN1 << "("<< xN1 << ")  " << ipN2 <<  "("<< xN2
            //      << ")  " << std::endl;
            //      Log::info()  << ipS1 << "("<< xS1 << ")  " << ipS2 <<  "("<< xS2
            //      << ")  " << std::endl;
            try_make_triangle_up   = false;
            try_make_triangle_down = false;
            try_make_quad          = false;

            // ------------------------------------------------
            // START RULES
            // ------------------------------------------------

            const double dxN    = std::abs( xN2 - xN1 );
            const double dxS    = std::abs( xS2 - xS1 );
            const double dx     = std::min( dxN, dxS );
            const double alpha1 = ( dx == 0. ? 0. : std::atan2( ( xN1 - xS1 ), dx ) * to_deg );
            const double alpha2 = ( dx == 0. ? 0. : std::atan2( ( xN2 - xS2 ), dx ) * to_deg );
            if ( std::abs( alpha1 ) <= max_angle && std::abs( alpha2 ) <= max_angle ) {
                if ( triangulate_quads ) {
                    if ( false )  // std::abs(alpha1) < 1 && std::abs(alpha2) < 1)
                    {
                        try_make_triangle_up   = ( jlat + ipN1 ) % 2;
                        try_make_triangle_down = ( jlat + ipN1 + 1 ) % 2;
                    }
                    else {
                        dN1S2 = std::abs( xN1 - xS2 );
                        dS1N2 = std::abs( xS1 - xN2 );
                        // dN2S2 = std::abs(xN2-xS2);
                        // Log::info()  << "  dN1S2 " << dN1S2 << "   dS1N2 " << dS1N2 << "
                        // dN2S2 " << dN2S2 << std::endl;
                        if ( dN1S2 == dS1N2 ) {
                            try_make_triangle_up   = ( jlat + ipN1 ) % 2;
                            try_make_triangle_down = ( jlat + ipN1 + 1 ) % 2;
                        }
                        else if ( dN1S2 < dS1N2 ) {
                            if ( ipS1 != ipS2 ) {
                                try_make_triangle_up = true;
                            }
                            else {
                                try_make_triangle_down = true;
                            }
                        }
                        else if ( dN1S2 > dS1N2 ) {
                            if ( ipN1 != ipN2 ) {
                                try_make_triangle_down = true;
                            }
                            else {
                                try_make_triangle_up = true;
                            }
                        }
                        else {
                            throw_Exception( "Should not be here", Here() );
                        }
                    }
                }
                else {
                    if ( ipN1 == ipN2 ) {
                        try_make_triangle_up = true;
                    }
                    else if ( ipS1 == ipS2 ) {
                        try_make_triangle_down = true;
                    }
                    else {
                        try_make_quad = true;
                    }

                    //          try_make_quad          = true;
                }
            }
            else {
                dN1S2 = std::abs( xN1 - xS2 );
                dS1N2 = std::abs( xS1 - xN2 );
                // dN2S2 = std::abs(xN2-xS2);
                // Log::info()  << "  dN1S2 " << dN1S2 << "   dS1N2 " << dS1N2 << "
                // dN2S2 " << dN2S2 << std::endl;
                if ( ( dN1S2 <= dS1N2 ) && ( ipS1 != ipS2 ) ) {
                    try_make_triangle_up = true;
                }
                else if ( ( dN1S2 >= dS1N2 ) && ( ipN1 != ipN2 ) ) {
                    try_make_triangle_down = true;
                }
                else {
                    if ( ipN1 == ipN2 ) {
                        try_make_triangle_up = true;
                    }
                    else if ( ipS1 == ipS2 ) {
                        try_make_triangle_down = true;
                    }
                    else {
                        ATLAS_DEBUG_VAR( dN1S2 );
                        ATLAS_DEBUG_VAR( dS1N2 );
                        ATLAS_DEBUG_VAR( jlat );
                        Log::info() << ipN1 << "(" << xN1 << ")  " << ipN2 << "(" << xN2 << ")  " << std::endl;
                        Log::info() << ipS1 << "(" << xS1 << ")  " << ipS2 << "(" << xS2 << ")  " << std::endl;
                        throw_Exception( "Should not try to make a quadrilateral!", Here() );
                    }
                }
            }
            // ------------------------------------------------
            // END RULES
            // ------------------------------------------------

#if DEBUG_OUTPUT
            ATLAS_DEBUG_VAR( jelem );
#endif

            auto elem = lat_elems_view.slice( jelem, Range::all() );

            if ( try_make_quad ) {
// add quadrilateral
#if DEBUG_OUTPUT
                Log::info() << "          " << ipN1 << "  " << ipN2 << '\n';
                Log::info() << "          " << ipS1 << "  " << ipS2 << '\n';
#endif
                elem( 0 ) = ipN1;
                elem( 1 ) = ipS1;
                elem( 2 ) = ipS2;
                elem( 3 ) = ipN2;
                add_quad  = false;
                std::array<int, 4> np{pN1, pN2, pS1, pS2};
                std::array<int, 4> pcnts;
                for ( int j = 0; j < 4; ++j ) {
                    pcnts[j] = static_cast<int>( std::count( np.begin(), np.end(), np[j] ) );
                }
                if ( pcnts[0] > 2 ) {  // 3 or more of pN1
                    pE = pN1;
                    if ( latS == rg.ny() - 1 ) {
                        pE = pS1;
                    }
                }
                else if ( pcnts[2] > 2 ) {  // 3 or more of pS1
                    pE = pS1;
                    if ( latN == 0 ) {
                        pE = pN1;
                    }
                }
                else {
                    std::array<int, 4>::iterator p_max = std::max_element( pcnts.begin(), pcnts.end() );
                    if ( *p_max > 2 ) {  // 3 or 4 points belong to same part
                        pE = np[std::distance( np.begin(), p_max )];
                    }
                    else {  // 3 or 4 points don't belong to mypart
                        pE = pN1;
                        if ( latS == rg.ny() - 1 ) {
                            pE = pS1;
                        }
                    }
                }
                add_quad = ( pE == mypart );
                if ( add_quad ) {
                    ++region.nquads;
                    ++jelem;
                    ++nelems;

                    if ( region.lat_begin.at( latN ) == -1 ) {
                        region.lat_begin.at( latN ) = ipN1;
                    }
                    if ( region.lat_begin.at( latS ) == -1 ) {
                        region.lat_begin.at( latS ) = ipS1;
                    }
                    region.lat_begin.at( latN ) = std::min<int>( region.lat_begin.at( latN ), ipN1 );
                    region.lat_begin.at( latS ) = std::min<int>( region.lat_begin.at( latS ), ipS1 );
                    region.lat_end.at( latN )   = std::max<int>( region.lat_end.at( latN ), ipN2 );
                    region.lat_end.at( latS )   = std::max<int>( region.lat_end.at( latS ), ipS2 );
                }
                else {
#if DEBUG_OUTPUT
                    Log::info() << "Quad belongs to other partition" << std::endl;
#endif
                }
                ipN1 = ipN2;
                ipS1 = ipS2;
            }
            else if ( try_make_triangle_down )  // make triangle down
            {
// triangle without ip3
#if DEBUG_OUTPUT
                Log::info() << "          " << ipN1 << "  " << ipN2 << '\n';
                Log::info() << "          " << ipS1 << '\n';
#endif
                elem( 0 ) = ipN1;
                elem( 1 ) = ipS1;
                elem( 2 ) = -1;
                elem( 3 ) = ipN2;

                pE = pN1;
                if ( latS == rg.ny() - 1 ) {
                    pE = pS1;
                }
                add_triag = ( mypart == pE );

                if ( add_triag ) {
                    ++region.ntriags;
                    ++jelem;
                    ++nelems;

                    if ( region.lat_begin.at( latN ) == -1 ) {
                        region.lat_begin.at( latN ) = ipN1;
                    }
                    if ( region.lat_begin.at( latS ) == -1 ) {
                        region.lat_begin.at( latS ) = ipS1;
                    }
                    region.lat_begin.at( latN ) = std::min<int>( region.lat_begin.at( latN ), ipN1 );
                    region.lat_begin.at( latS ) = std::min<int>( region.lat_begin.at( latS ), ipS1 );
                    region.lat_end.at( latN )   = std::max<int>( region.lat_end.at( latN ), ipN2 );
                    region.lat_end.at( latS )   = std::max<int>( region.lat_end.at( latS ), ipS1 );
                }
                else {
#if DEBUG_OUTPUT
                    Log::info() << "Downward Triag belongs to other partition" << std::endl;
#endif
                }
                ipN1 = ipN2;
                // and ipS1=ipS1;
            }
            else if ( try_make_triangle_up )  // make triangle up
            {
// triangle without ip4
#if DEBUG_OUTPUT
                Log::info() << "          " << ipN1 << " (" << pN1 << ")" << '\n';
                Log::info() << "          " << ipS1 << " (" << pS1 << ")"
                            << "  " << ipS2 << " (" << pS2 << ")" << '\n';
#endif
                elem( 0 ) = ipN1;
                elem( 1 ) = ipS1;
                elem( 2 ) = ipS2;
                elem( 3 ) = -1;

                if ( pS1 == pE && pN1 != pE ) {
                    if ( xN1 < 0.5 * ( xS1 + xS2 ) ) {
                        pE = pN1;
                    }  // else pE of previous element
                }
                else {
                    pE = pN1;
                }
                if ( ipN1 == rg.nx( latN ) ) {
                    pE = pS1;
                }
                if ( latS == rg.ny() - 1 ) {
                    pE = pS1;
                }

                add_triag = ( mypart == pE );

                if ( add_triag ) {
                    ++region.ntriags;
                    ++jelem;
                    ++nelems;

                    if ( region.lat_begin.at( latN ) == -1 ) {
                        region.lat_begin.at( latN ) = ipN1;
                    }
                    if ( region.lat_begin.at( latS ) == -1 ) {
                        region.lat_begin.at( latS ) = ipS1;
                    }
                    region.lat_begin.at( latN ) = std::min( region.lat_begin.at( latN ), ipN1 );
                    region.lat_begin.at( latS ) = std::min( region.lat_begin.at( latS ), ipS1 );
                    region.lat_end.at( latN )   = std::max( region.lat_end.at( latN ), ipN1 );
                    region.lat_end.at( latS )   = std::max( region.lat_end.at( latS ), ipS2 );
                }
                else {
#if DEBUG_OUTPUT
                    Log::info() << "Upward Triag belongs to other partition" << std::endl;
#endif
                }
                ipS1 = ipS2;
                // and ipN1=ipN1;
            }
            else {
                throw_Exception( "Could not detect which element to create", Here() );
            }
            ipN2 = std::min( endN, ipN1 + 1 );
            ipS2 = std::min( endS, ipS1 + 1 );
        }
        region.nb_lat_elems.at( jlat ) = jelem;
#if DEBUG_OUTPUT
        ATLAS_DEBUG_VAR( region.nb_lat_elems.at( jlat ) );
#endif
        if ( region.nb_lat_elems.at( jlat ) == 0 && latN == region.north ) {
            ++region.north;
        }
        if ( region.nb_lat_elems.at( jlat ) == 0 && latS == region.south ) {
            --region.south;
        }
        //    region.lat_end.at(latN) = std::min(region.lat_end.at(latN),
        //    int(rg.nx(latN)-1));
        //    region.lat_end.at(latS) = std::min(region.lat_end.at(latS),
        //    int(rg.nx(latS)-1));
        if ( yN == 90 && unique_pole ) {
            region.lat_end.at( latN ) = rg.nx( latN ) - 1;
        }
        if ( yS == -90 && unique_pole ) {
            region.lat_end.at( latS ) = rg.nx( latS ) - 1;
        }

        if ( region.nb_lat_elems.at( jlat ) > 0 ) {
            region.lat_end.at( latN ) = std::max( region.lat_end.at( latN ), region.lat_begin.at( latN ) );
            region.lat_end.at( latS ) = std::max( region.lat_end.at( latS ), region.lat_begin.at( latS ) );
        }
    }  // for jlat

    //  Log::info()  << "nb_triags = " << region.ntriags << std::endl;
    //  Log::info()  << "nb_quads = " << region.nquads << std::endl;
    //  Log::info()  << "nb_elems = " << nelems << std::endl;

    int nb_region_nodes = 0;
    for ( int jlat = region.north; jlat <= region.south; ++jlat ) {
        n                           = offset.at( jlat );
        region.lat_begin.at( jlat ) = std::max<idx_t>( 0, region.lat_begin.at( jlat ) );
        for ( idx_t jlon = 0; jlon < rg.nx( jlat ); ++jlon ) {
            if ( distribution.partition( n ) == mypart ) {
                region.lat_begin.at( jlat ) = std::min( region.lat_begin.at( jlat ), jlon );
                region.lat_end.at( jlat )   = std::max( region.lat_end.at( jlat ), jlon );
            }
            ++n;
        }
        nb_region_nodes += region.lat_end.at( jlat ) - region.lat_begin.at( jlat ) + 1;

        // Count extra periodic node
        // if( periodic_east_west && size_t(region.lat_end.at(jlat)) == rg.nx(jlat)
        // - 1) ++nb_region_nodes;
    }

    region.nnodes = nb_region_nodes;
    if ( region.nnodes == 0 ) {
        throw_Exception(
            "Trying to generate mesh with too many partitions. Reduce "
            "the number of partitions.",
            Here() );
    }
#if DEBUG_OUTPUT
    ATLAS_DEBUG( "End of generate_region()" );
#endif
}

namespace {
struct GhostNode {
    GhostNode( int _jlat, int _jlon, int _jnode ) {
        jlat  = _jlat;
        jlon  = _jlon;
        jnode = _jnode;
    }
    int jlat;
    int jlon;
    int jnode;
};
}  // namespace

void StructuredMeshGenerator::generate_mesh( const StructuredGrid& rg, const grid::Distribution& distribution,
                                             const Region& region, Mesh& mesh ) const {
    ATLAS_TRACE();

    ATLAS_ASSERT( !mesh.generated() );

    int mypart = options.getInt( "part" );
    int nparts = options.getInt( "nb_parts" );
    int n, l;
    const int y_numbering = ( rg.y().front() < rg.y().back() ) ? +1 : -1;
    double y_north        = y_numbering < 0 ? rg.y().front() : rg.y().back();
    double y_south        = y_numbering < 0 ? rg.y().back() : rg.y().front();
    idx_t nx_north        = y_numbering < 0 ? rg.nx().front() : rg.nx().back();
    idx_t nx_south        = y_numbering < 0 ? rg.nx().back() : rg.nx().front();

    std::vector<int> part_north( nx_north );
    std::vector<int> part_south( nx_south );
    if ( y_numbering < 0 ) {
        distribution.partition( 0, nx_north, part_north );
        distribution.partition( rg.size() - nx_south, rg.size(), part_south );
    }
    else {
        distribution.partition( 0, nx_south, part_south );
        distribution.partition( rg.size() - nx_north, rg.size(), part_north );
    }
    bool mypart_at_north = std::any_of( part_north.begin(), part_north.end(), [&]( int p ) { return p == mypart; } );
    bool mypart_at_south = std::any_of( part_south.begin(), part_south.end(), [&]( int p ) { return p == mypart; } );

    bool three_dimensional             = options.getBool( "3d" );
    bool periodic_east_west            = rg.periodic();
    bool include_periodic_ghost_points = periodic_east_west && !three_dimensional;
    bool remove_periodic_ghost_points  = periodic_east_west && three_dimensional;

    bool has_point_at_north_pole = y_north == 90 && nx_north > 0;
    bool has_point_at_south_pole = y_south == -90 && nx_south > 0;
    bool possible_north_pole     = !has_point_at_north_pole && rg.domain().containsNorthPole() && mypart_at_north;
    bool possible_south_pole     = !has_point_at_south_pole && rg.domain().containsSouthPole() && mypart_at_south;

    bool force_include_north_pole( options.has( "force_include_north_pole" ) &&
                                   options.getBool( "force_include_north_pole" ) );
    bool force_include_south_pole( options.has( "force_include_south_pole" ) &&
                                   options.getBool( "force_include_south_pole" ) );

    bool include_north_pole = ( possible_north_pole && options.getBool( "include_pole" ) ) || force_include_north_pole;
    bool include_south_pole = ( possible_south_pole && options.getBool( "include_pole" ) ) || force_include_south_pole;
    bool patch_north_pole   = possible_north_pole && options.getBool( "patch_pole" ) && nx_north > 0;
    bool patch_south_pole   = possible_south_pole && options.getBool( "patch_pole" ) && nx_south > 0;
    bool patch_quads        = options.getBool( "patch_quads" );

    int nnewnodes = ( !has_point_at_north_pole && include_north_pole ? 1 : 0 ) +
                    ( !has_point_at_south_pole && include_south_pole ? 1 : 0 );

    if ( three_dimensional && nparts != 1 ) {
        throw_Exception( "Cannot generate three_dimensional mesh in parallel", Here() );
    }
    int nnodes  = region.nnodes;
    int ntriags = region.ntriags;
    int nquads  = region.nquads;

    if ( include_north_pole ) {
        ++nnodes;
        ntriags += nx_north - ( periodic_east_west ? 0 : 1 );
    }
    else if ( patch_north_pole ) {
        if ( patch_quads ) {
            nquads += ( nx_north + 1 ) / 2;
        }
        else {
            ntriags += nx_north - 2;
        }
    }
    if ( include_south_pole ) {
        ++nnodes;
        ntriags += nx_south - ( periodic_east_west ? 0 : 1 );
    }
    else if ( patch_south_pole ) {
        if ( patch_quads ) {
            nquads += ( nx_south + 1 ) / 2;
        }
        else {
            ntriags += nx_south - 2;
        }
    }

    size_t node_numbering_size = nnodes;
    if ( remove_periodic_ghost_points ) {
        for ( idx_t jlat = 0; jlat < rg.ny(); ++jlat ) {
            if ( region.lat_end[jlat] >= rg.nx( jlat ) ) {
                --nnodes;
            }
        }
    }
    ATLAS_ASSERT( nnodes >= nnewnodes );

#if DEBUG_OUTPUT
    ATLAS_DEBUG_VAR( include_periodic_ghost_points );
    ATLAS_DEBUG_VAR( include_north_pole );
    ATLAS_DEBUG_VAR( include_south_pole );
    ATLAS_DEBUG_VAR( three_dimensional );
    ATLAS_DEBUG_VAR( patch_north_pole );
    ATLAS_DEBUG_VAR( patch_south_pole );
    ATLAS_DEBUG_VAR( rg.size() );
    ATLAS_DEBUG_VAR( nnodes );
    ATLAS_DEBUG_VAR( ntriags );
    ATLAS_DEBUG_VAR( nquads );
    ATLAS_DEBUG_VAR( options.getBool( "ghost_at_end" ) );
#endif

    std::vector<int> offset_glb( rg.ny() );
    std::vector<int> offset_loc( region.south - region.north + 1, 0 );

    n = 0;
    for ( idx_t jlat = 0; jlat < rg.ny(); ++jlat ) {
        offset_glb.at( jlat ) = n;
        n += rg.nx( jlat );
    }

    std::vector<int> periodic_glb( rg.ny() );

    if ( include_periodic_ghost_points ) {
        for ( idx_t jlat = 0; jlat < rg.ny(); ++jlat ) {
            if ( rg.nx( jlat ) > 0 ) {
                periodic_glb.at( jlat ) = n;
                ++n;
            }
        }
    }
    else {
        for ( idx_t jlat = 0; jlat < rg.ny(); ++jlat ) {
            if ( rg.nx( jlat ) > 0 ) {
                periodic_glb.at( jlat ) = offset_glb.at( jlat ) + rg.nx( jlat ) - 1;
            }
        }
    }
    int max_glb_idx = n;

    mesh.nodes().resize( nnodes );
    mesh::Nodes& nodes = mesh.nodes();

    auto xy      = array::make_view<double, 2>( nodes.xy() );
    auto lonlat  = array::make_view<double, 2>( nodes.lonlat() );
    auto glb_idx = array::make_view<gidx_t, 1>( nodes.global_index() );
    auto part    = array::make_view<int, 1>( nodes.partition() );
    auto ghost   = array::make_view<int, 1>( nodes.ghost() );
    auto flags   = array::make_view<int, 1>( nodes.flags() );
    auto halo    = array::make_view<int, 1>( nodes.halo() );


    std::vector<idx_t> node_numbering( node_numbering_size, -1 );
    if ( options.getBool( "ghost_at_end" ) ) {
        std::vector<GhostNode> ghost_nodes;
        ghost_nodes.reserve( nnodes );
        idx_t node_number = 0;
        idx_t jnode       = 0;
        l                 = 0;
        ATLAS_ASSERT( region.south >= region.north );
        for ( int jlat = region.north; jlat <= region.south; ++jlat ) {
            int ilat              = jlat - region.north;
            offset_loc.at( ilat ) = l;
            l += region.lat_end.at( jlat ) - region.lat_begin.at( jlat ) + 1;

            if ( region.lat_end.at( jlat ) < region.lat_begin.at( jlat ) ) {
                ATLAS_DEBUG_VAR( jlat );
                ATLAS_DEBUG_VAR( region.lat_begin[jlat] );
                ATLAS_DEBUG_VAR( region.lat_end[jlat] );
            }
            for ( idx_t jlon = region.lat_begin.at( jlat ); jlon <= region.lat_end.at( jlat ); ++jlon ) {
                if ( jlon < rg.nx( jlat ) ) {
                    n = offset_glb.at( jlat ) + jlon;
                    if ( distribution.partition( n ) == mypart ) {
                        node_numbering.at( jnode ) = node_number;
                        ++node_number;
                    }
                    else {
                        ghost_nodes.emplace_back( jlat, jlon, jnode );
                    }
                    ++jnode;
                }
                else if ( include_periodic_ghost_points )  // add periodic point
                {
                    //#warning TODO: use commented approach
                    part( jnode ) = mypart;
                    // part(jnode)      = parts.at( offset_glb.at(jlat) );
                    ghost( jnode ) = 1;
                    halo( jnode )  = 0;
                    ghost_nodes.emplace_back( jlat, rg.nx( jlat ), jnode );
                    ++jnode;
                }
                else {
                    --l;
                }
            }
        }
        for ( size_t jghost = 0; jghost < ghost_nodes.size(); ++jghost ) {
            node_numbering.at( ghost_nodes.at( jghost ).jnode ) = node_number;
            ++node_number;
        }
        if ( include_north_pole ) {
            node_numbering.at( jnode ) = jnode;
            ++jnode;
        }
        if ( include_south_pole ) {
            node_numbering.at( jnode ) = jnode;
            ++jnode;
        }
        ATLAS_ASSERT( jnode == nnodes );
    }
    else  // No renumbering
    {
        for ( idx_t jnode = 0; jnode < nnodes; ++jnode ) {
            node_numbering.at( jnode ) = jnode;
        }
    }

    idx_t jnode = 0;
    l           = 0;
    for ( idx_t jlat = region.north; jlat <= region.south; ++jlat ) {
        idx_t ilat            = jlat - region.north;
        offset_loc.at( ilat ) = l;
        l += region.lat_end.at( jlat ) - region.lat_begin.at( jlat ) + 1;

        double y = rg.y( jlat );
        for ( idx_t jlon = region.lat_begin.at( jlat ); jlon <= region.lat_end.at( jlat ); ++jlon ) {
            if ( jlon < rg.nx( jlat ) ) {
                idx_t inode = node_numbering.at( jnode );
                n           = offset_glb.at( jlat ) + jlon;

                double x = rg.x( jlon, jlat );
                // std::cout << "jlat = " << jlat << "; jlon = " << jlon << "; x = " <<
                // x << std::endl;

                xy( inode, XX ) = x;
                xy( inode, YY ) = y;

                // geographic coordinates by using projection
                double crd[] = {x, y};
                rg.projection().xy2lonlat( crd );
                lonlat( inode, LON ) = crd[LON];
                lonlat( inode, LAT ) = crd[LAT];

                glb_idx( inode ) = n + 1;
                part( inode )    = distribution.partition( n );
                ghost( inode )   = 0;
                halo( inode )    = 0;
                Topology::reset( flags( inode ) );
                Topology::set( flags( inode ), Topology::BC );
                if ( jlat == 0 ) {
                    if ( y_numbering < 0 && !include_north_pole ) {
                        Topology::set( flags( inode ), Topology::NORTH );
                    }
                    if ( y_numbering > 0 && !include_south_pole ) {
                        Topology::set( flags( inode ), Topology::SOUTH );
                    }
                }
                if ( jlat == rg.ny() - 1 ) {
                    if ( y_numbering < 0 && !include_south_pole ) {
                        Topology::set( flags( inode ), Topology::SOUTH );
                    }
                    if ( y_numbering > 0 && !include_north_pole ) {
                        Topology::set( flags( inode ), Topology::NORTH );
                    }
                }
                if ( jlon == 0 && include_periodic_ghost_points ) {
                    Topology::set( flags( inode ), Topology::WEST );
                }
                if ( part( inode ) != mypart ) {
                    Topology::set( flags( inode ), Topology::GHOST );
                    ghost( inode ) = 1;
                }
                ++jnode;
            }
            else if ( include_periodic_ghost_points )  // add periodic point
            {
                idx_t inode = node_numbering.at( jnode );
                // int inode_left = node_numbering.at(jnode-1);
                double x = rg.x( rg.nx( jlat ), jlat );

                xy( inode, XX ) = x;
                xy( inode, YY ) = y;

                // geographic coordinates by using projection
                double crd[] = {x, y};
                rg.projection().xy2lonlat( crd );
                lonlat( inode, LON ) = crd[LON];
                lonlat( inode, LAT ) = crd[LAT];

                glb_idx( inode ) = periodic_glb.at( jlat ) + 1;
                //#warning TODO: use commented approach
                //        part(inode)      = parts.at( offset_glb.at(jlat) );
                part( inode )  = mypart;  // The actual part will be fixed later
                ghost( inode ) = 1;
                halo( inode )  = 0;
                Topology::reset( flags( inode ) );
                Topology::set( flags( inode ), Topology::BC | Topology::EAST );
                Topology::set( flags( inode ), Topology::GHOST );
                if ( jlat == 0 ) {
                    if ( y_numbering < 0 && !include_north_pole ) {
                        Topology::set( flags( inode ), Topology::NORTH );
                    }
                    if ( y_numbering > 0 && !include_south_pole ) {
                        Topology::set( flags( inode ), Topology::SOUTH );
                    }
                }
                if ( jlat == rg.ny() - 1 ) {
                    if ( y_numbering < 0 && !include_south_pole ) {
                        Topology::set( flags( inode ), Topology::SOUTH );
                    }
                    if ( y_numbering > 0 && !include_north_pole ) {
                        Topology::set( flags( inode ), Topology::NORTH );
                    }
                }
                ++jnode;
            }
            else {
                --l;
            }
        }
    }

    idx_t jnorth = -1;
    if ( include_north_pole ) {
        idx_t inode     = node_numbering.at( jnode );
        jnorth          = jnode;
        double y        = 90.;
        double x        = 180.;
        xy( inode, XX ) = x;
        xy( inode, YY ) = y;

        // geographic coordinates by using projection
        double crd[] = {x, y};
        rg.projection().xy2lonlat( crd );
        lonlat( inode, LON ) = crd[LON];
        lonlat( inode, LAT ) = crd[LAT];

        glb_idx( inode ) = periodic_glb.at( rg.ny() - 1 ) + 2;
        part( inode )    = mypart;
        ghost( inode )   = 0;
        halo( inode )    = 0;
        Topology::reset( flags( inode ) );
        Topology::set( flags( inode ), Topology::NORTH );
        ++jnode;
    }

    idx_t jsouth = -1;
    if ( include_south_pole ) {
        idx_t inode     = node_numbering.at( jnode );
        jsouth          = jnode;
        double y        = -90.;
        double x        = 180.;
        xy( inode, XX ) = x;
        xy( inode, YY ) = y;

        // geographic coordinates by using projection
        double crd[] = {x, y};
        rg.projection().xy2lonlat( crd );
        lonlat( inode, LON ) = crd[LON];
        lonlat( inode, LAT ) = crd[LAT];

        glb_idx( inode ) = periodic_glb.at( rg.ny() - 1 ) + 3;
        part( inode )    = mypart;
        ghost( inode )   = 0;
        halo( inode )    = 0;
        Topology::reset( flags( inode ) );
        Topology::set( flags( inode ), Topology::SOUTH );
        ++jnode;
    }

    mesh.metadata().set<size_t>( "nb_nodes_including_halo[0]", nodes.size() );
    nodes.metadata().set<size_t>( "NbRealPts", size_t( nnodes - nnewnodes ) );
    nodes.metadata().set<size_t>( "NbVirtualPts", size_t( nnewnodes ) );
    nodes.global_index().metadata().set( "human_readable", true );
    nodes.global_index().metadata().set( "min", 1 );
    nodes.global_index().metadata().set( "max", max_glb_idx );

    // Now handle elements
    // -------------------

    mesh.cells().add( new mesh::temporary::Quadrilateral(), nquads );
    mesh.cells().add( new mesh::temporary::Triangle(), ntriags );

    mesh::HybridElements::Connectivity& node_connectivity = mesh.cells().node_connectivity();
    array::ArrayView<gidx_t, 1> cells_glb_idx             = array::make_view<gidx_t, 1>( mesh.cells().global_index() );
    array::ArrayView<int, 1> cells_part                   = array::make_view<int, 1>( mesh.cells().partition() );
    array::ArrayView<int, 1> cells_flags                  = array::make_view<int, 1>( mesh.cells().flags() );

    /*
     * label all patch cells a non-patch
     */
    cells_flags.assign( 0 );

    /*
     * Fill in connectivity tables with global node indices first
     */
    idx_t jcell;
    idx_t jquad       = 0;
    idx_t jtriag      = 0;
    idx_t quad_begin  = mesh.cells().elements( 0 ).begin();
    idx_t triag_begin = mesh.cells().elements( 1 ).begin();
    idx_t quad_nodes[4];
    idx_t triag_nodes[3];

    auto fix_quad_orientation = []( idx_t nodes[] ) {
        idx_t tmp;
        tmp      = nodes[0];
        nodes[0] = nodes[1];
        nodes[1] = tmp;
        tmp      = nodes[2];
        nodes[2] = nodes[3];
        nodes[3] = tmp;
    };
    auto fix_triag_orientation = []( idx_t nodes[] ) {
        idx_t tmp;
        tmp      = nodes[0];
        nodes[0] = nodes[1];
        nodes[1] = tmp;
    };


    for ( idx_t jlat = region.north; jlat < region.south; ++jlat ) {
        idx_t ilat  = jlat - region.north;
        idx_t jlatN = jlat;
        idx_t jlatS = jlat + 1;
        idx_t ilatN = ilat;
        idx_t ilatS = ilat + 1;
        for ( idx_t jelem = 0; jelem < region.nb_lat_elems.at( jlat ); ++jelem ) {
            const auto elem = array::make_view<int, 3>( *region.elems ).slice( ilat, jelem, Range::all() );

            if ( elem( 2 ) >= 0 && elem( 3 ) >= 0 )  // This is a quad
            {
                quad_nodes[0] = node_numbering.at( offset_loc.at( ilatN ) + elem( 0 ) - region.lat_begin.at( jlatN ) );
                quad_nodes[1] = node_numbering.at( offset_loc.at( ilatS ) + elem( 1 ) - region.lat_begin.at( jlatS ) );
                quad_nodes[2] = node_numbering.at( offset_loc.at( ilatS ) + elem( 2 ) - region.lat_begin.at( jlatS ) );
                quad_nodes[3] = node_numbering.at( offset_loc.at( ilatN ) + elem( 3 ) - region.lat_begin.at( jlatN ) );

                if ( three_dimensional && periodic_east_west ) {
                    if ( elem( 2 ) == rg.nx( jlatS ) ) {
                        quad_nodes[2] = node_numbering.at( offset_loc.at( ilatS ) );
                    }
                    if ( elem( 3 ) == rg.nx( jlatN ) ) {
                        quad_nodes[3] = node_numbering.at( offset_loc.at( ilatN ) );
                    }
                }

                if ( y_numbering > 0 ) {
                    fix_quad_orientation( quad_nodes );
                }

                jcell = quad_begin + jquad++;
                node_connectivity.set( jcell, quad_nodes );
                cells_glb_idx( jcell ) = jcell + 1;
                cells_part( jcell )    = mypart;
            }
            else  // This is a triag
            {
                if ( elem( 3 ) < 0 )  // This is a triangle pointing up
                {
                    triag_nodes[0] =
                        node_numbering.at( offset_loc.at( ilatN ) + elem( 0 ) - region.lat_begin.at( jlatN ) );
                    triag_nodes[1] =
                        node_numbering.at( offset_loc.at( ilatS ) + elem( 1 ) - region.lat_begin.at( jlatS ) );
                    triag_nodes[2] =
                        node_numbering.at( offset_loc.at( ilatS ) + elem( 2 ) - region.lat_begin.at( jlatS ) );
                    if ( three_dimensional && periodic_east_west ) {
                        if ( elem( 0 ) == rg.nx( jlatN ) ) {
                            triag_nodes[0] = node_numbering.at( offset_loc.at( ilatN ) );
                        }
                        if ( elem( 2 ) == rg.nx( jlatS ) ) {
                            triag_nodes[2] = node_numbering.at( offset_loc.at( ilatS ) );
                        }
                    }
                    if ( y_numbering > 0 ) {
                        fix_triag_orientation( triag_nodes );
                    }
                }
                else  // This is a triangle pointing down
                {
                    triag_nodes[0] =
                        node_numbering.at( offset_loc.at( ilatN ) + elem( 0 ) - region.lat_begin.at( jlatN ) );
                    triag_nodes[1] =
                        node_numbering.at( offset_loc.at( ilatS ) + elem( 1 ) - region.lat_begin.at( jlatS ) );
                    triag_nodes[2] =
                        node_numbering.at( offset_loc.at( ilatN ) + elem( 3 ) - region.lat_begin.at( jlatN ) );
                    if ( three_dimensional && periodic_east_west ) {
                        if ( elem( 1 ) == rg.nx( jlatS ) ) {
                            triag_nodes[1] = node_numbering.at( offset_loc.at( ilatS ) );
                        }
                        if ( elem( 3 ) == rg.nx( jlatN ) ) {
                            triag_nodes[2] = node_numbering.at( offset_loc.at( ilatN ) );
                        }
                    }
                    if ( y_numbering > 0 ) {
                        fix_triag_orientation( triag_nodes );
                    }
                }
                jcell = triag_begin + jtriag++;
                node_connectivity.set( jcell, triag_nodes );
                cells_glb_idx( jcell ) = jcell + 1;
                cells_part( jcell )    = mypart;
            }
        }
    }

    if ( include_north_pole ) {
        idx_t ilat = 0;
        idx_t ip1  = 0;
        idx_t nlon = rg.nx( 0 ) - ( periodic_east_west ? 0 : 1 );
        for ( idx_t ip2 = 0; ip2 < nlon; ++ip2 ) {
            jcell     = triag_begin + jtriag++;
            idx_t ip3 = ip2 + 1;
            if ( three_dimensional && ip3 == rg.nx( 0 ) ) {
                ip3 = 0;
            }
            triag_nodes[0] = node_numbering.at( jnorth + ip1 );
            triag_nodes[1] = node_numbering.at( offset_loc.at( ilat ) + ip2 );
            triag_nodes[2] = node_numbering.at( offset_loc.at( ilat ) + ip3 );
            if ( y_numbering > 0 ) {
                fix_triag_orientation( triag_nodes );
            }
            node_connectivity.set( jcell, triag_nodes );
            cells_glb_idx( jcell ) = jcell + 1;
            cells_part( jcell )    = mypart;
        }
    }
    else if ( patch_north_pole ) {
        idx_t jlat = y_numbering < 0 ? 0 : rg.ny() - 1;
        idx_t ilat = y_numbering < 0 ? 0 : std::abs( region.south - region.north );

        if ( patch_quads ) {
            idx_t ip1, ip2, ip3, ip4;

            idx_t jforward  = 0;
            idx_t jbackward = rg.nx( jlat ) - 1;

            while ( true ) {
                ip1 = jforward;
                ip2 = jforward + 1;
                ip3 = jbackward - 1;
                ip4 = jbackward;

                quad_nodes[0] = node_numbering.at( offset_loc.at( ilat ) + ip1 );
                quad_nodes[1] = node_numbering.at( offset_loc.at( ilat ) + ip2 );
                quad_nodes[2] = node_numbering.at( offset_loc.at( ilat ) + ip3 );
                quad_nodes[3] = node_numbering.at( offset_loc.at( ilat ) + ip4 );

                jcell = quad_begin + jquad++;
                node_connectivity.set( jcell, quad_nodes );

                cells_glb_idx( jcell ) = jcell + 1;
                cells_part( jcell )    = mypart;
                Topology::set( cells_flags( jcell ), Topology::PATCH );

                if ( jcell == cells_glb_idx.size() - 1 ) {
                }
                if ( jforward + 1 == jbackward ) {
                    break;
                }
                ++jforward;
                --jbackward;
            }
        }
        else {
            idx_t ip1, ip2, ip3;

            idx_t jforward  = 0;
            idx_t jbackward = rg.nx( jlat ) - 1;
            bool forward    = true;

            while ( true ) {
                if ( forward ) {
                    ip1 = jforward;
                    ip2 = jforward + 1;
                    ip3 = jbackward;
                }
                else {
                    ip1 = jforward;
                    ip2 = jbackward - 1;
                    ip3 = jbackward;
                }

                triag_nodes[0] = node_numbering.at( offset_loc.at( ilat ) + ip1 );
                triag_nodes[1] = node_numbering.at( offset_loc.at( ilat ) + ip2 );
                triag_nodes[2] = node_numbering.at( offset_loc.at( ilat ) + ip3 );
                //            if ( y_numbering > 0 ) {
                //                fix_triag_orientation( triag_nodes );
                //            }

                jcell = triag_begin + jtriag++;
                node_connectivity.set( jcell, triag_nodes );

                cells_glb_idx( jcell ) = jcell + 1;
                cells_part( jcell )    = mypart;
                Topology::set( cells_flags( jcell ), Topology::PATCH );

                if ( jbackward == jforward + 2 ) {
                    break;
                }

                if ( forward ) {
                    ++jforward;
                    forward = false;
                }
                else {
                    --jbackward;
                    forward = true;
                }
            }
        }
    }

    if ( include_south_pole ) {
        idx_t jlat = y_numbering > 0 ? 0 : rg.ny() - 1;
        idx_t ilat = y_numbering > 0 ? 0 : std::abs( region.south - region.north );
        idx_t ip1  = 0;
        idx_t nlon = rg.nx( jlat ) + 1 - ( periodic_east_west ? 0 : 1 );
        for ( idx_t ip2 = 1; ip2 < nlon; ++ip2 ) {
            jcell          = triag_begin + jtriag++;
            int ip3        = ip2 - 1;
            triag_nodes[0] = node_numbering.at( jsouth + ip1 );
            triag_nodes[1] = node_numbering.at( offset_loc.at( ilat ) + ip2 );
            triag_nodes[2] = node_numbering.at( offset_loc.at( ilat ) + ip3 );
            if ( three_dimensional && ip2 == rg.nx( jlat ) ) {
                triag_nodes[1] = node_numbering.at( offset_loc.at( ilat ) + 0 );
            }
            if ( y_numbering > 0 ) {
                fix_triag_orientation( triag_nodes );
            }

            node_connectivity.set( jcell, triag_nodes );
            cells_glb_idx( jcell ) = jcell + 1;
            cells_part( jcell )    = mypart;
        }
    }
    else if ( patch_south_pole ) {
        idx_t jlat = y_numbering > 0 ? 0 : rg.ny() - 1;
        idx_t ilat = y_numbering > 0 ? 0 : std::abs( region.south - region.north );

        if ( patch_quads ) {
            idx_t ip1, ip2, ip3, ip4;

            idx_t jforward  = 0;
            idx_t jbackward = rg.nx( jlat ) - 1;

            while ( true ) {
                ip1 = jbackward;
                ip2 = jbackward - 1;
                ip3 = jforward + 1;
                ip4 = jforward;

                quad_nodes[0] = node_numbering.at( offset_loc.at( ilat ) + ip1 );
                quad_nodes[1] = node_numbering.at( offset_loc.at( ilat ) + ip2 );
                quad_nodes[2] = node_numbering.at( offset_loc.at( ilat ) + ip3 );
                quad_nodes[3] = node_numbering.at( offset_loc.at( ilat ) + ip4 );

                jcell = quad_begin + jquad++;
                node_connectivity.set( jcell, quad_nodes );

                cells_glb_idx( jcell ) = jcell + 1;
                cells_part( jcell )    = mypart;
                Topology::set( cells_flags( jcell ), Topology::PATCH );

                if ( jcell == cells_glb_idx.size() - 1 ) {
                }
                if ( jforward + 1 == jbackward ) {
                    break;
                }
                ++jforward;
                --jbackward;
            }
        }
        else {
            idx_t ip1, ip2, ip3;

            idx_t jforward  = 0;
            idx_t jbackward = rg.nx( jlat ) - 1;
            bool forward    = true;

            while ( true ) {
                if ( forward ) {
                    ip1 = jbackward;
                    ip2 = jforward + 1;
                    ip3 = jforward;
                }
                else {
                    ip1 = jbackward;
                    ip2 = jbackward - 1;
                    ip3 = jforward;
                }

                triag_nodes[0] = node_numbering.at( offset_loc.at( ilat ) + ip1 );
                triag_nodes[1] = node_numbering.at( offset_loc.at( ilat ) + ip2 );
                triag_nodes[2] = node_numbering.at( offset_loc.at( ilat ) + ip3 );
                //            if ( y_numbering > 0 ) {
                //                fix_triag_orientation( triag_nodes );
                //            }

                jcell = triag_begin + jtriag++;
                node_connectivity.set( jcell, triag_nodes );

                cells_glb_idx( jcell ) = jcell + 1;
                cells_part( jcell )    = mypart;
                Topology::set( cells_flags( jcell ), Topology::PATCH );

                if ( jbackward == jforward + 2 ) {
                    break;
                }

                if ( forward ) {
                    ++jforward;
                    forward = false;
                }
                else {
                    --jbackward;
                    forward = true;
                }
            }
        }
    }
    generateGlobalElementNumbering( mesh );
}

namespace {
static MeshGeneratorBuilder<StructuredMeshGenerator> __Structured( "structured" );
}  // namespace

}  // namespace meshgenerator
}  // namespace atlas
