package view;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;

import bk.LayoutType;
import graph.LayeredGraphNode;

/**
 * A drawable representation of a node.
 * @author kolja
 *
 */
public class NodeView extends JPanel implements AnnimatedView, MouseListener {
    private static final long serialVersionUID = 1L;
    private LayeredGraphNode model;
    private LayoutType layout;
    private JFrame mainView;
    private int originalWidth;
    private int originalHeight;
    
    private static final int PADDING = 25;

    public NodeView( LayeredGraphNode model, LayoutType lt, JFrame mv ) {
        mainView = mv;
        this.model = model;
        layout = lt;
        addMouseListener( this );
        originalWidth = (int)model.getWidth( lt );
        originalHeight = (int)model.getHeight( lt );
    }

    private synchronized void update()
    {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                for( ComponentListener l : mainView.getComponentListeners() )
                {
                    l.componentResized( new ComponentEvent(mainView, 0) );
                }
            }
        });
    }
    
    private int calculatePadding()
    {
        double padding = Math.min( getWidth() / 100.0 * PADDING, getHeight() / 100.0 * PADDING);
        if( padding < 10 )
            return 10;
        if( padding > 50 )
            return 50;
        return (int)padding;
    }
    
    public int getScaledX( int x )
    {
        int padding = calculatePadding();
        double scale = Math.min( (getWidth()-padding) / (double)getWidthOfNeededArea(), (getHeight()-padding) / (double)getHeightOfNeededArea());
        x *= scale;
        return x;
    }
    
    public int getScaledY( int y )
    {
        int padding = calculatePadding();
        double scale = Math.min( (getWidth()-padding) / (double)getWidthOfNeededArea(), (getHeight()-padding) / (double)getHeightOfNeededArea());
        y *= scale;
        return y;
    }
    
    public void updateTooltipText() {
        if( layout != LayoutType.COMBINED )
        {
            setToolTipText( "<html>Name: " + model.toString() +
                            "<br>Root: " + model.getRoot( layout ).toString() +
                            "<br>Shink: " + model.getSink( layout ).toString() +
                            "<br>Shift: " + model.getShift( layout ) + "</html>" );
        }
        else
        {
            setToolTipText( "<html>Name: " + model.toString() +
                            "<br>result X: " + model.getX( LayoutType.COMBINED ) +
                            "<br>other layouts: [" + model.getX( LayoutType.LEFTMOST_UPPER ) + "," + model.getX( LayoutType.RIGHTMOST_UPPER ) + ","
                                + model.getX( LayoutType.LEFTMOST_LOWER ) + "," + model.getX( LayoutType.RIGHTMOST_LOWER ) + "]</html>" );
        }
        for( Component c : getComponents() )
        {
            if( !(c instanceof NodeView) )
                continue;
            ((NodeView)c).updateTooltipText();
        }
    }

    public int getXOffset()
    {
        int x = 0;
        int padding = calculatePadding();
        double scale = Math.min( (getWidth()-padding) / (double)getWidthOfNeededArea(), (getHeight()-padding) / (double)getHeightOfNeededArea());
        x += (getWidth()-padding) / 2 - (getWidthOfNeededArea() * scale ) / 2 + padding/2;
        return x;
    }

    public int getYOffset()
    {
        int y = 0;
        int padding = calculatePadding();
        double scale = Math.min( (getWidth()-padding) / (double)getWidthOfNeededArea(), (getHeight()-padding) / (double)getHeightOfNeededArea());
        y += (getHeight()-padding) / 2 - (getHeightOfNeededArea() * scale ) / 2 + padding/2;
        return y;
    }

    public int getOriginalWidth() {
    	if( model.isDummyNode() )
            return (int)(originalWidth / 3);
        return originalWidth;
    }

    public int getOriginalHeight() {
        return originalHeight;
    }

    private int getWidthOfNeededArea() {
        int max = 0;
        double min = Double.POSITIVE_INFINITY;
        for( Component c : getComponents() )
        {
            if( c instanceof NodeView )
            {
                max = Math.max( max, ((NodeView)c).getVirtualX() + ((NodeView)c).getOriginalWidth() );
                min = Math.min( ((AnnimatedView)c).getVirtualX(), min);
            }
        }
        return max - (int)min;
    }

    private int getHeightOfNeededArea() {
        int max = 0;
        double min = Double.POSITIVE_INFINITY;
        for( Component c : getComponents() )
        {
            if( c instanceof NodeView )
            {
                max = Math.max( max, ((NodeView)c).getVirtualY() + ((NodeView)c).getOriginalHeight() );
                min = Math.min( ((AnnimatedView)c).getVirtualY(), min);
            }
        }
        return max - (int)min;
    }

    @Override
    public void doLayout() {
        double minX = Double.POSITIVE_INFINITY;
        for( Component c : getComponents() )
        {
            if( !(c instanceof AnnimatedView) )
                continue;
            minX = Math.min( ((AnnimatedView)c).getVirtualX(), minX);
        }
        int x = 0;
        int y = 0;
        int padding = calculatePadding();
        double scale = Math.min( (getWidth()-padding) / (double)getWidthOfNeededArea(), (getHeight()-padding) / (double)getHeightOfNeededArea());
        x += (getWidth()) / 2 - (getWidthOfNeededArea() * scale ) / 2;
        y += (getHeight()) / 2 - (getHeightOfNeededArea() * scale ) / 2;
        for( Component c : getComponents() )
        {
            if( !(c instanceof AnnimatedView) )
                continue;
            AnnimatedView view = (AnnimatedView)c;
            if( c instanceof NodeView )
            {
                c.setLocation( getScaledX( ((NodeView)c).getVirtualX() - (int)minX ) + x, getScaledY( view.getVirtualY() ) + y);
                c.setSize( getScaledX( ((NodeView)c).getOriginalWidth() ), getScaledY( ((NodeView)c).getOriginalHeight() ) );
            }
            else
            {
                c.setLocation( getScaledX( view.getVirtualX() - (int)minX ) + x, getScaledY( view.getVirtualY() ) + y);
                c.setSize( getScaledX( view.getVirtualWidth() ), getScaledY( view.getVirtualHeight() ) );
            }
            c.doLayout();
        }
    }
    
    @Override
    public void paint( Graphics g )
    {
        if( layout == LayoutType.COMBINED && model.getColor( null ) == null )
            return;
        updateTooltipText();
        double minX = Double.POSITIVE_INFINITY;
        for( Component c : getComponents() )
        {
            if( !(c instanceof AnnimatedView) )
                continue;
            minX = Math.min( ((AnnimatedView)c).getVirtualX(), minX);
        }
        int x = 0;
        int padding = calculatePadding();
        double scale = Math.min( (getWidth()-padding) / (double)getWidthOfNeededArea(), (getHeight()-padding) / (double)getHeightOfNeededArea());
        x += (getWidth()-padding) / 2 - (getWidthOfNeededArea() * scale ) / 2 + padding/2;
        for( Component c : getComponents() )
        {
            if( c instanceof NodeView )
            {
                if( layout != LayoutType.COMBINED )
                {
                    NodeView v = (NodeView)c;
                    v.renderClass( g.create( getScaledX( v.getPlainVirtualX() - (int)minX - 12 ) + x, c.getY() - getScaledY(12), getScaledX( v.getPlainVirtualWidth() + 22 ), c.getHeight() + getScaledY(22) ) );
                }
            }
            c.paint( g.create( c.getX(), c.getY(), c.getWidth(), c.getHeight() ) );
        }
        paintComponent( g );
    }

    public void renderClass( Graphics g ) {
        g.setColor( model.getRoot( layout ).getClassColor( layout ) );
        if( model.getContainedNodes().size() == 0 && model.getRoot( layout ).getClassColor( layout ) != Color.LIGHT_GRAY )
        {
            g.setColor( new Color( (g.getColor().getRed() + 500) / 3, (g.getColor().getGreen() + 500) / 3, (g.getColor().getBlue() + 500) / 3 ) );
            g.fillRect( 0, 0, g.getClipBounds().width, g.getClipBounds().height );
        }
    }

    @Override
    public void paintComponent( Graphics g )
    {
        Graphics2D g2 = (Graphics2D)g;
        g2.setColor( model.getColor( layout ) );
        g2.setStroke(new BasicStroke(5));
        if( model.getContainedNodes().size() == 0 )
        {
            if( model.getRoot( layout ) == model )
                g2.fillOval( 0, 0, getWidth(), getHeight() );
            else
                g2.fillRect( 0, 0, getWidth(), getHeight() );
        }
        boolean selected = model.isSelected( layout );
        if( selected )
        {
            g.setColor( Color.BLACK );
            if( model.getContainedNodes().size() > 0 )
                g.setColor( Color.GRAY );
            g.fillRect( 0, 0, getWidth(), getHeight() );
        }
        Border linebor = BorderFactory.createLineBorder(model.getColor( layout ), 5);
        if( model.getRoot( layout ) != model || model.getContainedNodes().size() != 0  )
            linebor.paintBorder( this, g2, 0, 0, getWidth(), getHeight() );
        if( model.isMouseOver() && model.getContainedNodes().size() == 0 )
        {
            g.setColor( Color.WHITE );
            g.fillOval( getWidth() / 4, getHeight() / 4, getWidth() / 2, getHeight() / 2 );
        }
    }

    public int getPlainVirtualX() {
        return (int)model.getX( layout );
    }

    @Override
    public int getVirtualX() {
        if( model.isDummyNode() )
            return (int)(model.getX( layout ) + getVirtualWidth() / (3/4.0) );
        return (int)model.getX( layout );
    }

    @Override
    public int getVirtualY() {
        return (int)model.getY( layout );
    }

    @Override
    public int getVirtualWidth() {
        if( model.isDummyNode() )
            return (int)(model.getWidth( layout ) / 3);
        return (int)model.getWidth( layout );
    }

    public int getPlainVirtualWidth() {
        return (int)model.getWidth( layout );
    }

    @Override
    public int getVirtualHeight() {
        return (int)model.getHeight( layout );
    }

    @Override
    public void mouseClicked(MouseEvent e) {}

    @Override
    public void mousePressed(MouseEvent e) {}

    @Override
    public void mouseReleased(MouseEvent e) {}

    @Override
    public void mouseEntered(MouseEvent e) {
        model.setMouseOver( true );
        update();
    }

    @Override
    public void mouseExited(MouseEvent e) {
        model.setMouseOver( false );
        update();
    }
}
