#include "MeshPrimitives.h"

#include "MeshBuilder.h"

#define _USE_MATH_DEFINES
#include <math.h>

#include <iostream>

std::unique_ptr<TriMesh> MeshPrimitives::buildRectangleAsTriMesh()
{
    VecPoints locations = {
            {0, 0},
            {1, 0},
            {1, 1},
            {0, 1}
    };

    EdgeIds edge_ids = {
            {0, 1},
            {1, 2},
            {2, 0},
            {2, 3},
            {3, 0}
    };

    FaceIds face_ids = {
            {0, 1, 2},
            {2, 3, 4}
    };

    return MeshBuilder::buildTriMesh(locations, edge_ids, face_ids);
}

std::unique_ptr<TriMesh> MeshPrimitives::buildCircleAsTriMesh(unsigned numSegments)
{
    VecPoints locations(numSegments + 1);
    locations[0] = {0, 0};

    const double delta_theta = (2.0*M_PI)/double(numSegments);
    double theta = 0.0;
    for(unsigned idx=1; idx<=numSegments; idx++)
    {
        const double x = sin(theta);
        const double y = cos(theta);
        locations[idx] = {x, y};
        theta += delta_theta;
    }

    EdgeIds edge_ids(2*numSegments);
    for(unsigned idx=0; idx<numSegments; idx++)
    {
        edge_ids[idx] = {0, idx+1};

        auto wrap_node = idx + 2;
        if (wrap_node > numSegments)
        {
            wrap_node = 1;
        }
        edge_ids[idx + numSegments] = {idx + 1, wrap_node};
    }

    FaceIds face_ids(numSegments);
    for(unsigned idx=0; idx<numSegments; idx++)
    {
        auto top_edge_inner = idx + 1;
        if (top_edge_inner == numSegments)
        {
            top_edge_inner = 0;
        }

        const auto outer_edge = idx + numSegments;
        face_ids[idx] = {idx, outer_edge, top_edge_inner};
    }
    return MeshBuilder::buildTriMesh(locations, edge_ids, face_ids);
}

std::unique_ptr<LineMesh> MeshPrimitives::buildCircleAsLineMesh(unsigned numSegments)
{
    VecPoints locations(numSegments);
    const double delta_theta = (2.0*M_PI)/double(numSegments);
    double theta = 0.0;
    for(unsigned idx=0; idx<numSegments; idx++)
    {
        const double x = sin(theta);
        const double y = cos(theta);
        locations[idx] = {x, y};
    }

    EdgeIds edge_ids(2*numSegments);
    for(unsigned idx=0; idx<numSegments; idx++)
    {
        auto top_node = idx + 1;
        if (top_node == numSegments)
        {
            top_node = 0;
        }
        edge_ids[idx] = {idx, top_node};
    }
    return MeshBuilder::buildLineMesh(locations, edge_ids);
}

std::unique_ptr<TriMesh> MeshPrimitives::buildRoundedRectangleAsTriMesh(double radius, double aspect_ratio, unsigned num_segments)
{
    unsigned num_fans = 4;
    unsigned num_nodes_per_fan = num_segments + 2;
    VecPoints locations(num_fans * num_nodes_per_fan);

    double rect_start_x = radius;
    double rect_end_x = 1.0 - radius;
    double rect_end_y = 1.0 - radius;

    double delta_theta = (M_PI/4.0) / double (num_segments);
    double running_theta = 0;

    locations[0] = {rect_end_x, radius};
    unsigned offset = 1;
    for (unsigned idx=0; idx<=num_segments; idx++)
    {
        locations[offset + idx] = {rect_end_x + radius*sin(running_theta), radius*(1.0 - cos(running_theta))};
        running_theta += delta_theta;
    }
    offset += num_segments;

    locations[offset] = {rect_end_x, rect_end_y};
    offset++;
    running_theta = 0;
    for (unsigned idx=0; idx<=num_segments;idx++)
    {
        locations[offset + idx] = {rect_end_x + radius*cos(running_theta), rect_end_y + radius*sin(running_theta)};
        running_theta += delta_theta;
    }
    offset += num_segments;

    locations[offset] = {rect_start_x, rect_end_y};
    offset ++;
    running_theta = 0;
    for (unsigned idx=0; idx<=num_segments;idx++)
    {
        locations[offset + idx] = {rect_start_x - radius*sin(running_theta), rect_end_y + radius*cos(running_theta)};
        running_theta += delta_theta;
    }
    offset += num_segments;

    locations[offset] = {rect_start_x, radius};
    offset++;
    running_theta = 0;
    for (unsigned idx=0; idx<=num_segments;idx++)
    {
        locations[offset + idx] = {rect_start_x - radius*cos(running_theta), radius *(1 - sin(running_theta))};
        running_theta += delta_theta;
    }

    unsigned num_edges_per_fan = 2*num_segments + 1;
    unsigned num_inner_rect_edges = 3*num_fans;
    EdgeIds edge_ids(num_edges_per_fan*num_fans + num_inner_rect_edges + 1);

    // Fan edges
    for (unsigned jdx=0; jdx< num_fans; jdx++)
    {
        unsigned node_offset = jdx*num_nodes_per_fan;
        unsigned edge_offset = jdx*num_edges_per_fan;

        // Inner edges
        for(unsigned idx=0; idx<=num_segments; idx++)
        {
            edge_ids[edge_offset + idx] = {node_offset, node_offset + idx + 1};
        }

        // Outer edges
        for(unsigned idx=0; idx<num_segments; idx++)
        {
            edge_ids[edge_offset + num_segments + 1 + idx] = {node_offset + idx + 1, node_offset + idx + 2};
        }
    }

    // Inner rect edges
    unsigned edge_offset = num_edges_per_fan*num_fans;
    return nullptr;
}

