package cz.fidentis.analyst.scene;

import cz.fidentis.analyst.face.HumanFace;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

/**
 * Abstract class for ...
 * 
 * @author Radek Oslejsek
 * @author Dominik Racek
 */
public class Scene {
    
    private final List<HumanFace> faces = new ArrayList<>();
    private final List<DrawableFace> drawableFaces = new ArrayList<>();
    private final List<DrawableFeaturePoints> drawableFeaturePoints = new ArrayList<>();
    private final List<DrawablePlane> drawableSymmetryPlanes = new ArrayList<>();
    private final List<DrawablePlane> drawableCuttingPlanes = new ArrayList<>();
    private final List<DrawablePlane> drawableMirrorPlanes = new ArrayList<>();
    private final List<Drawable> otherDrawables = new ArrayList<>();
    
    /**
     * Constructor for single face analysis.
     * 
     * @param face Human face to be analyzed
     * @throws IllegalArgumentException if the {@code face} is {@code null}
     */
    public Scene(HumanFace face) {
        if (face == null) {
            throw new IllegalArgumentException("face");
        }

        faces.add(face);
        drawableFaces.add(new DrawableFace(face));
        if (face.getFeaturePoints() != null) {
            drawableFeaturePoints.add(new DrawableFeaturePoints(face.getFeaturePoints()));
        } else {
            drawableFeaturePoints.add(null);
        }
        drawableSymmetryPlanes.add(null);
        drawableCuttingPlanes.add(null);
        drawableMirrorPlanes.add(null);
        
        setDefaultColors();
    }
    
    /**
     * Constructor for one-to-one analysis.
     * 
     * @param primary Primary face to be analyzed
     * @param secondary Secondary face to be analyzed
     * @throws IllegalArgumentException if some face is {@code null}
     */
    public Scene(HumanFace primary, HumanFace secondary) {
        if (primary == null) {
            throw new IllegalArgumentException("primary");
        }
        if (secondary == null) {
            throw new IllegalArgumentException("secondary");
        }
        
        faces.add(primary);
        faces.add(secondary);
        
        drawableFaces.add(new DrawableFace(primary));
        drawableFaces.add(new DrawableFace(secondary));
        
        if (primary.getFeaturePoints() != null) {
            drawableFeaturePoints.add(new DrawableFeaturePoints(primary.getFeaturePoints()));
        } else {
            drawableFeaturePoints.add(null);
        }
        if (secondary.getFeaturePoints() != null) {
            drawableFeaturePoints.add(new DrawableFeaturePoints(secondary.getFeaturePoints()));
        } else {
            drawableFeaturePoints.add(null);
        }

        drawableSymmetryPlanes.add(null);
        drawableCuttingPlanes.add(null);
        drawableMirrorPlanes.add(null);

        drawableSymmetryPlanes.add(null);
        drawableCuttingPlanes.add(null);
        drawableMirrorPlanes.add(null);

        setDefaultColors();
    }
    
    /**
     * Sets default colors of faces and feature points
     */
    public final void setDefaultColors() {
        for (int i = 0; i < getNumFaces(); i++) {
            if (drawableFaces.get(i) != null) {
                drawableFaces.get(i).setColor((i == 0) ? DrawableFace.SKIN_COLOR_PRIMARY : DrawableFace.SKIN_COLOR_SECONDARY); 
            }
            if (drawableFeaturePoints.get(i) != null) {
                drawableFeaturePoints.get(i).setColor(getColorOfFeaturePoints((i == 0) ? DrawableFace.SKIN_COLOR_PRIMARY : DrawableFace.SKIN_COLOR_SECONDARY));
                drawableFeaturePoints.get(i).resetAllColorsToDefault();
            }
        }
    }
    
    /**
     * Returns number of faces in the scene.
     * @return number of faces in the scene
     */
    public int getNumFaces() {
        return faces.size();
    }
    
    /**
     * Returns human face.
     * 
     * @param index Index of the face
     * @return drawable face or {@code null}
     */
    public HumanFace getHumanFace(int index) {
        return (index >= 0 && index < getNumFaces()) ? faces.get(index) : null;
    }
    
    /**
     * Returns drawable face.
     * 
     * @param index Index of the face
     * @return drawable face or {@code null}
     */
    public DrawableFace getDrawableFace(int index) {
        return (index >= 0 && index < getNumFaces()) ? drawableFaces.get(index) : null;
    }
    
    /**
     * Returns drawable feature points.
     * 
     * @param index Index of the face
     * @return drawable face or {@code null}
     */
    public DrawableFeaturePoints getDrawableFeaturePoints(int index) {
        return (index >= 0 && index < getNumFaces()) ? drawableFeaturePoints.get(index) : null;
    }
    
    /**
     * Returns drawable symmetry plane.
     * 
     * @param index Index of the face
     * @return drawable plane or {@code null}
     */
    public DrawablePlane getDrawableSymmetryPlane(int index) {
        return (index >= 0 && index < getNumFaces()) ? drawableSymmetryPlanes.get(index) : null;
    }
    
