package graphicsDomain;

import sharedInterfaces.*;
import sharedDataInterfaces.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.font.*;

public class GraphicsFacade extends AbstractGraphicsFacade
{
    private GraphMediator med;

    private GraphMouseHandler mouseHandler = new GraphMouseHandler();
    private GuiFacadeInterface guiFacade; //Used to draw current waveform coordinates
    public GraphicsFacade()
    {
        med = new GraphMediator(this);
        addMouseListener(mouseHandler);
        addMouseMotionListener(mouseHandler);
    }
    public void initialise(GuiFacadeInterface guiFacade)
    {
        this.guiFacade = guiFacade;
    }
    public void setGraphData(GraphDataInterface[] data)
    {
        GraphDataInGraphics[] graphData = new GraphDataInGraphics[data.length];
        for (int i = 0; i < data.length; i++)
            graphData[i] = new GraphDataInGraphics(data[i]);
        med.updateData(graphData);
    }
    public void resetGraphData()
    {
        med.setIsGraphData(false);
        med.createDecorator();
        repaint();
    }
    public void setGraphOptions(GraphOptionsInterface graphOpt)
    {
	GraphOptionsInGraphics options = new GraphOptionsInGraphics(graphOpt);
	med.updateOptions(options);
    }

    public void applyGraphOptions()
    {
        med.createDecorator();
        repaint();
    }

    public void graphPointUpdated(double x, double y, boolean inRange)
    {
        guiFacade.graphPointUpdated(x, y, inRange);
    }

    public void paintComponent(Graphics g)
    {
        // Cast the graphics object to Graph2D
        Graphics2D g2 = (Graphics2D) g;

        // Get plot size
        Dimension size = getSize();
        int xSize = size.width;
        int ySize = size.height;

        // find border size
        int borderSize;
        if (med.getGraphOptions().getDefaultBorder())
        {
            borderSize = calcBorderSize(g2);
            med.getGraphOptions().setTickPix(4);
        }
        else
        {
            borderSize = 0;
            med.getGraphOptions().setTickPix(0);
        }

        // Set graph and border size in med
        med.setXSize(xSize);
        med.setYSize(ySize);
        med.setBorderSize(borderSize);

        // Set background Color
        g2.setColor(med.getGraphOptions().selectColor(
            med.getGraphOptions().getGraphColor()));
        g2.fill(new Rectangle2D.Double(0, 0, xSize, ySize));

        //call draw method of the decorator chain
        med.getDecorator().draw(g2);
    }

    public int calcBorderSize(Graphics2D g2)
    {
        /* borderSize calculation using test string "Test" */
        double border1, border2, border3, spaceFac = 1.5;

        // height of top adornment: graph title
        Font titleFont = med.getGraphOptions().getTitleFont();
        g2.setFont(titleFont); // title font
        FontRenderContext context = g2.getFontRenderContext();
        Rectangle2D bounds = titleFont.getStringBounds("Test", context);
        double stringHeight = bounds.getHeight();
        border1 = spaceFac * stringHeight;

        // height of bottom adornments: x-axis label plus x-tick-labels
        Font axisFont = med.getGraphOptions().getAxisFont();
        g2.setFont(med.getGraphOptions().getAxisFont()); // axis font
        context = g2.getFontRenderContext();
        bounds = axisFont.getStringBounds("Test", context);
        stringHeight = bounds.getHeight();
        border2 = spaceFac * stringHeight;

        Font tickFont = med.getGraphOptions().getTickFont();
        g2.setFont(tickFont); // tick font
        context = g2.getFontRenderContext();
        bounds = tickFont.getStringBounds("Test", context);
        stringHeight = bounds.getHeight();
        border3 = spaceFac * stringHeight;
        return (int) Math.max(border1, border2 + border3);
    }

