/*
 * (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 <iterator>
#include <string>

#include "atlas/field/Field.h"
#include "atlas/grid/Grid.h"
#include "atlas/library/config.h"
#include "atlas/mesh/Connectivity.h"
#include "atlas/mesh/ElementType.h"
#include "atlas/mesh/Elements.h"
#include "atlas/mesh/Mesh.h"
#include "atlas/mesh/Nodes.h"
#include "atlas/meshgenerator.h"
#include "atlas/runtime/Log.h"

#include "tests/AtlasTestEnvironment.h"

using namespace atlas::mesh;
using namespace atlas::mesh::temporary;

namespace atlas {
namespace test {

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

CASE("hybrid_elements") {
    HybridElements hybrid_elements;

    idx_t triangle_nodes[] = {1, 5, 3, 1, 5, 2};

    idx_t triags_type_idx = hybrid_elements.add(new Triangle(), 2, triangle_nodes);

    EXPECT(triags_type_idx == 0);

    hybrid_elements.add(Field("surface", array::make_datatype<double>(), array::make_shape(hybrid_elements.size())));

    std::vector<idx_t> quad_nodes(4);
    quad_nodes[0] = 0;
    quad_nodes[1] = 1;
    quad_nodes[2] = 2;
    quad_nodes[3] = 3;

    idx_t quads_type_idx = hybrid_elements.add(new Quadrilateral(), 1, quad_nodes);

    EXPECT(quads_type_idx == 1);

    {
        const HybridElements::Connectivity& connectivity = hybrid_elements.node_connectivity();
        for (idx_t e = 0; e < hybrid_elements.size(); ++e) {
            eckit::Log::info() << e << std::endl;
            eckit::Log::info() << "  " << hybrid_elements.name(e) << std::endl;
            eckit::Log::info() << "  nb_nodes = " << hybrid_elements.nb_nodes(e) << std::endl;
            eckit::Log::info() << "  nb_edges = " << hybrid_elements.nb_edges(e) << std::endl;
            eckit::Log::info() << "  nodes = [ ";
            for (idx_t n = 0; n < hybrid_elements.nb_nodes(e); ++n) {
                eckit::Log::info() << connectivity(e, n) << " ";
            }
            eckit::Log::info() << "]" << std::endl;
        }
        EXPECT(connectivity(0, 0) == 1);
        EXPECT(connectivity(0, 1) == 5);
        EXPECT(connectivity(0, 2) == 3);
        EXPECT(connectivity(1, 0) == 1);
        EXPECT(connectivity(1, 1) == 5);
        EXPECT(connectivity(1, 2) == 2);
        EXPECT(connectivity(2, 0) == 0);
        EXPECT(connectivity(2, 1) == 1);
        EXPECT(connectivity(2, 2) == 2);
        EXPECT(connectivity(2, 3) == 3);
    }

    eckit::Log::info() << std::endl;
    idx_t quad0[4] = {9, 8, 7, 6};
    {
        for (idx_t t = 0; t < hybrid_elements.nb_types(); ++t) {
            Elements& elements                          = hybrid_elements.elements(t);
            const BlockConnectivity& block_connectivity = elements.node_connectivity();
            if (t == 0) {
                EXPECT(block_connectivity(0, 0) == 1);
                EXPECT(block_connectivity(0, 1) == 5);
                EXPECT(block_connectivity(0, 2) == 3);
                EXPECT(block_connectivity(1, 0) == 1);
                EXPECT(block_connectivity(1, 1) == 5);
                EXPECT(block_connectivity(1, 2) == 2);
            }
            if (t == 1) {
                EXPECT(block_connectivity(0, 0) == 0);
                EXPECT(block_connectivity(0, 1) == 1);
                EXPECT(block_connectivity(0, 2) == 2);
                EXPECT(block_connectivity(0, 3) == 3);
            }
            elements.node_connectivity().set(0, quad0);
            // const_cast<Elements::Connectivity&>(block_connectivity).set(0,quad0);
            // elements.set_node_connectivity(0,quad0);

            if (t == 0) {
                EXPECT(block_connectivity(0, 0) == 9);
                EXPECT(block_connectivity(0, 1) == 8);
                EXPECT(block_connectivity(0, 2) == 7);
                EXPECT(block_connectivity(1, 0) == 1);
                EXPECT(block_connectivity(1, 1) == 5);
                EXPECT(block_connectivity(1, 2) == 2);
            }
            if (t == 1) {
                EXPECT(block_connectivity(0, 0) == 9);
                EXPECT(block_connectivity(0, 1) == 8);
                EXPECT(block_connectivity(0, 2) == 7);
                EXPECT(block_connectivity(0, 3) == 6);
            }

            eckit::Log::info() << "name = " << elements.name() << std::endl;
            eckit::Log::info() << "nb_elements = " << elements.size() << std::endl;
            const BlockConnectivity& connectivity = elements.node_connectivity();
            for (idx_t e = 0; e < elements.size(); ++e) {
                eckit::Log::info() << "  nodes = [ ";
                for (idx_t n = 0; n < elements.nb_nodes(); ++n) {
                    eckit::Log::info() << connectivity(e, n) << " ";
                }
                eckit::Log::info() << "]" << std::endl;
            }
        }
    }

    idx_t nb_elements = 3;
    EXPECT(hybrid_elements.size() == nb_elements);
    EXPECT(hybrid_elements.global_index().size() == nb_elements);
    EXPECT(hybrid_elements.partition().size() == nb_elements);
    EXPECT(hybrid_elements.remote_index().size() == nb_elements);
    EXPECT(hybrid_elements.field("surface").size() == nb_elements);
    EXPECT(hybrid_elements.elements(0).begin() == 0);
    EXPECT(hybrid_elements.elements(0).end() == 2);
    EXPECT(hybrid_elements.elements(1).begin() == 2);
    EXPECT(hybrid_elements.elements(1).end() == 3);
    //  EXPECT( hybrid_elements.elements(0).type_idx() == 0 );
    //  EXPECT( hybrid_elements.elements(1).type_idx() == 1 );
}

CASE("elements") {
    eckit::Log::info() << "\nelements" << std::endl;

    idx_t triangle_nodes[] = {1, 5, 3, 1, 5, 2};
    idx_t triag1[3]        = {9, 8, 7};

    Elements elements(new Triangle(), 2, triangle_nodes);

    EXPECT(elements.begin() == 0);
    EXPECT(elements.end() == 2);
    //  EXPECT( elements.type_idx() == 0);

    elements.node_connectivity().set(0, triag1);
    eckit::Log::info() << "name = " << elements.name() << std::endl;
    eckit::Log::info() << "nb_elements = " << elements.size() << std::endl;
    const BlockConnectivity& connectivity = elements.node_connectivity();
    for (idx_t e = 0; e < elements.size(); ++e) {
        eckit::Log::info() << "  nodes = [ ";
        for (idx_t n = 0; n < elements.nb_nodes(); ++n) {
            eckit::Log::info() << connectivity(e, n) << " ";
        }
        eckit::Log::info() << "]" << std::endl;
    }

    // This will create a new structure with a copy of elements
    HybridElements hybrid_elements;
    hybrid_elements.add(elements);
    {
        for (idx_t t = 0; t < hybrid_elements.nb_types(); ++t) {
            Elements& elements = hybrid_elements.elements(t);
            elements.node_connectivity().set(0, triag1);
            eckit::Log::info() << "name = " << elements.name() << std::endl;
            eckit::Log::info() << "nb_elements = " << elements.size() << std::endl;
            const BlockConnectivity& connectivity = elements.node_connectivity();
            for (idx_t e = 0; e < elements.size(); ++e) {
                eckit::Log::info() << "  nodes = [ ";
                for (idx_t n = 0; n < elements.nb_nodes(); ++n) {
                    eckit::Log::info() << connectivity(e, n) << " ";
                }
                eckit::Log::info() << "]" << std::endl;
            }
        }
    }
}

CASE("hybrid_connectivity") {
    eckit::Log::info() << "\nhybrid_connectivity" << std::endl;

    idx_t triangle_nodes[] = {1, 5, 3, 1, 5, 2};
    MultiBlockConnectivity hybrid_connectivity;
    hybrid_connectivity.add(2, 3, triangle_nodes);
    for (idx_t e = 0; e < hybrid_connectivity.rows(); ++e) {
        eckit::Log::info() << "  cols = [ ";
        for (idx_t n = 0; n < hybrid_connectivity.cols(e); ++n) {
            eckit::Log::info() << hybrid_connectivity(e, n) << " ";
        }
        eckit::Log::info() << "]" << std::endl;
    }

    idx_t quad_nodes[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
    hybrid_connectivity.add(3, 4, quad_nodes);

    for (idx_t e = 0; e < hybrid_connectivity.rows(); ++e) {
        eckit::Log::info() << "  cols = [ ";
        for (idx_t n = 0; n < hybrid_connectivity.cols(e); ++n) {
            eckit::Log::info() << hybrid_connectivity(e, n) << " ";
        }
        eckit::Log::info() << "]" << std::endl;
    }

    for (idx_t b = 0; b < hybrid_connectivity.blocks(); ++b) {
        const BlockConnectivity& block = hybrid_connectivity.block(b);
        for (idx_t r = 0; r < block.rows(); ++r) {
            eckit::Log::info() << "  cols = [ ";
            for (idx_t c = 0; c < block.cols(); ++c) {
                eckit::Log::info() << block(r, c) << " ";
            }
            eckit::Log::info() << "]" << std::endl;
        }
    }
}

CASE("block_connectivity") {
    eckit::Log::info() << "\nblock_connectivity" << std::endl;

    idx_t triangle_nodes[] = {1, 5, 3, 1, 5, 2};
    BlockConnectivity block;
    block.add(2, 3, triangle_nodes);
    block.add(2, 3, triangle_nodes);
    for (idx_t r = 0; r < block.rows(); ++r) {
        eckit::Log::info() << "  cols = [ ";
        for (idx_t c = 0; c < block.cols(); ++c) {
            eckit::Log::info() << block(r, c) << " ";
        }
        eckit::Log::info() << "]" << std::endl;
    }
}

CASE("zero_elements") {
    HybridElements hybrid_elements;
    idx_t* nodes = nullptr;

    hybrid_elements.add(new Triangle(), 0, nodes);
    hybrid_elements.add(new Quadrilateral(), 0, nodes);

    EXPECT(hybrid_elements.size() == 0);
    EXPECT(hybrid_elements.nb_types() == 2);
    EXPECT(hybrid_elements.elements(0).size() == 0);
    EXPECT(hybrid_elements.elements(1).size() == 0);
}

CASE("irregularconnectivity_insert") {
    IrregularConnectivity connectivity;
    idx_t c[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    connectivity.add(3, 4, c);
    idx_t c2[] = {13, 14, 15, 16, 17, 18};
    connectivity.insert(1, 2, 3, c2);
    connectivity.insert(2, 1, 5);

    idx_t iregular_c[] = {2, 3, 4, 1};
    connectivity.insert(5, 4, iregular_c);

    idx_t values[] = {1, 2, 3,  4,  13, 14, 15, -1, -1, -1, -1, -1, 16, 17, 18, 5, 6,
                      7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9,  10, 11, 12};
    idx_t counts[] = {4, 3, 5, 3, 4, 2, 3, 4, 1, 4};

    idx_t n(0);
    idx_t r(0);
    for (idx_t jrow = 0; jrow < connectivity.rows(); ++jrow) {
        EXPECT(connectivity.cols(jrow) == counts[r++]);
        for (idx_t jcol = 0; jcol < connectivity.cols(jrow); ++jcol) {
            EXPECT(connectivity(jrow, jcol) == values[n++]);
        }
    }
}

CASE("multiblockconnectivity_insert") {
    MultiBlockConnectivity connectivity;
    idx_t c1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    connectivity.add(3, 4, c1);
    idx_t c2[] = {13, 14, 15, 16, 17, 18};
    connectivity.add(2, 3, c2);

    idx_t c1i[] = {19, 20, 21, 22};
    idx_t c2i[] = {23, 24, 25, 26, 27, 28};

    EXPECT(connectivity.block(0).rows() == 3);
    EXPECT(connectivity.block(1).rows() == 2);
    EXPECT(connectivity.block(0).cols() == 4);
    EXPECT(connectivity.block(1).cols() == 3);

    std::cout << "block 0" << std::endl;
    for (idx_t jrow = 0; jrow < connectivity.block(0).rows(); ++jrow) {
        for (idx_t jcol = 0; jcol < connectivity.block(0).cols(); ++jcol) {
            std::cout << connectivity.block(0)(jrow, jcol) << " ";
        }
        std::cout << std::endl;
    }

    connectivity.insert(1, 1, 4, c1i);
    connectivity.insert(5, 2, 3, c2i);

    EXPECT(connectivity.block(0).rows() == 4);
    EXPECT(connectivity.block(1).rows() == 4);

    std::cout << "\nfull\n";
    for (idx_t jrow = 0; jrow < connectivity.rows(); ++jrow) {
        for (idx_t jcol = 0; jcol < connectivity.cols(jrow); ++jcol) {
            std::cout << connectivity(jrow, jcol) << " ";
        }
        std::cout << std::endl;
    }
    idx_t values[] = {1,  2,  3,  4,  19, 20, 21, 22, 5,  6,  7,  8,  9,  10,
                      11, 12, 13, 14, 15, 23, 24, 25, 26, 27, 28, 16, 17, 18};
    idx_t counts[] = {4, 4, 4, 4, 3, 3, 3, 3};

    idx_t n(0);
    idx_t r(0);
    for (idx_t jrow = 0; jrow < connectivity.rows(); ++jrow) {
        EXPECT(connectivity.cols(jrow) == counts[r++]);
        for (idx_t jcol = 0; jcol < connectivity.cols(jrow); ++jcol) {
            EXPECT(connectivity(jrow, jcol) == values[n++]);
        }
    }
}

CASE("cells_insert") {
    Log::info() << "\n\n\ncells_insert \n============ \n\n" << std::endl;
    HybridElements cells;
    idx_t c1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    cells.add(new Quadrilateral(), 3, c1);
    idx_t c2[] = {13, 14, 15, 16, 17, 18};
    cells.add(new Triangle(), 2, c2);

    EXPECT(cells.elements(0).size() == 3);
    EXPECT(cells.elements(1).size() == 2);
    EXPECT(cells.size() == 5);
    EXPECT(cells.elements(0).node_connectivity().rows() == 3);
    EXPECT(cells.elements(1).node_connectivity().rows() == 2);

    Log::info() << "Update elements(0)" << std::endl;
    idx_t pos0 = cells.elements(0).add(3);

    Log::info() << "Update elements(1)" << std::endl;
    idx_t pos1 = cells.elements(1).add(2);

    EXPECT(pos0 == 3);
    EXPECT(pos1 == 2);
    EXPECT(cells.elements(0).size() == 6);
    EXPECT(cells.elements(1).size() == 4);
    EXPECT(cells.size() == 10);
    EXPECT(cells.node_connectivity().block(0).rows() == 6);
    EXPECT(cells.elements(0).node_connectivity().rows() == 6);
    EXPECT(cells.elements(1).node_connectivity().rows() == 4);

    const BlockConnectivity& conn1 = cells.elements(0).node_connectivity();
    const BlockConnectivity& conn2 = cells.elements(1).node_connectivity();

    std::cout << "\nconn1\n";
    for (idx_t jrow = 0; jrow < conn1.rows(); ++jrow) {
        for (idx_t jcol = 0; jcol < conn1.cols(); ++jcol) {
            std::cout << conn1(jrow, jcol) << " ";
        }
        std::cout << std::endl;
    }

    std::cout << "\nconn2\n";
    for (idx_t jrow = 0; jrow < conn2.rows(); ++jrow) {
        for (idx_t jcol = 0; jcol < conn2.cols(); ++jcol) {
            std::cout << conn2(jrow, jcol) << " ";
        }
        std::cout << std::endl;
    }
}

CASE("cells_add_add") {
    HybridElements cells;

    cells.add(new Quadrilateral(), 3);
    cells.add(new Triangle(), 2);

    HybridElements::Connectivity& conn = cells.node_connectivity();

    idx_t nodes[] = {0, 1, 2, 3};

    conn.set(0, nodes);

    EXPECT(conn(0, 0) == 0);
    EXPECT(conn(0, 1) == 1);
    EXPECT(conn(0, 2) == 2);
    EXPECT(conn(0, 3) == 3);

    EXPECT(cells.elements(0).node_connectivity()(0, 0) == 0);
    EXPECT(cells.elements(0).node_connectivity()(0, 1) == 1);
    EXPECT(cells.elements(0).node_connectivity()(0, 2) == 2);
    EXPECT(cells.elements(0).node_connectivity()(0, 3) == 3);
}

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

}  // namespace test
}  // namespace atlas

int main(int argc, char** argv) {
    return atlas::test::run(argc, argv);
}