    /**
     * Sets the drawable symmetry plane.
     * 
     * @param index Index of the face
     * @param sPlane New symmetry plane
     */
    public void setDrawableSymmetryPlane(int index, DrawablePlane sPlane) {
        if (index >= 0 && index < getNumFaces() && sPlane != null) {
            this.drawableSymmetryPlanes.set(index, sPlane);
        }
    }

    /**
     * Helper function for showing or hiding all symmetry planes
     *
     * @param show determines whether to hide or show the planes
     */
    private void hideShowSymmetryPlanes(boolean show) {
        for (int i = 0; i < drawableSymmetryPlanes.size(); ++i) {
            if (drawableSymmetryPlanes.get(i) != null) {
                if (show) {
                    drawableSymmetryPlanes.get(i).show();
                } else {
                    drawableSymmetryPlanes.get(i).hide();
                }
            }
        }
    }

    /**
     * Show all symmetry planes
     */
    public void showSymmetryPlanes() {
        hideShowSymmetryPlanes(true);
    }

    /**
     * Hide all symmetry planes
     */
    public void hideSymmetryPlanes() {
        hideShowSymmetryPlanes(false);
    }

    /**
     * Returns drawable cutting plane.
     *
     * @param index Index of the face
     * @return drawable plane or {@code null}
     */
    public DrawablePlane getDrawableCuttingPlane(int index) {
        return (index >= 0 && index < getNumFaces()) ? drawableCuttingPlanes.get(index) : null;
    }

    /**
     * Sets the drawable cutting plane.
     *
     * @param index Index of the face
     * @param cPlane New cutting plane
     */
    public void setDrawableCuttingPlane(int index, DrawablePlane cPlane) {
        if (index >= 0 && index < getNumFaces() && cPlane != null) {
            this.drawableCuttingPlanes.set(index, cPlane);
        }
    }

    /**
     * Helper function for showing or hiding all cutting planes
     *
     * @param show determines whether to hide or show the planes
     */
    private void hideShowCuttingPlanes(boolean show) {
        for (int i = 0; i < drawableCuttingPlanes.size(); ++i) {
            if (drawableCuttingPlanes.get(i) != null) {
                if (show) {
                    drawableCuttingPlanes.get(i).show();
                } else {
                    drawableCuttingPlanes.get(i).hide();
                }
            }
        }
    }

    /**
     * Show all cutting planes
     */
    public void showCuttingPlanes() {
        hideShowCuttingPlanes(true);
    }

    /**
     * Hide all cutting planes
     */
    public void hideCuttingPlanes() {
        hideShowCuttingPlanes(false);
    }

    /**
     * Returns drawable mirror plane.
     *
     * @param index Index of the face
     * @return drawable face or {@code null}
     */
    public DrawablePlane getDrawableMirrorPlane(int index) {
        return (index >= 0 && index < getNumFaces()) ? drawableMirrorPlanes.get(index) : null;
    }

    /**
     * Sets the drawable mirror plane.
     *
     * @param index Index of the face
     * @param mPlane New mirror plane
     */
    public void setDrawableMirrorPlane(int index, DrawablePlane mPlane) {
        if (index >= 0 && index < getNumFaces() && mPlane != null) {
            this.drawableMirrorPlanes.set(index, mPlane);
        }
    }

    /**
     * Helper function for showing or hiding all mirror planes
     *
     * @param show determines whether to hide or show the planes
     */
    private void hideShowMirrorPlanes(boolean show) {
        for (int i = 0; i < drawableMirrorPlanes.size(); ++i) {
            if (drawableMirrorPlanes.get(i) != null) {
                if (show) {
                    drawableMirrorPlanes.get(i).show();
                } else {
                    drawableMirrorPlanes.get(i).hide();
                }
            }
        }
    }

    /**
     * Show all mirror planes
     */
    public void showMirrorPlanes() {
        hideShowMirrorPlanes(true);
    }

    /**
     * Hide all mirror planes
     */
    public void hideMirrorPlanes() {
        hideShowMirrorPlanes(false);
    }

    /**
     * Adds new drawable object to the scene.
     * 
     * <p>
     * Note: This drawable object is ignored in the {@link setDefaultColors} method.
     * The caller must take care of the colors of the object themself.
     * </p>
     * 
     * @param otherDrawable Drawable object to be added to the scene
     */
    public void addOtherDrawable(Drawable otherDrawable) {
        otherDrawables.add(otherDrawable);
    }
    
    /**
     * Returns all drawable objects.
     * 
     * @return all drawable objects.
     */
    public List<Drawable> getAllDrawables() {
        List<Drawable> ret = new ArrayList<>();
        ret.addAll(this.drawableFaces);
        ret.addAll(this.drawableFeaturePoints);
        ret.addAll(this.drawableSymmetryPlanes);
        ret.addAll(this.drawableCuttingPlanes);
        ret.addAll(this.drawableMirrorPlanes);
        ret.addAll(this.otherDrawables);
        while (ret.remove(null)) {}
        return ret;
    }
    
    private Color getColorOfFeaturePoints(Color origColor) {
        return new Color(
                Math.min(255, origColor.getRed() + 40),
                Math.min(255, origColor.getGreen() + 40),
                Math.min(255, origColor.getBlue() + 40)
        );
    }
}