    /** inner class to handle mouse events */
    private class GraphMouseHandler
        implements MouseListener, MouseMotionListener
    {
        //Recorded mouse position when mouse button pressed
        private int startX = 0, startY = 0;
        //Recorded previous mouse position
        private int prevX = 0, prevY = 0;

        public void mousePressed(MouseEvent e)
        {
           //Record mouse position when clocked
            startX = prevX = e.getX();
            startY = prevY = e.getY();
            switch (med.getGraphOptions().getDisplayMode())
            {
                //Notify decorators interested in current mouse
                //position: zoomDecorator and crossHairDecorator
               case GraphOptionsInterface.GRAB_MODE:
                {
                    GraphicsFacade.this.repaint();
                    break;
                }
                case GraphOptionsInterface.CROSSHAIR_MODE:
                {
                    med.setCurrX(startX);
                    med.setCurrY(startY);
                    med.recordClosestWave();
                    GraphicsFacade.this.repaint();
                    break;
                }
                case GraphOptionsInterface.ZOOM_MODE:
                {
                    med.getZoomDecorator().setStartX(startX);
                    med.getZoomDecorator().setStartY(startY);
                    med.getZoomDecorator().setZooming(true);
                    break;
                }
            }
        }

        public void mouseMoved(MouseEvent e)
        {   //define cursor shapes for the different modes
            if (med.getGraphOptions().getDisplayMode() ==
                          GraphOptionsInterface.CROSSHAIR_MODE)
                setCursor(Cursor.getDefaultCursor());
            if (med.getGraphOptions().getDisplayMode() ==
                          GraphOptionsInterface.ZOOM_MODE)
                setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
            if (med.getGraphOptions().getDisplayMode() ==
                          GraphOptionsInterface.GRAB_MODE)
                setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        }

        public void mouseClicked(MouseEvent e)
        {
            if (e.getClickCount()>1) //If double click
            {
                //Reset graph to draw complete waveform
                med.getZoomDecorator().setZooming(false);
                med.updateData(med.getGraphDataArr());
                GraphicsFacade.this.repaint();
            }
        }

        public void mouseEntered(MouseEvent e)
        {
        }

        public void mouseExited(MouseEvent e)
        {}

        public void mouseDragged(MouseEvent e)
        {
            int currX = e.getX();
            int currY = e.getY();

            //If the current display mode is GRAB, we need to change med,
            //so that the waveform is dragged
            //For the other modes (zoom, crosshair) no change in the waveform
            //is required during mouse drag
            switch (med.getGraphOptions().getDisplayMode())
            {
                case GraphOptionsInterface.GRAB_MODE:
                    if (prevX == currX && prevY == currY)
                        return; //Mouse not dragged

                    //Change waveform limits in med to perform dragging
                    double deltaXData = med.getXValue(prevX) -
                                        med.getXValue(currX);
                    double deltaYData = med.getYValue(prevY) -
                                        med.getYValue(currY);;
                    med.setMaxXData(med.getMaxXData() + deltaXData);
                    med.setMinXData(med.getMinXData() + deltaXData);
                    med.setMaxYData(med.getMaxYData() + deltaYData);
                    med.setMinYData(med.getMinYData() + deltaYData);
                    break;
                case GraphOptionsInterface.CROSSHAIR_MODE:
                    med.setCurrX(currX);
                    break;
                case GraphOptionsInterface.ZOOM_MODE:
                    med.getZoomDecorator().setEndX(currX);
                    med.getZoomDecorator().setEndY(currY);
                    break;
             }
            prevX = currX;
            prevY = currY;

            //Force repaint
            GraphicsFacade.this.repaint();
        }

        public void mouseReleased(MouseEvent e)
        {
            //Mouse released actions are only required when in ZOOM mode
            //In this case, med parameters are changed so that the waveform
            //region fits the zoom box
            if (med.getGraphOptions().getDisplayMode() ==
                GraphOptionsInterface.ZOOM_MODE)
            {
                int currX = e.getX();
                int currY = e.getY();
                if (startX == currX && startY == currY) return; //Not dragged

                //Set minimum width and height for the zoom box
                if (startX == currX) currX = startX + 1;
                if (startY == currY) currY = startY + 1;

                //Get the  values corresponding to the limits of the zoom box
                //Need to handle every direction in which mouse has been moved
                double minXData, minYData, maxXData, maxYData;
                if (currX > startX)
                {
                    minXData = med.getXValue(startX);
                    maxXData = med.getXValue(currX);
                }
                else
                {
                    minXData = med.getXValue(currX);
                    maxXData = med.getXValue(startX);
                }
                if (currY < startY)
                {
                    minYData = med.getYValue(startY);
                    maxYData = med.getYValue(currY);
                }
                else
                {
                    minYData = med.getYValue(currY);
                    maxYData = med.getYValue(startY);
                }

                //Let the values corresponding to the limits of the zoom box
                //be the limits of the displayed waveform
                med.setMaxXData(maxXData);
                med.setMaxYData(maxYData);
                med.setMinXData(minXData);
                med.setMinYData(minYData);
                med.getZoomDecorator().setZooming(false);
                //Force repaint
                GraphicsFacade.this.repaint();
            }
        }
    }
}

