package cz.fidentis.analyst.scene;

import com.jogamp.opengl.GL2;
import com.jogamp.opengl.glu.GLU;
import com.jogamp.opengl.glu.GLUquadric;
import cz.fidentis.analyst.feature.FeaturePoint;
import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Drawable feature points.
 * 
 * @author Radek Oslejsek
 * @author Daniel Schramm
 */
public class DrawableFeaturePoints extends Drawable {
    
    public static final Color DEFAULT_COLOR = Color.LIGHT_GRAY;
    public static final double DEFAULT_SIZE = 3f;
    
    private static final GLU GLU_CONTEXT = new GLU();
    
    /* feature points */
    private final List<FeaturePoint> featurePoints;
    
    /**
     * feature points with color different from the default color
     */
    private final Map<Integer, Color> specialColors = new HashMap<>();
    
    /**
     * feature points with size different from the default size
     */
    private final Map<Integer, Double> specialSizes = new HashMap<>();
    
    /**
     * feature points rendered differently than the default render mode
     */
    private final Map<Integer, Integer> specialRenderModes = new HashMap<>();
    
    /**
     * Constructor.
     * 
     * @param featurePoints Feature points
     */
    public DrawableFeaturePoints(List<FeaturePoint> featurePoints) {
        this.featurePoints = new ArrayList<>(featurePoints);
        setColor(DEFAULT_COLOR);
    }
    
    /**
     * Returns color of the feature point.
     * 
     * @param index Index of the feature point
     * @return The color or {@code null}
     */
    public Color getColor(int index) {
        if (index < 0 || index >= featurePoints.size()) {
            return null;
        }
        if (specialColors.containsKey(index)) {
            return specialColors.get(index);
        } else {
            return getColor();
        }
    }
    
    /**
     * Sets color of the feature point.
     * 
     * @param index Index of the feature point
     * @param color New color of the feature point
     */
    public void setColor(int index, Color color) {
        if (index < 0 || index >= featurePoints.size() || color == null) {
            return;
        }
        if (color.equals(getColor())) {
            specialColors.remove(index);
        } else {
            specialColors.put(index, color);
        }
    }
    
    /**
     * Removes (possible) special color of the feature point.
     * 
     * @param index Index of the feature point
     */
    public void resetColorToDefault(int index) {
        setColor(index, getColor());
    }
    
    /**
     * Removes all individual colors of feature points.
     */
    public void resetAllColorsToDefault() {
        this.specialColors.clear();
    }
    
    /**
     * Returns size of the feature point.
     * 
     * @param index Index of the feature point
     * @return The size or {@code null}
     */
    public Double getSize(int index) {
        if (index < 0 || index >= featurePoints.size()) {
            return null;
        }
        if (specialSizes.containsKey(index)) {
            return specialSizes.get(index);
        } else {
            return DEFAULT_SIZE;
        }
    }
    
    /**
     * Sets size of the feature point.
     * 
     * @param index Index of the feature point
     * @param size New size of the feature point
     */
    public void setSize(int index, double size) {
        if (index < 0 || index >= featurePoints.size()) {
            return;
        }
        if (size == DEFAULT_SIZE) {
            specialSizes.remove(index);
        } else {
            specialSizes.put(index, size);
        }
    }
    
    /**
     * Removes (possible) special size of the feature point.
     * 
     * @param index Index of the feature point
     */
    public void resetSizeToDefault(int index) {
        setSize(index, DEFAULT_SIZE);
    }
    
    /**
     * Removes all individual sizes of feature points.
     */
    public void resetAllSizesToDefault() {
        specialSizes.clear();
    }
    
    /**
     * Returns render mode of the feature point.
     * 
     * @param index Index of the feature point
     * @return The render mode or {@code null}
     */
    public Integer getRenderMode(int index) {
        if (index < 0 || index >= featurePoints.size()) {
            return null;
        }
        if (specialRenderModes.containsKey(index)) {
            return specialRenderModes.get(index);
        } else {
            return getRenderMode();
        }
    }
    
    /**
     * Sets render mode of the feature point.
     * The render mode must be {@link GL2#GL_FILL}, {@link GL2#GL_LINE}
     * or {@link GL2#GL_POINT}.
     * 
     * @param index Index of the feature point
     * @param renderMode New render mode of the feature point
     * @throws IllegalArgumentException if renderMode isn't {@code GL_FILL},
     *         {@code GL_LINE} or {@code GL_POINT}
     */
    public void setRenderMode(int index, int renderMode) {
        if (renderMode != GL2.GL_FILL &&
                renderMode != GL2.GL_LINE &&
                renderMode != GL2.GL_POINT) {
            throw new IllegalArgumentException("renderMode");
        }
        if (index < 0 || index >= featurePoints.size()) {
            return;
        }
        if (renderMode == getRenderMode()) {
            specialRenderModes.remove(index);
        } else {
            specialRenderModes.put(index, renderMode);
        }
    }
    
    /**
     * Removes (possible) special render mode of the feature point.
     * 
     * @param index Index of the feature point
     */
    public void resetRenderModeToDefault(int index) {
        setRenderMode(index, getRenderMode());
    }
    
    /**
     * Removes all individual render modes of feature points.
     */
    public void resetAllRenderModesToDefault() {
        specialRenderModes.clear();
    }

    @Override
    protected void renderObject(GL2 gl) {
        float[] rgba = {
            getColor().getRed() / 255f, 
            getColor().getGreen() / 255f, 
            getColor().getBlue() / 255f, 
            getTransparency()
        };
        gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE, rgba, 0); // set default color
        
        for (int i = 0; i < featurePoints.size(); i++) {
            FeaturePoint fp = featurePoints.get(i);
            
            Color specialColor = specialColors.get(i);
            if (specialColor != null) {
                float[] tmpRGB = {specialColor.getRed() / 255f, specialColor.getGreen() / 255f, specialColor.getBlue() / 255f, getTransparency()};
                gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE, tmpRGB, 0);
            }
            
            Integer specialRenderMode = specialRenderModes.get(i);
            if (specialRenderMode != null) {
                gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, specialRenderMode);
            }
            
            gl.glPushMatrix(); 
            gl.glTranslated(fp.getX(), fp.getY(), fp.getZ());
            GLUquadric center = GLU_CONTEXT.gluNewQuadric();
            GLU_CONTEXT.gluQuadricDrawStyle(center, GLU.GLU_FILL);
            GLU_CONTEXT.gluQuadricNormals(center, GLU.GLU_FLAT);
            GLU_CONTEXT.gluQuadricOrientation(center, GLU.GLU_OUTSIDE);
            GLU_CONTEXT.gluSphere(center, specialSizes.getOrDefault(i, DEFAULT_SIZE), 16, 16);
            GLU_CONTEXT.gluDeleteQuadric(center);
            gl.glPopMatrix();
    
            if (specialColor != null) {
                gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE, rgba, 0); // set default color
            }
            if (specialRenderMode != null) {
                gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, getRenderMode()); // set default render mode
            }
        }
    }
    
    
       
    /**
     * @return {@link List} of {@link FeaturePoint}
     */
    public List<FeaturePoint> getFeaturePoints() {
        return featurePoints;
    }

    /**
     * Sets feature points
     * @param featurePoints Feature points
     */
    /*
    public void setFeaturePoints(List<FeaturePoint> featurePoints) {
        this.featurePoints = featurePoints;
        List<Color> colors = new ArrayList<>();
        featurePoints.forEach((_item) -> {
            colors.add(getColor());
        });
        this.setFeaturePointsColor(colors);
    } 
    */
    
}
