import java.awt.Rectangle;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Cursor;
import java.awt.Container;
import java.awt.BorderLayout;
import java.awt.Color;

import java.awt.event.ComponentListener;
import java.awt.event.ComponentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;

import java.awt.image.ImageObserver;

import javax.swing.JScrollBar;
import javax.swing.JDesktopPane;
import javax.swing.JPanel;
import javax.swing.JInternalFrame;

import javax.swing.event.InternalFrameListener;
import javax.swing.event.InternalFrameEvent;


public class ImagePanel extends JDesktopPane implements ComponentListener, AdjustmentListener,
                                                        MouseListener, MouseMotionListener,
                                                        InternalFrameListener
{
  private JP2Image jp2Image;
  private JP2ImageView mainView;
  private JP2ImageView miniView;

  private JScrollBar verScroll;
  private JScrollBar horScroll;

  private int pressX, pressY;
  private int incrX, incrY;
  private boolean panelMoving;
  private boolean mousePressed;
  private boolean zoomMode;
  private ImageWindow parentWindow;
  private JInternalFrame miniViewFrame;
  private JPanel miniViewPanel;
  private Rectangle miniViewRect;


  public ImagePanel()
  {
    init(null);
  }

  public ImagePanel(ImageWindow imageWindow)
  {
    init(imageWindow);
  }

  private void init(ImageWindow imageWindow)
  {
    zoomMode = false;
    panelMoving = false;
    incrX = 0; incrY = 0;
    mousePressed = false;
    miniViewFrame = null;

    parentWindow = imageWindow;

    mainView = null;
    jp2Image = new JP2Image();

    addMouseListener(this);
    addComponentListener(this);
    addMouseMotionListener(this);

    if(parentWindow == null) {
      horScroll = null;
      verScroll = null;

    } else {
      horScroll = parentWindow.getHorizontalScrollBar();
      verScroll = parentWindow.getVerticalScrollBar();
    }

    if(horScroll != null) {
      horScroll.addMouseListener(this);
      horScroll.addAdjustmentListener(this);
    }

    if(verScroll != null) {
      verScroll.addMouseListener(this);
      verScroll.addAdjustmentListener(this);
    }

    setCursor((Cursor)ProyectoProperties.cursors.get("openedHand"));
  }

  public void setZoomMode(boolean mode)
  {
    zoomMode = mode;
    if(zoomMode) setCursor((Cursor)ProyectoProperties.cursors.get("zoomCursor"));
    else setCursor((Cursor)ProyectoProperties.cursors.get("openedHand"));
  }

  public boolean getZoomMode()
  {
    return zoomMode;
  }

  public Dimension getImageSize()
  {
    return new Dimension(jp2Image.getWidth(), jp2Image.getHeight());
  }

  public Dimension getImageRealSize()
  {
    return new Dimension(jp2Image.getRealWidth(), jp2Image.getRealHeight());
  }

  public double getScale()
  {
    return (100.0 / (double)(1 << mainView.getResolution()));
  }

  public void mouseDragged(MouseEvent e)
  {
    if(mainView == null) return;

    if(e.getSource() == this) {
      if(zoomMode) return;

      incrX += (e.getX() - pressX);
      incrY += (e.getY() - pressY);

      Rectangle imageROI = mainView.getBounds();

      int minX = mainView.getImageWidth() - (imageROI.x + imageROI.width);
      if(incrX > imageROI.x) incrX = imageROI.x;
      if(incrX < -minX) incrX = -minX;

      int minY = mainView.getImageHeight() - (imageROI.y + imageROI.height);
      if(incrY > imageROI.y) incrY = imageROI.y;
      if(incrY < -minY) incrY = -minY;

      if(horScroll != null) horScroll.setValue(imageROI.x - incrX);
      if(verScroll != null) verScroll.setValue(imageROI.y - incrY);

      pressX = e.getX();
      pressY = e.getY();

      updateMiniView();
      repaint();

    } else if(e.getSource() == miniViewPanel) {
      if(panelMoving) {
        double mainViewScale = mainView.getScale();
        double miniViewScale = miniView.getScale();

        Rectangle imageROI = mainView.getBounds();

        incrX -= (int)(((double)(e.getX() - pressX) / miniViewScale) * mainViewScale);
        incrY -= (int)(((double)(e.getY() - pressY) / miniViewScale) * mainViewScale);

        int minX = mainView.getImageWidth() - (imageROI.x + imageROI.width);
        if(incrX > imageROI.x) incrX = imageROI.x;
        if(incrX < -minX) incrX = -minX;

        int minY = mainView.getImageHeight() - (imageROI.y + imageROI.height);
        if(incrY > imageROI.y) incrY = imageROI.y;
        if(incrY < -minY) incrY = -minY;

        if(horScroll != null) horScroll.setValue(imageROI.x - incrX);
        if(verScroll != null) verScroll.setValue(imageROI.y - incrY);

        pressX = e.getX();
        pressY = e.getY();

        updateMiniView();
        repaint();
      }
    }
  }

  public void mouseMoved(MouseEvent e)
  {
    if(e.getSource() == miniViewPanel) {
      if(miniViewRect.contains(e.getPoint()))
        miniViewPanel.setCursor((Cursor)ProyectoProperties.cursors.get("openedHand"));
      else
        miniViewPanel.setCursor(Cursor.getDefaultCursor());
    }
  }

  public void mouseClicked(MouseEvent e) { }
  public void mouseEntered(MouseEvent e) { }
  public void mouseExited(MouseEvent e) { }

  public void mousePressed(MouseEvent e)
  {
    if(mainView == null) return;

    pressX = e.getX();
    pressY = e.getY();

    mousePressed = true;

    if(e.getSource() == miniViewPanel) {
      if(miniViewRect.contains(e.getPoint())) {
        miniViewPanel.setCursor((Cursor)ProyectoProperties.cursors.get("closedHand"));
        panelMoving = true;
      }

    } if(e.getSource() == this) {
      if(!zoomMode) {
        setCursor((Cursor)ProyectoProperties.cursors.get("closedHand"));
        panelMoving = true;

      } else {
        mousePressed = false;

        Dimension size = getSize();
        if((e.getModifiers() & e.BUTTON1_MASK) != 0)
          mainView.changeResolution(pressX, pressY, size.width, size.height, -1);
        else if((e.getModifiers() & e.BUTTON3_MASK) != 0)
          mainView.changeResolution(pressX, pressY, size.width, size.height, 1);

        adjustProgressBars();
        updateMiniView();
        repaint();

        if(parentWindow != null) {
          Dimension imageSize = getImageRealSize();
          parentWindow.notifyImageInfo("Imagen: " + imageSize.width + "x" + imageSize.height + "  Escala: " + getScale() + "%");
        }
      }
    }
  }

  private void adjustProgressBars()
  {
    Rectangle imageROI = mainView.getBounds();

    if(horScroll != null) {
      horScroll.setMinimum(0);
      horScroll.setMaximum(jp2Image.getWidth());
      horScroll.setValue(imageROI.x);
      horScroll.setVisibleAmount(imageROI.width);
    }

    if(verScroll != null) {
      verScroll.setMinimum(0);
      verScroll.setMaximum(jp2Image.getHeight());
      verScroll.setValue(imageROI.y);
      verScroll.setVisibleAmount(imageROI.height);
    }
  }

  public void mouseReleased(MouseEvent e)
  {
    if(mainView == null) return;

    if(e.getSource() == this) {
      if(zoomMode) return;
      else {
        setCursor((Cursor)ProyectoProperties.cursors.get("openedHand"));
        panelMoving = false;
      }

    } else if(e.getSource() == miniViewPanel) {

      miniViewPanel.setCursor((Cursor)ProyectoProperties.cursors.get("openedHand"));
      panelMoving = false;
    }

    mainView.setLocation(mainView.getX() - incrX, mainView.getY() - incrY);
    adjustProgressBars();

    mousePressed = false;

    incrX = 0;
    incrY = 0;
  }

  public void adjustmentValueChanged(AdjustmentEvent e)
  {
    if(panelMoving) return;

    if(!mousePressed) {
      horScroll.setValue(mainView.getX());
      verScroll.setValue(mainView.getY());

    } else {
      if(e.getAdjustable() == horScroll) incrX = mainView.getX() - e.getValue();
      else incrY = mainView.getY() - e.getValue();

      updateMiniView();
      repaint();
    }
  }

  public void openImage(String fname) throws Exception
  {
    if(parentWindow != null) parentWindow.notifyTotalBytes(0);

    setCursor((Cursor)ProyectoProperties.cursors.get("openedHand"));

    if(miniViewFrame != null) {
      miniViewFrame.setVisible(false);
      miniViewFrame = null;
    }

    incrX = 0;
    incrY = 0;
    mainView = null;
    zoomMode = false;
    panelMoving = false;
    mousePressed = false;

    jp2Image.open(fname);

    mainView = jp2Image.createView(700, 700);
    mainView.addObserver(this);
    mainView.make();

    adjustProgressBars();
  }

  public void showViewFrame()
  {
    if(miniViewFrame == null) {
      miniView = jp2Image.createView(300, 300);
      miniView.addObserver(this);

      miniViewFrame = new JInternalFrame("Vista", false, true, false, false);
      miniViewFrame.addInternalFrameListener(this);

      double mainViewScale = mainView.getScale();
      double miniViewScale = miniView.getScale();

      miniViewRect = new Rectangle(
        (int)((mainView.getX() / mainViewScale) * miniViewScale),
        (int)((mainView.getY() / mainViewScale) * miniViewScale),
        (int)((mainView.getWidth() / mainViewScale) * miniViewScale),
        (int)((mainView.getHeight() / mainViewScale) * miniViewScale)
      );

      if(miniViewRect.width >= miniView.getWidth()) miniViewRect.width = miniView.getWidth() - 1;
      if(miniViewRect.height >= miniView.getHeight()) miniViewRect.height = miniView.getHeight() - 1;

      miniViewPanel = new JPanel() {
        public void paint(Graphics g)
        {
          Image img = miniView.getImage();
          if(img != null) g.drawImage(img, 0, 0, this);

          g.setColor(Color.red);
          //g.setXORMode(Color.white);
          g.drawRect(miniViewRect.x, miniViewRect.y, miniViewRect.width, miniViewRect.height);
        }

        public Dimension getPreferredSize()
        {
          return miniView.getSize();
        }
      };

      miniViewPanel.addMouseListener(this);
      miniViewPanel.addMouseMotionListener(this);

      Container contentPane = miniViewFrame.getContentPane();
      contentPane.setLayout(new BorderLayout());
      contentPane.add(miniViewPanel, BorderLayout.CENTER);

      miniViewFrame.pack();
      miniViewFrame.setVisible(true);

      add(miniViewFrame);

      miniView.make();
    }

    miniViewFrame.setLocation(5, 5);
  }

  protected void paintComponent(Graphics g)
  {
    Dimension size = getSize();

    g.setColor(Color.black);
    g.fillRect(0, 0, size.width, size.height);

    if(mainView != null)
      g.drawImage(mainView.getImage(), incrX, incrY, this);
  }

  public void componentResized(ComponentEvent e)
  {
    if(mainView == null) return;

    Dimension newSize = getSize();
    mainView.setSize(newSize.width, newSize.height);
    adjustProgressBars();
    updateMiniView();
  }

  public void updateMiniView()
  {
    if(miniView == null) return;

    double mainViewScale = mainView.getScale();
    double miniViewScale = miniView.getScale();

    miniViewRect = new Rectangle(
      (int)(((mainView.getX() - incrX) / mainViewScale) * miniViewScale),
      (int)(((mainView.getY() - incrY) / mainViewScale) * miniViewScale),
      (int)((mainView.getWidth() / mainViewScale) * miniViewScale),
      (int)((mainView.getHeight() / mainViewScale) * miniViewScale)
    );

    if(miniViewRect.width >= miniView.getWidth()) miniViewRect.width = miniView.getWidth() - 1;
    if(miniViewRect.height >= miniView.getHeight()) miniViewRect.height = miniView.getHeight() - 1;

    miniViewPanel.repaint();
  }

   public void componentHidden(ComponentEvent e) {}
   public void componentMoved(ComponentEvent e) {}
   public void componentShown(ComponentEvent e) {}

   public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height)
   {
     if((mainView != null) && (img == mainView.getImage())) {
       if(infoflags != ImageObserver.ALLBITS) repaint(x, y, width, height);

     } else if((miniView != null) && (img == miniView.getImage())) {
       if(infoflags == ImageObserver.ALLBITS) mainView.make();
       else miniViewPanel.repaint(x, y, width, height);
     }

     if((parentWindow != null)  && (jp2Image.isRemote()))
       parentWindow.notifyTotalBytes(HTTPReader.getTotalBytesRead());

     return false;
   }

   public void internalFrameActivated(InternalFrameEvent e) {}
   public void internalFrameClosing(InternalFrameEvent e) {}
   public void internalFrameDeactivated(InternalFrameEvent e) {}
   public void internalFrameDeiconified(InternalFrameEvent e) {}
   public void internalFrameIconified(InternalFrameEvent e) {}
   public void internalFrameOpened(InternalFrameEvent e) {}

   public void internalFrameClosed(InternalFrameEvent e)
   {
     if(jp2Image.getActualView() == miniView) mainView.make();

     miniViewFrame = null;
     miniView = null;
   }
}