package sema;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.Vector;


/**
 * Graphics and parameters.
 * This class is in charge of all the drawing within the simulation.
 * An element of the world must use this class to draw itself,
 * because Drawing is the only class in Sema which knows the graphics context.
 * The main advantage is that the elements don't have to convert dimensions between simulated units and pixels.
 */
public class Drawing {
    
    /**
     * New graphical interface for the simulation.
     */
    public Drawing(float pixelsByUnitZoom1x, float boxLength, float minZoom, float maxZoom, float minTfactor, float maxTfactor, float tFactor, float zoomFactor, int virtualD, int realD, float centerX, float centerY, int boxFactor) {
        this.pixelsByUnitZoom1x = pixelsByUnitZoom1x;
        this.pixelsByBoxZoom1x = pixelsByUnitZoom1x*boxLength;
        this.minZoom = minZoom;
        this.maxZoom = maxZoom;
        this.minTfactor = minTfactor;
        this.maxTfactor = maxTfactor;
        this.tFactor = tFactor;
        this.virtualDelay = virtualD;
        this.realDelay = realD;
        this.zoom = zoomFactor;
        this.centerX = centerX;
        this.centerY = centerY;
        this.boxFactor = boxFactor;
    }
    
    private Graphics2D g;
    
    /**
     * Draws the world.
     * Takes in arguments the graphics context and the boxes to refresh.
     */
    public void draw(Graphics2D g, Vector boxToRefresh){
        this.g = g;
        g.setColor(Color.WHITE);
        TreeSet elementsToDraw = new TreeSet();
        for(int i = 0; i<boxToRefresh.size(); i++) { //dessin des cases
            Box bi = (Box) boxToRefresh.get(i);
            bi.draw();
            Iterator it = bi.getContents().iterator();
            while (it.hasNext()) { //pour chaque élément de la case, on le déclare à dessiner
                elementsToDraw.add(it.next());
            }
        }
        
        Iterator it = elementsToDraw.iterator();
        while (it.hasNext()) { //on dessine les éléments
            ((Element)it.next()).draw();
        }
    }
    
    
    /**
     * Default boxfactor to use for the simulation.
     * The box factor allows to have a more accurate
     * simulation by increasing the number of boxes on the map,
     * with the same dimensions of the map (so, xBoxes and yBoxes
     * and boxLength parameters of the map are linked to this one).
     */
    public final float boxFactor;
    
    /**
     * Pixels by simulated unit, when zoom=1.
     */
    public final float pixelsByUnitZoom1x;
    
    /**
     * Pixels by square box side, when zoom=1.
     */
    public final float pixelsByBoxZoom1x;
    
    /**
     * X-coordinate to center the map by default.
     */
    public final float centerX;
    
    /**
     * Y-coordinate to center the map by default.
     */
    public final float centerY;
    
    /**
     * Min value of the zoom slider in the interface.
     */
    public final float minZoom;
    
    /**
     * Max value of the zoom slider in the interface.
     */
    public final float maxZoom;
    
    /**
     * Max value of the time factor slider in the interface.
     */
    public final float minTfactor;
    
    /**
     * Max value of the time factor slider in the interface.
     */
    public final float maxTfactor;
    
    /**
     * Default temporal factor.
     */
    public final float tFactor;
    
    /**
     * Default virtual delay (in ms) between to displays of the world in the graphical interface.
     */
    public final float virtualDelay;
    
    /**
     * Default virtual delay (in ms) between to displays of the world in the graphical interface.
     * As the system time returned by {@link System#currentTimeMillis()} is an integer (a long),
     * it doesn't need to be a float.
     */
    public final int realDelay;
    
    private float pbu; //= pixelsByUnit
    private float pbb; //= pixelsByBox
    
    private float zoom; //facteur de zoom, en 1x
    
    /**
     * Sets the current zoom.
     */
    public void setZoom(float z){
        zoom=z;
        pbb = zoom*pixelsByBoxZoom1x;
        pbu = zoom*pixelsByUnitZoom1x;
    }
    
    /**
     * Current zoom.
     */
    public float getZoom(){
        return zoom;
    }
    
    /**
     * Pixels by square box side (with current zoom).
     */
    public float getPixelsByBox() {
        return(pbb);
    }
    
    /**
     * Pixels by simulated unit (with current zoom).
     */
    public float getPixelsByUnit() {
        return(pbu);
    }
    
