import kdu_jni.Kdu_dims;
import kdu_jni.Kdu_coords;
import kdu_jni.Kdu_compressed_source;
import kdu_jni.Kdu_codestream;
import kdu_jni.Kdu_channel_mapping;
import kdu_jni.Kdu_simple_file_source;
import kdu_jni.Jp2_family_src;
import kdu_jni.Jp2_source;
import kdu_jni.Jp2_locator;
import kdu_jni.KduException;

import java.awt.Rectangle;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.GraphicsConfiguration;

import java.io.File;

import java.util.ArrayList;


public class JP2Image
{
  private int discardLevels;
  private int maxDiscardLevels;      // Maximo numero de niveles a ignorar
  private int referenceComponent;    // Componente de referencia para la visualizacion
  private int maxComponents;         // Maximo numero de componentes a utilizar (0 == todos)
  private int imageRealWidth;        // Ancho real de la imagen
  private int imageRealHeight;       // Alto real de la imagen
  private int imageWidth;            // Ancho de la imagen (segun resolucion)
  private int imageHeight;           // Alto de la imagen (segun resolucion)
  private int numLayers;             // Numero de capas permitidas
  private int maxNumLayers;          // Numero maximo de capas permitidas
  private String imageName;

  private Kdu_dims varDim;
  private Kdu_coords minExpansion;
  private Kdu_coords expansion;
  private Kdu_compressed_source input;
  private Kdu_codestream codestream;
  private Kdu_channel_mapping channels;

  private Kdu_simple_file_source fileIn;
  private Jp2_family_src jp2FamilyIn;
  private Jp2_source jp2In;
  private Jp2_locator jp2Loc;

  private JP2Reader jp2Reader;
  private JP2Render jp2Render;
  private JP2ImageView actualView;

  private Mutex codestreamMutex;


  public JP2Image()
  {
    input = null;

    codestreamMutex = new Mutex();

    minExpansion = new Kdu_coords();
    expansion = new Kdu_coords();
    varDim = new Kdu_dims();

    codestream = new Kdu_codestream();
    channels = new Kdu_channel_mapping();

    fileIn = new Kdu_simple_file_source();
    jp2FamilyIn = new Jp2_family_src();
    jp2Loc = new Jp2_locator();
    jp2In = new Jp2_source();

    jp2Reader = null;
    actualView = null;
    jp2Render = new JP2Render(this);
  }

  public void lockCodestream()
  {
    codestreamMutex.lock();
  }

  public void unlockCodestream()
  {
    codestreamMutex.unlock();
  }

  public synchronized void stopDecoding()
  {
    System.out.println("]]Procedo a parar la decodificacion");
    if(jp2Reader != null) jp2Reader.stop();
    jp2Render.stop();
    System.out.println("]]Decodificacion parada");

    actualView = null;
  }

  public synchronized void startDecoding(JP2ImageView newView)
  {
    if(newView.isCompleted()) return;
    if(actualView == newView) return;

    actualView = newView;
    discardLevels = newView.getResolution();

    try {
      codestream.Apply_input_restrictions(0, maxComponents, discardLevels, 0, null);
      codestream.Get_dims(referenceComponent, varDim);

      imageWidth = varDim.Access_size().Get_x();
      imageHeight = varDim.Access_size().Get_y();

      Kdu_dims region = new Kdu_dims();
      Kdu_dims realRegion = new Kdu_dims();

      region.Access_pos().Set_x(newView.getX());
      region.Access_pos().Set_y(newView.getY());
      region.Access_size().Set_x(newView.getWidth());
      region.Access_size().Set_y(newView.getHeight());

      codestream.Map_region(0, region, realRegion);
      codestream.Apply_input_restrictions(0, maxComponents, discardLevels, 0, realRegion);

    } catch(KduException ex) { }

    jp2Render.start();
    if(jp2Reader != null) jp2Reader.start();
  }

  public boolean isRemote()
  {
    return (jp2Reader != null);
  }

  public JP2ImageView getActualView()
  {
    return actualView;
  }

  private void determineReferenceExpansion() throws KduException
  {
    maxComponents = 0;

    Kdu_coords ref_subs = new Kdu_coords();
    Kdu_coords subs = new Kdu_coords();
    codestream.Get_subsampling(referenceComponent, ref_subs);
    Kdu_coords min_subs = new Kdu_coords(); min_subs.Assign(ref_subs);

    for(int c = 0; c < channels.Get_num_channels(); c++)
    {
      codestream.Get_subsampling(channels.Get_source_component(c),subs);
      if(subs.Get_x() < min_subs.Get_x()) min_subs.Set_x(subs.Get_x());
      if (subs.Get_y() < min_subs.Get_y()) min_subs.Set_y(subs.Get_y());
    }

    minExpansion.Set_x(ref_subs.Get_x() / min_subs.Get_x());
    minExpansion.Set_y(ref_subs.Get_y() / min_subs.Get_y());

    for(int c = 0; c < channels.Get_num_channels(); c++)
    {
      codestream.Get_subsampling(channels.Get_source_component(c), subs);
      if((((subs.Get_x() * minExpansion.Get_x()) % ref_subs.Get_x()) != 0) ||
         (((subs.Get_y() * minExpansion.Get_y()) % ref_subs.Get_y()) != 0))
          {
            maxComponents = 1;
            codestream.Apply_input_restrictions(0, maxComponents, 0, 0, null);
            channels.Configure(codestream);

            minExpansion.Set_x(1);
            minExpansion.Set_y(1);
          }
    }

    expansion.Assign(minExpansion);
  }