std::unique_ptr<LineMesh> MeshPrimitives::buildRectangleAsLineMesh()
{
    VecPoints locations = {
            {0, 0},
            {1, 0},
            {1, 1},
            {0, 1}
    };

    EdgeIds edge_ids = {
            {0, 1},
            {1, 2},
            {2, 3},
            {3, 0}
    };

    return MeshBuilder::buildLineMesh(locations, edge_ids);
}

std::unique_ptr<TriMesh> MeshPrimitives::buildExplodedGridAsTriMesh(unsigned numX, unsigned numY)
{
    double delta_x = 1.0/double(numX);
    double delta_y = 1.0/double(numY);

    VecPoints locations (4 * numX * numY);
    double offset_x = delta_x/2.0;
    double offset_y = delta_y/2.0;
    for (unsigned idx=0; idx<numY; idx++)
    {
        for(unsigned jdx=0; jdx<numX; jdx++)
        {
            auto locX0 = offset_x - delta_x/2.0;
            auto locX1 = offset_x + delta_x/2.0;
            auto locY0 = offset_y - delta_y/2.0;
            auto locY1 = offset_y + delta_y/2.0;

            auto id_offset = 4* (jdx + numX*idx);
            locations[id_offset] = Point(locX0, locY0);
            locations[id_offset + 1] = Point(locX1, locY0);
            locations[id_offset + 2] = Point(locX1, locY1);
            locations[id_offset + 3] = Point(locX0, locY1);

            offset_x += delta_x;
        }
        offset_x = delta_x/2.0;
        offset_y += delta_y;
    }

    EdgeIds edge_ids(5 * numX * numY);
    for (unsigned idx=0; idx<numY; idx++)
    {
        for(unsigned jdx=0; jdx<numX; jdx++)
        {
            unsigned node_offset = 4 * (jdx + numX * idx);
            auto id_offset = 5* (jdx + numX*idx);
            edge_ids[id_offset] = {node_offset, node_offset + 1};
            edge_ids[id_offset + 1] = {node_offset + 1, node_offset + 2};
            edge_ids[id_offset + 2] = {node_offset + 2, node_offset + 3};
            edge_ids[id_offset + 3] = {node_offset + 3, node_offset};
            edge_ids[id_offset + 4] = {node_offset + 2, node_offset};
        }
    }

    FaceIds face_ids(2 *numX * numY);
    for (unsigned idx=0; idx<numY; idx++)
    {
        for(unsigned jdx=0; jdx<numX; jdx++)
        {
            unsigned edge_offset = 5 * (jdx + numX * idx);
            unsigned face_offset = 2 * (jdx + numX * idx);
            face_ids[face_offset] = {edge_offset, edge_offset + 1, edge_offset + 4};
            face_ids[face_offset + 1] = {edge_offset + 4, edge_offset + 2, edge_offset + 3};
        }
    }

    return MeshBuilder::buildTriMesh(locations, edge_ids, face_ids);
}

std::unique_ptr<LineMesh> MeshPrimitives::buildExplodedGridAsLineMesh(unsigned numX, unsigned numY)
{
    double delta_x = 1.0/double(numX);
    double delta_y = 1.0/double(numY);

    VecPoints locations (4 * numX * numY);
    double offset_x = delta_x/2.0;
    double offset_y = delta_y/2.0;
    for (unsigned idx=0; idx<numY; idx++)
    {
        for(unsigned jdx=0; jdx<numX; jdx++)
        {
            auto locX0 = offset_x - delta_x/2.0;
            auto locX1 = offset_x + delta_x/2.0;
            auto locY0 = offset_y - delta_y/2.0;
            auto locY1 = offset_y + delta_y/2.0;

            auto id_offset = 4* (jdx + numX*idx);
            locations[id_offset] = Point(locX0, locY0);
            locations[id_offset + 1] = Point(locX1, locY0);
            locations[id_offset + 2] = Point(locX1, locY1);
            locations[id_offset + 3] = Point(locX0, locY1);

            offset_x += delta_x;
        }
        offset_x = delta_x/2.0;
        offset_y += delta_y;
    }

    EdgeIds edge_ids(4 * numX * numY);
    for (unsigned idx=0; idx<numY; idx++)
    {
        for(unsigned jdx=0; jdx<numX; jdx++)
        {
            unsigned node_offset = 4 * (jdx + numX * idx);
            auto id_offset = 4* (jdx + numX*idx);
            edge_ids[id_offset] = {node_offset, node_offset + 1};
            edge_ids[id_offset + 1] = {node_offset + 1, node_offset + 2};
            edge_ids[id_offset + 2] = {node_offset + 2, node_offset + 3};
            edge_ids[id_offset + 3] = {node_offset + 3, node_offset};
        }
    }

    return MeshBuilder::buildLineMesh(locations, edge_ids);
}