    private int l2p(float length) { //l2p = lengthToPixels: nombre de pixels (arrondi) pour représenter une longueur donnée
        return((int)Math.round(pbu*length));
    }
    
    private int l2dp(float length) { //l2dp = lengthToDownPixels: nombre de pixels (arrondi vers le bas) pour représenter une longueur donnée
        return((int)Math.floor(pbu*length));
    }
    private int l2up(float length) { //l2up = lengthToUpPixels: nombre de pixels (arrondi vers le haut) pour représenter une longueur donnée
        return((int)Math.ceil(pbu*length));
    }
    
    /**
     * Sets the color of drawing.
     */
    public void setColor(Color c) {
        g.setColor(c);
    }
    
    /**
     * Draws a line. The arguments are in simulated unit.
     */
    public void drawLine(float x1, float y1, float x2, float y2) {
        g.drawLine(l2p(x1),l2p(y1),l2p(x2),l2p(y2));
    }
    
    /**
     * Draws a rectangle. The arguments are in simulated unit.
     */
    public void drawRect(float x, float y, float xLength, float yLength) {
        g.drawRect(l2p(x),l2p(y),l2up(xLength),l2up(yLength));
    }
    
    /**
     * Draws a round rectangle. The arguments are in simulated unit.
     */
    public void drawRoundRect(float x, float y, float xLength, float yLength) {
        g.drawRoundRect(l2p(x),l2p(y),l2up(xLength),l2up(yLength),l2up(xLength)/5,l2up(yLength)/5);
    }
    
    /**
     * Fills a round rectangle. The arguments are in simulated unit.
     */
    public void fillRoundRect(float x, float y, float xLength, float yLength) {
        g.fillRoundRect(l2p(x),l2p(y),l2up(xLength),l2up(yLength),l2up(xLength)/5,l2up(yLength)/5);
    }
    
    /**
     * Draws a rectangle, provided its angle and a center of rotation. The arguments are in simulated unit.
     */
    public void drawRect(float x0, float y0, float xLength, float yLength, float angle,float x, float y) {
        g.rotate(angle,l2p(x),l2p(y));
        g.drawRect(l2p(x0),l2p(y0),l2p(xLength),l2p(yLength));
        g.rotate(-angle,l2p(x),l2p(y));
    }
    
    /**
     * Fills a rectangle. The arguments are in simulated unit.
     */
    public void fillRect(float x, float y, float xLength, float yLength) {
        g.fillRect(l2p(x),l2p(y),l2up(xLength),l2up(yLength));
    }
    
    /**
     * Fills a rectangle, provided its angle and a center of rotation. The arguments are in simulated unit.
     */
    public void fillRect(float x0, float y0, float xLength, float yLength, float angle,float x, float y) {
        g.rotate(angle,l2p(x),l2p(y));
        g.fillRect(l2p(x0),l2p(y0),l2p(xLength),l2p(yLength));
        g.rotate(-angle,l2p(x),l2p(y));
    }
    
    
    /**
     * Draws a circle. The arguments are in simulated unit.
     */
    public void drawCircle(float x, float y, float radius){
        g.drawOval(l2p(x-radius),l2p(y-radius),l2up(2*radius),l2up(2*radius));
    }
    
    /**
     * Fills a circle. The arguments are in simulated unit.
     */
    public void fillCircle(float x, float y, float radius){
        g.fillOval(l2p(x-radius),l2p(y-radius),l2up(2*radius),l2up(2*radius));
    }
    
    /**
     * Draws a picture. The arguments are in simulated unit.
     */
    public void drawImage(Image image,float x, float y, float xLength, float yLength) {
        g.drawImage(image,l2p(x),l2p(y),l2up(xLength),l2up(yLength),null);
    }
    
    /**
     * Draws a picture, provided an angle and a center of rotation. The arguments are in simulated unit.
     */
    public void drawImage(Image image,float x0, float y0, float xLength, float yLength, float angle,float x, float y) {
        AffineTransform at = new AffineTransform();
        at.rotate(angle, pbu*x, pbu*y);
        at.translate((double)l2p(x0),(double)l2p(y0));
        at.scale((l2p(xLength)/(float)image.getWidth(null)),(double)(l2p(yLength)/(float)image.getHeight(null)));
        g.drawImage(image,at,null);
    }
    
    public void drawString(String s, float x, float y){
        g.drawString(s,l2p(x),l2p(y));
    }
    
}