/**
 * C++ fast marching on point cloud Library
 * Copyright (c) 2000-2026 Eric Bechet <eric at bechet dot ca>
 * 
 * This file is part of the C++ Mesh Generation Library.
 *  See the NOTICE & LICENSE files for conditions.
 * 
 * Version 0.2 (July 2024)
 */

#ifndef FMPCFIELD_H
#define FMPCFIELD_H

#include <map>
#include <string>
#include <unordered_set>
#include <functional>
#include <limits>
#include "nutil.h"
#include "GmshGlobal.h"
#include "gmshElement.h"
#include "FMPointCloud.h"

namespace FM
{

struct node_data {
    double dist; // Distance at node
    double grad[3]; // Gradient at node
};

typedef std::function<double(double,double,double)> realFunction;

class FM0PC;

class FMPCField
{
    protected:

    /// Data stored for each node
    std::map<int,node_data> node_info;

    /// Name of the data set
    std::string name;

    /// Pointer to the point cloud
    FM::FMPointCloud* PC;

    /// Order of the results (0 = FM0 thus no gradient, 1 = FM1)
    int order;

    public:

    /* CONSTRUCTORS */

    /// @brief Constructor
    /// @param PC_ Point cloud (node geometric information)
    /// @param name_ Name of the data field (default: "Results")
    FMPCField(FM::FMPointCloud* PC_,std::string name_ = "Results") { PC=PC_; name = name_;}

    /* GET SET FUNCTIONS*/

    /// @brief Set order of the data stored (0=FM0, 1=FM1)
    /// @param order_ 0=FM0 (only distance stored); 1=FM1 (gradients also stored)
    void setOrder(int order_) { order=order_;}

    int getOrder() { return order; }

    /// @brief Get distance at specific node
    /// @param i Index of the node
    /// @return Distance at node
    double getDistance(int i) { return node_info[i].dist; }

    /// @brief Set distance at a node (gradient not stored)
    /// @param i Index of the node to set
    /// @param dist_ Distance to set
    void setDistance(int i, double dist_) {
        if(node_info.find(i)==node_info.end()){ node_data newData = node_data(); node_info[i] = newData; }
        node_info[i].dist = dist_;
        //node_info.insert(std::pair<int,node_data>(i,newData));
    }

    void setGradient(int i, npoint3& grad_) {
        if(node_info.find(i)==node_info.end()){ node_data newData = node_data(); node_info[i] = newData; }
        for(int j=0; j<3; j++) node_info[i].grad[j] = grad_[j];
    }

    void setGradient(int i, double &x, double &y, double &z){
        if(node_info.find(i)==node_info.end()){ node_data newData = node_data(); node_info[i] = newData; }
        node_info[i].grad[0] = x;
        node_info[i].grad[1] = y;
        node_info[i].grad[2] = z;
    }

    void getGradient(int i, npoint3& grad_){
        if(node_info.find(i)!=node_info.end()){ 
            for(int j=0; j<3; j++){
                grad_[j] = node_info[i].grad[j];
            }
        } else {
            grad_ = npoint3();
        }
    }

    std::vector<int> getSetNodeIndexes(){
        std::vector<int> idxs;
        for(std::map<int,node_data>::iterator it=node_info.begin(); it != node_info.end(); it++){
            idxs.push_back(it->first);
        }
        return idxs;
    }

    std::string getName() { return name; }

    /* OPERATIONS */

    FMPCField operator+(FMPCField& f2);

    FMPCField operator-(FMPCField& f2);

    /* INTERPOLATING FUNCTIONS */

    /// @brief Interpolation order 0
    /// @details Dijsktra distance from the patch of node the closest
    /// @param pt Query point
    /// @return Dijsktra distance at the query point
    double distanceAtPoint0(npoint3 pt) const;

    /// @brief Interpolation order 1 (Barycentric interpolation)
    /// @param pt Query point
    /// @return Distance from linear (barycentric) interpolation at the query point
    double distanceAtPoint1(npoint3 pt) const;

    /// @brief Calling FM to find distance at point
    /// @param pt Query point
    /// @param algo Object algo to compute distance
    /// @return Distance as if pt was a node (computation depends on algo object)
    // double distanceAtPoint2(npoint3 pt, FMPCAlgo& algo);


    /* COMPUTING ERROR FUNCTIONS */

    double meanErrorLine(npoint3 pt0, npoint3 pt1, realFunction dRef, double fDist) const;

    double errorPoint(npoint3 pt, realFunction dRef) const;

    double errorRelNode(int idx, realFunction dRef) const;

    double errorAbsNode(int idx, realFunction dRef) const;

    double meanErrorRel(realFunction dRef, double* std = nullptr) const;

    double meanErrorAbs(realFunction dRef, double* std = nullptr) const;

    double rmsErrorRel(realFunction dRef) const;

    double rmsErrorAbs(realFunction dRef) const;

    double rmsErrorRel(realFunction dRef, std::vector<int>& pts) const;

    double rmsErrorAbs(realFunction dRef, std::vector<int>& pts) const;

    double maxErrorAbs(realFunction dRef, std::vector<int>& pts, double* other = nullptr) const;

    /* DISPLAY FUNCTIONS */

    /// @brief Fill data container for displaying
    /// @details Set node with colorbar (green to red) and set gradients if order 1
    /// @param data Data container to fill
    void display(data_container &data) const;

    void displayGradients(data_container &data, std::vector<int> *idxs = nullptr) const;

    /* EXPORT FUNCTONS */

    /// @brief Export data in a csv file (gradients also exported if order 1)
    /// @param file Name of the file (default: [name of the datafield].csv)
    void exportCSV(std::string file="") const;

    /// @brief Export data in a msh file (no gradient exported)
    /// @param file Name of the file (default: [name of the datafield].msh)
    /// @warning FMPointCloud must contained a model (obtained from a msh file)
    void exportMSH(std::string file="", bool addGrad=false, std::string nameField="Distance") const;

    void appendMSH(std::string file) const;

    void exportErrorRelMSH(realFunction dRef, std::string file="") const;

    void exportErrorAbsMSH(realFunction dRef, std::string file="") const;

    void exportErrorAbsCSV(realFunction dRef, std::string file="") const;
};

}

#endif // FMPCFIELD_H