  public void close() throws KduException
  {
    stopDecoding();

    input = null;
    jp2Reader = null;

    if(codestream.Exists()) codestream.Destroy();
    if(fileIn.Exists()) fileIn.Close();
    if(jp2FamilyIn.Exists()) jp2FamilyIn.Close();
    if(jp2In.Exists()) jp2In.Close();

    channels.Clear();
  }

  public String getImageName()
  {
    return imageName;
  }

  public void open(String fname) throws Exception
  {
    if(input != null) close();

    imageName = fname;

    HTTPReader.resetTotalBytesRead();

    String upper = fname.toUpperCase();
    if(upper.endsWith("JP2") || upper.endsWith("JPX")) {

      jp2FamilyIn.Open(fname, true);
      jp2In.Open(jp2FamilyIn, jp2Loc);
      jp2In.Read_header();
      input = jp2In;

    } else if(upper.startsWith("HTTP://")) {

      jp2Reader = new JP2Reader(this);
      input = jp2Reader;

    } else {

      fileIn.Open(fname, true);
      input = fileIn;
    }

    codestream.Create(input);
    if(jp2In.Exists()) channels.Configure(jp2In, false);
    else channels.Configure(codestream);

    discardLevels = 0;
    maxDiscardLevels = codestream.Get_min_dwt_levels();
    referenceComponent = channels.Get_source_component(0);
    determineReferenceExpansion();
    codestream.Get_dims(referenceComponent, varDim);
    imageWidth = imageRealWidth = varDim.Access_size().Get_x();
    imageHeight = imageRealHeight = varDim.Access_size().Get_y();
    numLayers = maxNumLayers = codestream.Get_max_tile_layers();

    codestream.Set_persistent();

    if(jp2Reader != null) jp2Reader.init();

    /**/System.out.println("Imagen: " + fname);
    /**/System.out.println("  - Niveles de resolucion: " + maxDiscardLevels);
    /**/System.out.println("  - Expansion minima: " + minExpansion.Get_x() + " x " + minExpansion.Get_y());
    /**/System.out.println("  - Tamaņo original de la imagen: " + imageRealWidth + " x " + imageRealHeight);
    /**/System.out.println("  - Numero de capas: " + maxNumLayers);
  }

  public JP2ImageView createView()
  {
    if(input == null) return null;
    return new JP2ImageView(this, null, 0);
  }

  public JP2ImageView createView(int limitWidth, int limitHeight)
  {
    if(input == null) return null;

    int res = 0;
    int or_width = imageRealWidth;
    int or_height = imageRealHeight;
    while(or_width > limitWidth || or_height > limitHeight) {
      or_width >>= 1;
      or_height >>= 1;
      res++;
    }

    return new JP2ImageView(this, null, res);
  }

  public void adjustImageSize() throws KduException
  {
    if(input == null) return;

    discardLevels = 0;
    imageWidth = imageRealWidth;
    imageHeight = imageRealHeight;

    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice gd = ge.getDefaultScreenDevice();
    GraphicsConfiguration gc = gd.getDefaultConfiguration();
    Rectangle rec = gc.getBounds();

    int or_width = imageWidth + 20;
    int or_height = imageHeight + 20;
    while(or_width > rec.getWidth() || or_height > rec.getHeight()) {
      discardLevels++;

      or_width >>= 1;
      or_height >>= 1;
    }

    if(discardLevels > maxDiscardLevels)
      discardLevels = maxDiscardLevels;

    codestream.Apply_input_restrictions(0, maxComponents, discardLevels, 0, null);
    codestream.Get_dims(referenceComponent, varDim);

    imageWidth = varDim.Access_size().Get_x();
    imageHeight = varDim.Access_size().Get_y();

    /**/System.out.println("  - Resolucion de la pantalla: " + rec.getWidth() + " x " + rec.getHeight());
    /**/System.out.println("  - Tamaņo final de la imagen: " + imageWidth + " x " + imageHeight);
    /**/System.out.println("  - Niveles a descartar: " + discardLevels);
    /**/System.out.println("");
  }

  public Kdu_codestream getCodestream() { return codestream; }
  public Kdu_channel_mapping getChannels() { return channels; }
  public int getDiscardLevels() { return discardLevels; }
  public int getMaxDiscardLevels() { return maxDiscardLevels; }
  public int getReferenceComponent() { return referenceComponent; }
  public Kdu_coords getExpansion() { return expansion; }
  public int getNumLayers() { return numLayers; }
  public int getRealWidth() { return imageRealWidth; }
  public int getRealHeight() { return imageRealHeight; }
  public int getWidth() { return imageWidth; }
  public int getHeight() { return imageHeight; }
}