import java.awt.Point;
import java.awt.Dimension;
import java.awt.Rectangle;

import java.util.ArrayList;
import java.util.LinkedList;

import java.awt.image.DataBufferInt;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;


public class JP2ImageView
{
  private int discardLevels;          // Niveles a ignorar en la imagen
  private int[] imageBuffer;          // Buffer de la imagen
  private BufferedImage image;        // Imagen
  private Rectangle imageROI;         // Region de la vista
  private Rectangle newImageROI;      // Nueva region
  private ArrayList observers;        // Observadores de la vista
  private JP2Image jp2Image;          // Imagen JPEG2000 asociada
  boolean completed;                  // Indica si esta completa o no la imagen
  boolean contentCompleted;           // Indica si el contenido esta completo o no
  private LinkedList regionsList;     // Lista de regiones a descomprimir
  private int imageWidth;             // Ancho de la resolucion asociada
  private int imageHeight;            // Alto de la resolucion asociada


  public JP2ImageView(JP2Image jp2Image, Rectangle roi, int resolution)
  {
    image = null;
    completed = false;
    this.jp2Image = jp2Image;
    discardLevels = resolution;
    observers = new ArrayList();

    regionsList = new LinkedList();

    imageROI = new Rectangle(0, 0, 0, 0);
    newImageROI = new Rectangle(0, 0, 0, 0);

    contentCompleted = !jp2Image.isRemote();

    imageWidth = (int)Math.ceil((double)jp2Image.getRealWidth() / (double)(1 << discardLevels));
    imageHeight = (int)Math.ceil((double)jp2Image.getRealHeight() / (double)(1 << discardLevels));

    if(roi != null) {
      imageROI.setBounds(roi);
      newImageROI.setBounds(roi);

    } else {
      imageROI.setBounds(0, 0, imageWidth, imageHeight);
      newImageROI.setBounds(0, 0, imageWidth, imageHeight);
    }

    regionsList.add(imageROI);
  }

  public void make()
  {
    if(!completed) {
      jp2Image.stopDecoding();
      changeROI();
      jp2Image.startDecoding(this);
    }
  }

  public int getImageWidth()
  {
    return imageWidth;
  }

  public int getImageHeight()
  {
    return imageHeight;
  }

  public int getResolution()
  {
    return discardLevels;
  }

  public LinkedList getRegionsToDecode()
  {
    return regionsList;
  }

  public boolean isCompleted()
  {
    return completed;
  }

  public boolean isContentCompleted()
  {
    return contentCompleted;
  }

  public void setContentCompleted()
  {
    contentCompleted = true;
  }

  public void setCompleted()
  {
    int numObservers = observers.size();
    for(int i = 0; i < numObservers; i++) {
      System.out.println("--> Notifico completitud");
      ((ImageObserver)observers.get(i)).imageUpdate(image, ImageObserver.ALLBITS, 0, 0, 0, 0);
    }

    completed = true;
  }

  public void addObserver(ImageObserver observer)
  {
    observers.add(observer);
  }

  public void removeObserver(ImageObserver observer)
  {
    observers.remove(observer);
  }

  public void setBounds(Rectangle newRect)
  {
    jp2Image.stopDecoding();
    newImageROI.setBounds(newRect);
    changeROI();
    jp2Image.startDecoding(this);
  }

  public void setLocation(int x, int y)
  {
    jp2Image.stopDecoding();
    newImageROI.setLocation(x, y);
    changeROI();
    jp2Image.startDecoding(this);
  }

  public void setSize(int width, int height)
  {
    jp2Image.stopDecoding();
    newImageROI.setSize(width, height);
    changeROI();
    jp2Image.startDecoding(this);
  }

  public void changeResolution(int px, int py, int width, int height, int resIncr)
  {
    int newDiscardLevels = discardLevels + resIncr;

    if(newDiscardLevels < 0) newDiscardLevels = 0;
    if(newDiscardLevels > jp2Image.getMaxDiscardLevels()) newDiscardLevels = jp2Image.getMaxDiscardLevels();

    if(discardLevels == newDiscardLevels) return;

    image = null;

    px = (imageROI.x + px) * (1 << discardLevels);
    py = (imageROI.y + py) * (1 << discardLevels);

    discardLevels = newDiscardLevels;

    px /= (1 << discardLevels);
    py /= (1 << discardLevels);

    px -= (width / 2);
    if(width % 2 != 0) px--;
    py -= (height / 2);
    if(height % 2 != 0) py--;

    imageWidth = (int)Math.ceil((double)jp2Image.getRealWidth() / (double)(1 << discardLevels));
    imageHeight = (int)Math.ceil((double)jp2Image.getRealHeight() / (double)(1 << discardLevels));

    jp2Image.stopDecoding();

    newImageROI.setBounds(px, py, width, height);
    changeROI();

    jp2Image.startDecoding(this);
  }

