/**
 * 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.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.font.GlyphVector;
import java.lang.ref.SoftReference;
import java.util.Iterator;
import javax.swing.ImageIcon;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.inb.biomoby.client.gui.DynamicMobyComponent;
import org.inb.biomoby.client.gui.IDynamicComponent.MobyComponentListener;
import org.inb.biomoby.client.gui.ITransformable;
import org.inb.biomoby.client.gui.ISequenceCoordinateTransformer;
import org.inb.biomoby.client.gui.ISequenceDependent;
import org.inb.biomoby.client.gui.ISequenceTransformer;
import org.inb.biomoby.client.gui.SequenceTransformerBean;
import org.inb.biomoby.client.gui.auxiliary.Base64;
import org.inb.biomoby.client.gui.ontology.GenericSequenceComponent.GenericSequenceListener;
import org.inb.biomoby.shared.datatypes.MobyString;
import org.inb.biomoby.shared.ontology.GenericSequence;

/**
 * @author Dmitry Repchevsky
 */

public class GenericSequenceComponent<T extends GenericSequence> extends DynamicMobyComponent<T, GenericSequenceListener>
         implements ISequenceDependent,
                    ITransformable, 
                    DocumentListener, ActionListener, MouseMotionListener, MouseListener,
                    ClipboardOwner
{
    private final static String COPY_ICON_IMAGE =
        "R0lGODlhEAAQAPcAAAQChPz+/AA8/QAAf3jwABsLAAA8AAAAAL8AxBsAAwA8RwAAAAAABAAAABU8" +
        "AAAAAAkAAQAAAAA8AAAAAAAAJAIA6gA8EgAAAAAAFwMAswAAQgAAflQBBOgAABIACgAAANEYxOUI" +
        "A4EAR3wAAADEAAADAAFHAAAAAFYAJgAAswA8QgAAflw0xOfpAxISRwAAAHMAWAAAwAA8eQAAAHzc" +
        "AOgAABIAAAAAAAAAAOkA6pA8EnwAAEAAcAAA65E8EnwAAP/EcP8D6/9HEv8AAD0AjwAABJEARHwA" +
        "fm0AMOcAs4EAQnwAfgAY/wAI/xUA/wAA/2AAJgMAswAAQgAAfuBcOAgwAiF2QwB0fogAWFcA6hUA" +
        "EgAAAAABAAAAAAAAAAAAAH4CSAAAAgAAQ8AAfgAFAAAAAAAAAAAAAP+wAP/oAP8SAP8AAP9cBP/p" +
        "AP8SAP8AAACkaADp6gASEgAAAACmjADx6gB1EgB0AAAwUwAX5BVykAB0fIz/8Oj/nhL/gAD/fLpW" +
        "AOZZAIFyrHx0AIhwAFfpABUSAAAAADMBB+O0AIFCAHx+AEAoAHg8AFB0AAAAAIgEAFcAAAEArAAA" +
        "AGwEAAAAAAAKAAAAAMiOAOdOABIAAAAAAACkAADpAAASAAAAAASdAPsrABKDAAB8AAAA+OkAnpAA" +
        "gHwAfEAA/wAA/5EA/3wA//8A8P8Anv8AgP8AfD05MQA8AJGTAHx8ANoAMfQAAICsAHwAAABMVABk" +
        "8BWDEgB8AAD//wD//wD//wD//4gAAFcAABUAAAAAAACI0AHp6gASEgAAAABMxgBkPACDTAB8AOcE" +
        "sPT76oASEnwAALQAd+npEBKQTwB8AIgQ5Fc86hWTEgB8AFb/TFn/ZHL/g3T/fBgAkQgA7ACsEgAA" +
        "AIBMzYpk/0GD/358fwDMYADq7AASEgAAAKT/AAZkAACDrAB8AOCgTABkZACDgwB8fAAAAAAAAAAA" +
        "rAAAAAAwjgAATgAAAAAAAABYAADAAAB5AAAAAPgAtgsA6jwARwAAACH5BAEAAAEALAAAAAAQABAA" +
        "BwhHAAMIHEiwoEAACBMaJAiAYcOFAR4ejAixIcKICS8y3DhQ48SMGyVKpHgRJEmFBh+OtAhSZcaR" +
        "HReyVAhzYkqZL1FC3MmTYEAAOw==";
    private JPopupMenu popup;

    private GenericSequence sequence;
    private CharSequence characters;
    
    private ISequenceTransformer transformer;
    
    private Point mousePoint;
    private MouseEvent lastEvent = null;
        
    public GenericSequenceComponent()
    {
        setBackground(new Color(0xFFFF66));

        popup = new JPopupMenu();

        ImageIcon icon = new ImageIcon(Base64.decodeFast(COPY_ICON_IMAGE));
        JMenuItem menuItem = new JMenuItem("copy sequence", icon);
        menuItem.addActionListener(this);
        popup.add(menuItem);

        addMouseMotionListener(this);
        addMouseListener(this);
        
        transformer = new SequenceTransformerBean();
    }
    
    public GenericSequenceComponent(T sequence)
    {
        this();
        
        setSequence(sequence);
    }

    public CharSequence getCharSequence()
    {
        if (characters == null)
        {
            if (sequence == null)
            {
                return "";
            }
            else
            {
                MobyString mobyString = sequence.getSequenceString();

                if (mobyString != null)
                {
                    characters = mobyString.getString();

                    if (characters != null)
                    {
                        return characters;
                    }
                }
                characters = "";
            }
        }
        return characters;
    }
    
    @Override
    public ISequenceTransformer getTransformer()
    {
        return transformer;
    }

    @Override
    public void setTransformer(ISequenceCoordinateTransformer transformer)
    {
        if (transformer instanceof ISequenceTransformer)
        {
            this.transformer = (ISequenceTransformer)transformer;
        }
    }
    
    public void setSequence(GenericSequence sequence)
    {
        repaint();

        if (this.sequence != sequence)
        {
            this.sequence = sequence;
            
            getTransformer().setPosition(0);
            firePositionChanged(0);
        }
        
        characters = null;
    }
    
    public int getPosition()
    {
        return getTransformer().getPosition();
    }
    
    @Override
    public void setPosition(int position)
    {
        ISequenceCoordinateTransformer t = getTransformer();
        
        if (position != t.getPosition())
        {
            t.setPosition(position);
            firePositionChanged(position);
        }
        repaint();
    }
    
    @Override
    public void setSequenceVisible(boolean isVisible)
    {
        super.setVisible(isVisible);
    }

    public Color getColor(char ch)
    {
        return Color.BLACK;
    }
    
    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D)g.create();
        
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);

        final int w = getWidth();
        final int h = getHeight();
        
        final CharSequence string = getCharSequence();
        final int length = string.length();
                
        g2d.setColor(new Color(0xFFFF66));
        g2d.fillRect(0, 0, w, h);
        
        transformer.setWidth(w);
        transformer.setHeight(h);

        if (length != transformer.getLength())
        {
            transformer.setLength(length);
            firePositionChanged(length);
        }

        final int labelSize = h/3;

        // draw full sequence bar...
        g2d.setColor(new Color(0xFFCC00));
        g2d.fill3DRect(0, labelSize/5, w, labelSize + 1, true);

        //if (string != null && length > 0)
        {
            GlyphVector gv = transformer.getGlyphVector(string);
            
            final int fVisible = transformer.getFirstVisibleChar();
            final int highlight = transformer.getPosition() - fVisible;
            
            for (int i = 0, n = gv.getNumGlyphs(); i < n; i++)
            {
                Shape sh = gv.getGlyphOutline(i, 0, h);
                
                if (i == highlight)
                {
                    g2d.setColor(Color.RED);
                }
                else
                {
                    g2d.setColor(getColor(string.charAt(i + fVisible)));
                }
                g2d.fill(sh);
            }
            
            int x1Seq = (int) (w * transformer.getFirstFixedCharPosition() / (float)length);
            int x2Seq = (int) (w * transformer.getLastFixedCharPosition() / (float)length);
            
            g2d.setColor(new Color(0x66CCFF));
            g2d.fillRect(x1Seq, labelSize/5, x2Seq - x1Seq, labelSize);
            
            // draw additional info...
            g2d.setColor(Color.LIGHT_GRAY);
            g2d.drawLine(transformer.getXFixedSegmentStart(), 0, transformer.getXFixedSegmentStart(), h);
            g2d.drawLine(transformer.getXFixedSegmentEnd(), 0, transformer.getXFixedSegmentEnd(), h);

            Font font = new Font("Monospaced", Font.BOLD, labelSize);
            FontMetrics fm = g2d.getFontMetrics(font);

            final int start = transformer.getXFixedSegmentStart();
            final int end = transformer.getXFixedSegmentEnd();
            
            g2d.setFont(font);
            g2d.setColor(Color.BLUE);
            
            String startLabel = String.valueOf(transformer.getFirstFixedCharPosition() + 1);
            g2d.drawString(startLabel, start, labelSize);
            
            String endLabel = String.valueOf(transformer.getLastFixedCharPosition() + 1);
            g2d.drawString(endLabel, end - fm.stringWidth(endLabel), labelSize);
            
            g2d.setFont(font);
            g2d.setColor(Color.RED);
            
            String posLabel = String.valueOf(transformer.getPosition() + 1);
            g2d.drawString(posLabel, start + (end - start - fm.stringWidth(posLabel))/2, labelSize);

            // ...
            
            paintMouseCursor(g2d, length);
        }
    }

    private void paintMouseCursor(Graphics2D g2d, int length)
    {
        if (mousePoint != null)
        {
            if (mousePoint.y < getHeight()/3)
            {
                final int pos = length * mousePoint.x/getWidth();
                
                g2d.setColor(Color.RED);
                g2d.drawString(String.valueOf(pos + 1), mousePoint.x, mousePoint.y);
                
                mousePoint = null;
            }
        }
    }

    @Override
    public Dimension getPreferredSize()
    {
        Dimension dim = super.getPreferredSize();
        return new Dimension(dim.width, 24);
    }

    @Override
    public Dimension getMaximumSize()
    {
        Dimension prf = this.getPreferredSize();
        Dimension max = super.getMaximumSize();

        return new Dimension(max.width, prf.height);
    }

    public void actionPerformed(ActionEvent e)
    {
        StringSelection selection = new StringSelection(getCharSequence().toString());

        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.setContents(selection, this);
    }

    @Override
    public void mouseDragged(MouseEvent e)
    {
        if (lastEvent != null)
        {
            final int x = e.getX();
            
            if (e.getY() < getHeight()/4 && x >= 0 && x < getWidth())
            { // fast scroll
                //final int length = string.length();
                final int length = transformer.getLength();
                
                final int newPosition = length * x/getWidth();
//                final int oldPosition = transformer.getPosition();
                
                transformer.setPosition(newPosition);


                firePositionChanged(newPosition);
                repaint();
            }
            else
            {
                final int dx = x - lastEvent.getX();

                if (Math.abs(dx) > 5)
                {
                    int movement = dx/5;

                    int newPosition = transformer.getPosition() + movement;

                    if (newPosition >= 0)
                    {
                        //final int length = string.length();
                        final int length = transformer.getLength();

                        if (newPosition < length)
                        {
                            transformer.setPosition(newPosition);

                            firePositionChanged(newPosition);
                        }
                    }
                    lastEvent = e;

                    repaint();
                }
            }
        }
    }

    @Override
    public void mouseMoved(MouseEvent e)
    {
        mousePoint = e.getPoint();
        repaint();
    }

    @Override public void mouseClicked(MouseEvent e) {}

    @Override
    public void mousePressed(MouseEvent e)
    {
        lastEvent = e;

        if (e.getY() < getHeight()/4)
        {
            //final int length = string.length();
            final int length = transformer.getLength();
            
            final int position = length * e.getX()/getWidth();

            transformer.setPosition(position);
            firePositionChanged(position);

            repaint();
        }
    }

    @Override
    public void mouseReleased(MouseEvent e)
    {
        lastEvent = null;

        if (e.isPopupTrigger())
        {
            popup.show(e.getComponent(), e.getX(), e.getY());
        }
    }

    @Override
    public void mouseEntered(MouseEvent e) {}

    @Override
    public void mouseExited(MouseEvent e)
    {
        repaint();
    }

    @Override
    public void insertUpdate(DocumentEvent e)
    {
        repaint();
    }

    @Override
    public void removeUpdate(DocumentEvent e)
    {
        repaint();
    }

    @Override
    public void changedUpdate(DocumentEvent e)
    {
        repaint();
    }
    
    protected void firePositionChanged(int pos)
    {
        for (Iterator<SoftReference<? super GenericSequenceListener>> iter = getListeners(); iter.hasNext();)
        {
            SoftReference<? super GenericSequenceListener> ref = iter.next();
            
            Object o = ref.get();
            
            if (o == null)
            {
                iter.remove();
            }
            else if (o instanceof GenericSequenceListener)
            {
                GenericSequenceListener listener = (GenericSequenceListener)o;
                listener.positionChanged(pos);
            }
        }
    }

    public void lostOwnership(Clipboard clipboard, Transferable contents)
    {
    }

    public static interface GenericSequenceListener extends MobyComponentListener
    {
        public void positionChanged(int pos);
    }
    
    public static void main(String[] args)
    {        
//        JFrame frame = new JFrame("Test");
//        
//        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//        
//        GenericSequence seq = new GenericSequence();
//        
//        seq.setSequenceString(new MobyString("ATTCTTCTCTGCCGCGATGGAATTTCGGGTACCGCTCGCGCGCACATATCATCTTAGAATTTCGGGTACCGCTCGCGCGCACATATCA"));
//        
//        GenericSequencePanel panel = new GenericSequencePanel(seq);
//        
//        frame.add(panel);
//        
//        frame.setSize(200, 100);
//        frame.setVisible(true);
    }
}
