/**
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 *
 * Copyright (C)
 * <a href="http://www.inab.org">Spanish National Institute of Bioinformatics (INB)</a>
 * <a href="http://www.bsc.es">Barcelona Supercomputing Center (BSC)</a>
 * <a href="http://inb.bsc.es">Computational Node 6</a>
 */

package org.inb.biomoby.client.gui.ontology;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.inb.biomoby.client.gui.auxiliary.AbstractPopupMobyComponent;
import org.inb.biomoby.client.gui.auxiliary.ImageComponent;
import org.inb.biomoby.client.gui.StringPainter;
import org.inb.biomoby.shared.datatypes.MobyFloat;
import org.inb.biomoby.shared.datatypes.MobyString;
import org.inb.biomoby.shared.ontology.ArrayXYData;
import org.inb.biomoby.shared.ontology.ElementXYData;

/**
 * @author Dmitry Repchevsky
 */

public class ArrayXYDataComponent extends AbstractPopupMobyComponent
        implements MouseMotionListener, MouseListener
{
    private final static Color GRID_COLOR = new Color(0xF8F8F8);
    
    private ArrayXYData data;
    private Set<Point> points;
    
    private ImageComponent popup;

    private double xMin = Float.MAX_VALUE;
    private double xMax = Float.MIN_VALUE;
    private double yMin = Float.MAX_VALUE;
    private double yMax = Float.MIN_VALUE;
    
    private double width;
    private double height;
    
    private double titleHeight;
    private double titleWidth;
    private double titleDescent;
    
    private Font font;
    
    public ArrayXYDataComponent()
    {
         points = new TreeSet<Point>();
         addMouseMotionListener(this);
         addMouseListener(this);
         
    }
    
    public ArrayXYDataComponent(ArrayXYData data)
    {
        this();
        
        setData(data);
    }
    
    public void setData(ArrayXYData data)
    {
        this.data = data;
        
        points.clear();

        List<? extends ElementXYData> coords = data.getElements();

        // calculating MIN/MAX values (X/Y)
        for (ElementXYData plot : coords)
        {
            MobyFloat xMobyFloat = plot.getKey();
            MobyFloat yMobyFloat = plot.getValue();

            if (xMobyFloat != null && yMobyFloat != null)
            {
                final double x = xMobyFloat.getFloat();

                xMin = Math.min(xMin, x);
                xMax = Math.max(xMax, x);

                final double y = yMobyFloat.getFloat();

                yMin = Math.min(yMin, y);
                yMax = Math.max(yMax, y);

                points.add(new Point(x, y));
            }
        }
    }

    public ArrayXYData getData()
    {
        return data;
    }

    @Override
    public Dimension getPreferredSize()
    {
        return new Dimension(120, 240);
    }

    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D)g;
        paintImage(g2d);
    }

    @Override
    protected ImageComponent getPopup()
    {
        return popup;
    }

    @Override public void mouseClicked(MouseEvent e) {}
    @Override public void mousePressed(MouseEvent e) {}
    @Override public void mouseReleased(MouseEvent e) {}
    @Override public void mouseEntered(MouseEvent e) {}

    @Override
    public void mouseExited(MouseEvent e)
    {
        hidePopup();
        popup = null;
    }

    @Override public void mouseDragged(MouseEvent e) {}
    
    @Override
    public void mouseMoved(MouseEvent e)
    {
        if (xMax != xMin && yMax != yMin)
        {
            final int x = e.getX();
            final int y = e.getY();

            final int w = this.getWidth();
            final int h = this.getHeight();

            double xScale = w / (xMax - xMin);
            double yScale = (h - titleHeight) / (yMax - yMin);

            for (Point point : points)
            {
                final int px = (int)((point.x - xMin) * xScale);
                final int py = (int)(height - titleHeight - (point.y - yMin) * yScale);
                
                if (Math.abs(px - x) <= 2  && Math.abs(py - y) <= 2)
                {
                    StringBuilder sb = new StringBuilder();
                    
                    String xTitle = getXTitle();
                    if (xTitle != null)
                    {
                        sb.append(xTitle).append(": ");
                    }
                    else
                    {
                        sb.append("x: ");
                    }
                    
                    sb.append(point.x).append(", ");
                    
                    String yTitle = getYTitle();
                    if (yTitle != null)
                    {
                        sb.append(yTitle).append(": ");
                    }
                    else
                    {
                        sb.append("y: ");
                    }
                    
                    sb.append(point.y);

                    String coords = sb.toString();
                    
                    FontRenderContext frc = new FontRenderContext(null, true, false);
                    Font f = new Font(Font.SANS_SERIF, Font.PLAIN, 12);
                    LineMetrics lm = f.getLineMetrics(coords, frc);
                    Rectangle2D rec = font.getStringBounds(coords, frc);
                    
                    GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
                    GraphicsDevice device = environment.getDefaultScreenDevice();
                    GraphicsConfiguration config = device.getDefaultConfiguration();
                    BufferedImage image = config.createCompatibleImage((int)rec.getWidth(), (int)rec.getHeight());

                    Graphics2D g2d = image.createGraphics();
                    g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                    g2d.setFont(f);
                    g2d.setColor(Color.WHITE);
                    g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
                    g2d.setColor(Color.DARK_GRAY);
                    g2d.drawString(coords, 0, image.getHeight() - lm.getDescent());
                    g2d.dispose();

                    if (popup == null)
                    {
                        popup = new ImageComponent(image);
                        showPopup(x, y);                        
                    }
                    return;
                }
            }
            hidePopup();
            popup = null;
        }
    }

    private void paintImage(Graphics2D g2d)
    {
        final double w = this.getWidth();
        final double h = this.getHeight();
        
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, (int)w, (int)h);
        
        if (data != null)
        {
            // painting a title
            MobyString titleMobyString = data.getTitle();
            if (titleMobyString != null)
            {
                String title = titleMobyString.getString();
                
                if (title != null && title.length() > 0)
                {
                    if (width != w || height != h)
                    {
                        width = w;
                        height = h;
                        
                        font = new Font("SansSerif", Font.PLAIN, 14);
                        g2d.setFont(font);
                        
                        LineMetrics lm = g2d.getFontMetrics().getLineMetrics(title, g2d);
                        final Rectangle2D r = g2d.getFontMetrics().getStringBounds(title, g2d);

                        titleWidth = r.getWidth();
                        titleHeight = lm.getHeight();
                        titleDescent = lm.getDescent();
                    }
                    else
                    {
                        g2d.setFont(font);
                    }

                    g2d.setColor(Color.DARK_GRAY);
                    g2d.drawString(title,(float)(w - titleWidth)/2,(float)(h - titleDescent));
                }
            }

            // prevent devision by zero...
            if (xMax != xMin && yMax != yMin)
            {
                g2d.setBackground(Color.WHITE);
                
                g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                double xScale = w / (xMax - xMin);
                double yScale = (h - titleHeight) / (yMax - yMin);

                Point previous = null;
                
                for (Point point : points)
                {
                    final int x2 = (int)((point.x - xMin) * xScale);
                    final int y2 = (int)(height - titleHeight - (point.y - yMin) * yScale);

                    g2d.setColor(GRID_COLOR);
                    g2d.drawLine(0, y2, (int)width, y2);
                    g2d.drawLine(x2, 0, x2, (int)(height - titleHeight));
                    
                    if (previous != null)
                    {
                        final int x1 = (int)((previous.x - xMin) * xScale);
                        final int y1 = (int)(height - titleHeight - (previous.y - yMin) * yScale);

                        g2d.setColor(Color.DARK_GRAY);
                        g2d.drawLine(x1, y1, x2, y2);
                        
                        g2d.setColor(Color.BLUE);
                        g2d.drawOval(x1 - 1, y1 - 1, 2, 2);
                        g2d.drawOval(x2 - 1, y2 - 1, 2, 2);
                    }
                    previous = point;
                }
                
                // painting axises...
                g2d.setColor(Color.DARK_GRAY);
                if (xMin <= 0 && xMax >= 0)
                {
                    final int x = (int)(Math.abs(xMin) * xScale);
                    final int y = (int)((yMax - yMin) * yScale);
                    
                    g2d.drawLine(x, 0, x, y);
                }
                
                if (yMin <= 0 && yMax >= 0)
                {
                    final int x = (int)((xMax - xMin) * yScale);
                    final int y = (int)(Math.abs(yMin) * xScale);
                    
                    g2d.drawLine(0, y, x, y);
                }
                
                // painting axis labels
                
                String xTitle = getXTitle();
                
                if (xTitle != null)
                {
                    g2d.setClip(0, (int)(height - titleHeight - 20), (int)width, 20);

                    StringPainter.drawString(g2d, xTitle, StringPainter.ALIGNMENT.RIGHT);
                }

                String yTitle = getYTitle();
                if (yTitle != null && yTitle.length() > 0)
                {
                    g2d.setClip(0, 0, (int)width, 20);

                    StringPainter.drawString(g2d, yTitle, StringPainter.ALIGNMENT.LEFT);
                }
            }
        }
    }
    
    private String getXTitle()
    {
        MobyString xTitleMobyString = data.getXtitle();
        if (xTitleMobyString != null)
        {
            String xTitle = xTitleMobyString.getString();

            if (xTitle != null && xTitle.length() > 0)
            {
                return xTitle;
            }
        }

        return null;
    }
    
    private String getYTitle()
    {
        MobyString yTitleMobyString = data.getYtitle();
        if (yTitleMobyString != null)
        {
            String yTitle = yTitleMobyString.getString();

            if (yTitle != null && yTitle.length() > 0)
            {
                return yTitle;
            }
        }
        
        return null;
    }
    
    public static class Point implements Comparable<Point>
    {
        final double x;
        final double y;
        
        public Point(double x, double y)
        {
            this.x = x;
            this.y = y;
        }

        @Override
        public int compareTo(Point point)
        {
            return x == point.x ? 0 : x > point.x ? 1 : -1;
        }
    }
}