  public int getX() { return imageROI.x; }
  public int getY() { return imageROI.y; }
  public int getWidth() { return imageROI.width; }
  public int getHeight() { return imageROI.height; }
  public Dimension getSize() { return imageROI.getSize(); }
  public Point getLocation() { return imageROI.getLocation(); }
  public Rectangle getBounds() { return new Rectangle(imageROI); }

  private void changeROI()
  {
    if(newImageROI.equals(imageROI) && (image != null)) return;

    contentCompleted = !jp2Image.isRemote();
    completed = false;

    if(newImageROI.width > imageWidth) newImageROI.width = imageWidth;
    if(newImageROI.height > imageHeight) newImageROI.height = imageHeight;
    if(newImageROI.x < 0) newImageROI.x = 0;
    if(newImageROI.y < 0) newImageROI.y = 0;

    if(newImageROI.x + newImageROI.width > imageWidth)
      newImageROI.x -= ((newImageROI.x + newImageROI.width) - imageWidth);
    if(newImageROI.y + newImageROI.height > imageHeight)
      newImageROI.y -= ((newImageROI.y + newImageROI.height) - imageHeight);

    BufferedImage newImage = new BufferedImage(newImageROI.width, newImageROI.height, BufferedImage.TYPE_INT_RGB);
    int[] newImageBuffer = ((DataBufferInt)newImage.getRaster().getDataBuffer()).getData();

    if((image == null) || !imageROI.intersects(newImageROI)) {
      regionsList.clear();
      regionsList.add(newImageROI);
      System.out.println("Añadoooo");

    } else {
      Rectangle copyRect = imageROI.intersection(newImageROI);

      int orgX = copyRect.x - imageROI.x;
      int orgY = copyRect.y - imageROI.y;
      int destX = copyRect.x - newImageROI.x;
      int destY = copyRect.y - newImageROI.y;

      int orgPos = (orgX + (orgY * imageROI.width));
      int destPos = (destX + (destY * newImageROI.width));

      for(int j = 0; j < copyRect.height; j++) {
        System.arraycopy(imageBuffer, orgPos, newImageBuffer, destPos, copyRect.width);
        destPos += newImageROI.width;
        orgPos += imageROI.width;
      }

      regionsList.clear();
      regionsList.add(newImageROI);

      /*
      Rectangle region;
      int listSize = regionsList.size();

      for(int i = 0; i < listSize; i++) {
        region = (Rectangle)regionsList.removeFirst();
        if(region.intersects(imageROI))
          regionsList.add(region.intersection(imageROI));
      }

      if(copyRect.y != newImageROI.y)
        regionsList.add(
          new Rectangle(newImageROI.x, newImageROI.y, newImageROI.width, copyRect.y - newImageROI.y)
        );

      if(copyRect.x != newImageROI.x)
        regionsList.add(
          new Rectangle(newImageROI.x, copyRect.y, copyRect.x - newImageROI.x, copyRect.height)
        );

      if(copyRect.width != newImageROI.width)
        regionsList.add(
          new Rectangle(copyRect.x + copyRect.width, copyRect.y, (newImageROI.x + newImageROI.width) - (copyRect.x + copyRect.width), copyRect.height)
        );

      if(copyRect.height != newImageROI.height)
        regionsList.add(
          new Rectangle(newImageROI.x, copyRect.y + copyRect.height, newImageROI.width, (newImageROI.y + newImageROI.height) - (copyRect.y + copyRect.height))
        );*/
    }

    image = newImage;
    imageBuffer = newImageBuffer;
    imageROI.setBounds(newImageROI);
  }

  public void updateNewRegion(Rectangle region, int regionBuffer[])
  {
    if(imageBuffer == null) return;

    if(imageROI.intersects(region)) {

      Rectangle copyRect = imageROI.intersection(region);

      int orgX = copyRect.x - region.x;
      int orgY = copyRect.y - region.y;
      int destX = copyRect.x - imageROI.x;
      int destY = copyRect.y - imageROI.y;

      int orgPos = (orgX + (orgY * region.width));
      int destPos = (destX + (destY * imageROI.width));

      for(int j = 0; j < copyRect.height; j++) {
        System.arraycopy(regionBuffer, orgPos, imageBuffer, destPos, copyRect.width);
        orgPos += region.width;
        destPos += imageROI.width;
      }

      int numObservers = observers.size();
      for(int i = 0; i < numObservers; i++)
        ((ImageObserver)observers.get(i)).imageUpdate(image, 0, destX, destY, copyRect.width, copyRect.height);
    }
  }

  public BufferedImage getImage()
  {
    return image;
  }

  public double getScale()
  {
    return (100.0 / (double)(1 << discardLevels));
  }
}


