#pragma once

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

#include <graphene/surface_mesh/data_structure/Surface_mesh.h>

static bool texture_boundary_empty(const cv::Mat& face_id_tex)
{
    const int width  = face_id_tex.cols;
    const int height = face_id_tex.rows;

    for (int col = 0; col < width; ++col)
    {
        if (face_id_tex.at<int32_t>(0, col) >= 0)
            return false;

        if (face_id_tex.at<int32_t>(height - 1, col) >= 0)
            return false;
    }

    for (int row = 0; row < height; ++row)
    {
        if (face_id_tex.at<int32_t>(row, 0) >= 0)
            return false;

        if (face_id_tex.at<int32_t>(row, width - 1) >= 0)
            return false;
    }

    return true;
}

//
// Creates a texture that encodes the face id corresponding to each texel
// (0 if texel does not correspond to any face)
//
static bool
face_id_map(const graphene::surface_mesh::Surface_mesh& mesh, cv::Mat& result)
{
    const unsigned int rows = result.rows;
    const unsigned int cols = result.cols;
    const unsigned int dim  = rows;

    if (rows != cols || rows == 0 || result.type() != CV_32SC1)
    {
        std::cerr << "[ERROR] "<< __FUNCTION__ << ": rows != cols || rows == 0 || type != S32C1" << std::endl;
        return false;
    }
    result.setTo(cv::Scalar(-1));

    auto texcoords = mesh.get_halfedge_property<graphene::Texture_coordinate>("h:texcoord");
    if (!texcoords)
    {
        std::cerr << "[ERROR] " << __FUNCTION__ << ": no tex coords." << std::endl;
        return false;
    }

    for (auto f : mesh.faces())
    {
        // texture coordinates
        std::vector<graphene::Texture_coordinate> tex_coords;
        for (auto he : mesh.halfedges(f))
        {
            graphene::Texture_coordinate t = texcoords[he];
            tex_coords.push_back(t);
        }

        // corners
        const graphene::Vec2f corner_1 = graphene::Vec2f(tex_coords[0][0], 1.0 - tex_coords[0][1]) * static_cast<double>(dim);
        const graphene::Vec2f corner_2 = graphene::Vec2f(tex_coords[1][0], 1.0 - tex_coords[1][1]) * static_cast<double>(dim);
        const graphene::Vec2f corner_3 = graphene::Vec2f(tex_coords[2][0], 1.0 - tex_coords[2][1]) * static_cast<double>(dim);
        cv::Point corner_points[1][3];
        corner_points[0][0] = cv::Point(std::round(corner_1[0]), std::round(corner_1[1]));
        corner_points[0][1] = cv::Point(std::round(corner_2[0]), std::round(corner_2[1]));
        corner_points[0][2] = cv::Point(std::round(corner_3[0]), std::round(corner_3[1]));

        int32_t val(f.idx());

        // Rasterize triangle via opencv
        const cv::Point* ppt[1] = { corner_points[0] };
        int npt[] = { 3 };
        cv::fillPoly(result, ppt, npt, 1, cv::Scalar(val), cv::LINE_8);
    }

    return texture_boundary_empty(result);
}

static void
mask_from_face_id_map(cv::Mat& face_id_texture, cv::Mat& mask)
{
    int rows = face_id_texture.rows;
    int cols = face_id_texture.cols;

    mask = cv::Mat::zeros(rows, cols, CV_8UC1);

    for (int row = 0; row < rows; ++row)
        for (int col = 0; col < cols; ++col)
            if (face_id_texture.at<int32_t>(row, col) > -1)
                mask.at<uchar>(row, col) = 255;
}
