package cz.fidentis.analyst.procrustes;

import cz.fidentis.analyst.face.HumanFace;
import cz.fidentis.analyst.feature.FeaturePoint;
import cz.fidentis.analyst.mesh.core.MeshModel;
import cz.fidentis.analyst.mesh.core.MeshPoint;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

/**
 * Holds important face attributes that are required for procrustes analysis.
 *
 * @author Jakub Kolman
 */
public class ProcrustesAnalysisFaceModel {

    private final HumanFace humanFace;
    private HashMap<Integer, FeaturePoint> featurePointsMap;        // sorted by feature point type
    private List<MeshPoint> vertices;
    private MeshModel meshModel;
    private final HashMap<Integer, Integer> featurePointTypeCorrespondence;

    /**
     * constructor creating inner attributes used in procrustes analysis from face
     *
     * @param face
     */
    public ProcrustesAnalysisFaceModel(HumanFace face) {
        this.humanFace = face;
        // getFeaturePoints() returns unmodifiable List. To sort it we need to make copy first
        List<FeaturePoint> modifiableFeaturePointList = new ArrayList<>(face.getFeaturePoints());

        // To be able to move vertices we have to make a copy of them because  getFacets() returns unmodifiable List
        List<MeshPoint> modifiableVertices = new ArrayList<>(face.getMeshModel().getFacets().get(0).getVertices());

        this.vertices = modifiableVertices;
        this.meshModel = face.getMeshModel();
        this.featurePointsMap = createFeaturePointMap(
                sortListByFeaturePointType(modifiableFeaturePointList));
        this.featurePointTypeCorrespondence = createFeaturePointTypeCorrespondence(face.getFeaturePoints());
    }

    /**
     * sets feature points map and also sets feature point on human face for visualisation
     *
     * @param featurePointsMap
     */
    public void setFeaturePointsMap(HashMap<Integer, FeaturePoint> featurePointsMap) {
        this.featurePointsMap = featurePointsMap;
        this.humanFace.setFeaturePoints(getFeaturePointValues());
    }

    public MeshModel getMeshModel() {
        return meshModel;
    }

    public List<FeaturePoint> getFeaturePointValues() {
        return new ArrayList<>(this.getFeaturePointsMap().values());
}

    public void setVertices(List<MeshPoint> vertices) {
        this.vertices = vertices;
    }

    public List<MeshPoint> getVertices() {
        return vertices;
    }

    public HashMap<Integer, FeaturePoint> getFeaturePointsMap() {
        return featurePointsMap;
    }

    public HashMap<Integer, Integer> getFeaturePointTypeCorrespondence() {
        return featurePointTypeCorrespondence;
    }

    /**
     * Creates corresponding key value pair of matrix row index and a feature point type value.
     * It is used so we can get back type of feature point after working with matrices where the type is not stored,
     * but instead are used matrix indexes.
     *
     * @param fpList
     * @return
     */
    private HashMap<Integer, Integer> createFeaturePointTypeCorrespondence(List<FeaturePoint> fpList) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < fpList.size(); i++) {
            map.put(i, fpList.get(i).getFeaturePointType().getType());
        }
        return map;
    }

    /**
     * Sorts List by featurePoint type property
     *
     * @param featurePointList
     * @return ordered list of feature points by type
     */
    protected List<FeaturePoint> sortListByFeaturePointType(List<FeaturePoint> featurePointList) {
        Collections.sort(
                featurePointList, Comparator.comparingInt(fp -> fp.getFeaturePointType().getType())
        );
        return featurePointList;
    }

    /**
     * Creates feature point hash map for internal use in Procrustes Analysis.
     *
     * Map key is feature point type (Integer).
     * Value is feature point itself.
     *
     * @param featurePointList
     * @return
     */
    private HashMap<Integer, FeaturePoint> createFeaturePointMap(List<FeaturePoint> featurePointList) {
        HashMap<Integer, FeaturePoint> map = new HashMap<>();
        for (FeaturePoint fp: featurePointList) {
            map.put(fp.getFeaturePointType().getType(), fp);
        }
        return map;
    }

}
