/**
 * HistoJ plugin 
 * @author antreas gribas
 */

import ij.IJ;
import ij.ImagePlus;
import ij.Macro;
import ij.gui.GenericDialog;
import ij.gui.ImageWindow;
import ij.gui.Overlay;
import ij.gui.PolygonRoi;
import ij.gui.Roi;
import ij.plugin.PlugIn;
import ij.process.ImageProcessor;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Checkbox;
import java.awt.CheckboxGroup;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridLayout;
import java.awt.Label;
import java.awt.Panel;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.TextEvent;
import java.awt.event.TextListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.rmi.RemoteException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.swing.JFileChooser;
import javax.swing.UIManager;

public class HistoJLite_ implements PlugIn {

    public static final String TitleVersionString = " - Pathomation v1.0";

    private String pathoruid;
    private ImageInfo Ii;

    private PmaCoreClient client;

    public static final int MaxWidth = 2500;
    public static final int MaxHeight = 2500;

    private Roi CropRegion = null;

    private double FinalScale = 1.0;
    private int FinalChannel = 0;

    private double scalePreview = 0.0;
    Channel[] ch = null; 

    public HistoJLite_() {
    }

    @Override
    public void run(String arg) {
	this.client = new PmaCoreClient("http://localhost:54001/");

	if (IJ.isMacro() && Macro.getOptions() != null && !Macro.getOptions().trim().isEmpty()) {
	    MacroMode(Macro.getOptions().trim());
	    return;
	}

	try {
	    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
	} catch (Exception e1) {
	    e1.printStackTrace();
	}

	HistoJFileSystemView fsv;
	try {
	    fsv = new HistoJFileSystemView(client);
	} catch (RemoteException e1) {
	    return;
	}

	HistoFileChooser jfc = new HistoFileChooser(fsv);

	// jfc.setCurrentDirectory(fsv.getHomeDirectory());
	int returnVal = jfc.showDialog(IJ.getInstance(), "Open");
	if (returnVal == JFileChooser.APPROVE_OPTION) {
	    File file = jfc.getSelectedFile();
	    this.pathoruid = file.getPath().replace("\\", "/");
	    int i = this.pathoruid.indexOf("/");
	    if (i != -1) {
		this.pathoruid = this.pathoruid.substring(i + 1);
	    }

	    Ii = client.getImageInfo(this.pathoruid);

	    if (Ii == null) {
		IJ.error(this.pathoruid + "  Cannot get image info!");
		return;
	    }

	    this.ch = Ii.getTimeFrames()[0].getLayers()[0].getChannels();

	    // Fetch the preview in size 1000xsomething or somethingx1000
	    long maxDimension;
	    if (Ii.getWidth() > Ii.getHeight()) {
		maxDimension = Ii.getWidth();
	    } else {
		maxDimension = Ii.getHeight();
	    }

	    this.scalePreview = 1000.0 / maxDimension;
	    if (scalePreview > 1.0)
		scalePreview = 1.0;

	    // get preview image FIRST CHANNEL
	    ImagePlus impCrop = this.fetchImage(0, 0, Ii.getWidth() * scalePreview, Ii.getHeight() * scalePreview, scalePreview, 0, "Select a cropping region"
		    + HistoJLite_.TitleVersionString);

	    final ImageWindow imageWindow = new ImageWindow(impCrop);
	    imageWindow.running = true;

	    final double scalePreviewCopy = scalePreview;

	    Panel panel = new Panel(new FlowLayout(FlowLayout.CENTER));
	    Button okButton = new Button("    OK    ");
	    Button cancelButton = new Button("Cancel");
	    imageWindow.add(new Label("Use the rectangle tool to optionally select an area"));
	    panel.add(okButton);
	    panel.add(cancelButton);

	    imageWindow.add(panel);
	    imageWindow.invalidate();
	    imageWindow.validate();
	    // imageWindow.revalidate();

	    imageWindow.setSize(Math.max(panel.getWidth(), imageWindow.getWidth()), imageWindow.getHeight() + 60);
	    imageWindow.validate();

	    okButton.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    imageWindow.close();
		    continueAfterRoi();
		}
	    });

	    cancelButton.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    imageWindow.close();
		}
	    });

	    imageWindow.getCanvas().addMouseMotionListener(new MouseMotionListener() {

		private void update() {
		    if (imageWindow != null) {
			CropRegion = imageWindow.getImagePlus().getRoi();
		    }
		}

		public void mouseMoved(MouseEvent e) {
		}

		@Override
		public void mouseDragged(MouseEvent arg0) {
		    update();
		}
	    });

	    imageWindow.addWindowListener(new WindowListener() {

		@Override
		public void windowClosed(WindowEvent e) {
		}

		public void windowActivated(WindowEvent e) {
		}

		public void windowClosing(WindowEvent e) {
		}

		public void windowDeactivated(WindowEvent e) {
		}

		public void windowDeiconified(WindowEvent e) {
		}

		public void windowIconified(WindowEvent e) {
		}

		public void windowOpened(WindowEvent e) {
		}
	    });
	}
    }

    private void MacroMode(String options) {
	String[] splitted = options.split(",");
	this.pathoruid = splitted[0];
	String[] dirs = client.getDirectories("");
	
	String[] rootsplits = this.pathoruid.split("/");
	if (rootsplits.length == 0)
	{
	    IJ.error("Invalid directory");
	    return;
	}
	String root = rootsplits[0];
	
	if (!root.endsWith(":")) {
	    IJ.error("Invalid directory");
	    return;
	}
	
	String rootDirectory = "";
	for(String dir : dirs) {
	    if (dir.equalsIgnoreCase(root) || dir.endsWith("(" + root +")")){
		rootDirectory = dir;
		break;
	    }
	}
	
	if (rootDirectory == ""){
	    IJ.error("Invalid directory");
	    return;
	}
	this.pathoruid = rootDirectory + this.pathoruid.substring(root.length());
		
	String scaleString = "small";

	if (splitted.length > 1) {
	    if (splitted[1].toLowerCase().trim().startsWith("scale=")) {
		scaleString = splitted[1].split("=")[1].toLowerCase();
	    }
	}

	Ii = client.getImageInfo(this.pathoruid);
	if (Ii == null) {
	    IJ.error(this.pathoruid + "  Cannot get image info!");
	    return;
	}

	double scale = 1000.0 / Math.max(Ii.getHeight(), Ii.getWidth());
	if (scaleString.trim().equalsIgnoreCase("large")) {
	    scale = 1.0;
	}

	if (scaleString.trim().equalsIgnoreCase("medium")) {
	    scale = 2500.0 / Math.max(Ii.getHeight(), Ii.getWidth());
	}

	scale = Math.min(1.0, scale);
	// get preview image FIRST CHANNEL
	ImagePlus impfinal = this.fetchImage(0, 0, Ii.getWidth() * scale, Ii.getHeight() * scale, scale, 0, Ii.getFilename() + HistoJLite_.TitleVersionString);
	impfinal.show();
	impfinal.draw();
    }

    public void continueAfterRoi() {
	double newScale = 1.0;
	Roi croppedRoi = new Roi(0, 0, Ii.getWidth(), Ii.getHeight());

	if (CropRegion != null) {
	    // We have cropping....
	    croppedRoi = new Roi(CropRegion.getBounds().x / scalePreview, CropRegion.getBounds().y / scalePreview, CropRegion.getBounds().width / scalePreview,
		    CropRegion.getBounds().height / scalePreview);
	}

	// Now show the scale dialog
	if (this.showScaleDialog(croppedRoi.getBounds().width, croppedRoi.getBounds().height, ch)) {

	    // Now the scale dialog results are in this.FinalScale and
	    // this.FinalChannel
	    ImagePlus impfinal = this.fetchImage((int) (croppedRoi.getBounds().x * this.FinalScale), (int) (croppedRoi.getBounds().y * this.FinalScale),
		    croppedRoi.getBounds().width * this.FinalScale, croppedRoi.getBounds().height * this.FinalScale, this.FinalScale, this.FinalChannel,
		    Ii.getFilename() + HistoJLite_.TitleVersionString);

	    impfinal.show();
	    impfinal.draw();
	}
    }

    public ImagePlus fetchImage(int xx, int yy, double width, double height, double scale, Integer channelId, String title) {
	StringBuffer u = null;
	int x = xx;
	int y = yy;
	double fwidth = width;
	double fheight = height;

	boolean isFirst = true;
	ImagePlus impfinal = null;
	int max = (((int) (fwidth / HistoJLite_.MaxWidth)) + 1) * (((int) (fheight / HistoJLite_.MaxHeight)) + 1);
	int progressi = 0;
	while ((int) (fwidth) > 0) {
	    while ((int) (fheight) > 0) {
		int tempwidth = (fwidth < HistoJLite_.MaxWidth) ? (int) (fwidth / scale) : (int) (HistoJLite_.MaxWidth / scale);
		int tempheight = (fheight < HistoJLite_.MaxHeight) ? (int) (fheight / scale) : (int) (HistoJLite_.MaxHeight / scale);
		try {
		    u = new StringBuffer(Ii.getBaseUrl() + "Region?drawScaleBar=" + String.valueOf(isFirst) + "&sessionID=pma.view.lite" + "&PathOrUid="
			    + URLEncoder.encode(pathoruid, "UTF-8") + "&timeframe=0&channels=" + channelId.toString() + "&layer=0&x=" + (int) (x / scale)
			    + "&y=" + (int) (y / scale) + "&width=" + String.valueOf(tempwidth) + "&height=" + String.valueOf(tempheight) + "&scale=");
		    u = u.append(scale);
		    // System.err.println(u.toString());
		} catch (UnsupportedEncodingException e) {
		    e.printStackTrace();
		}

		// ExecutorService exec = Executors.newFixedThreadPool(4);
		ImagePlus imp = new ImagePlus(u.toString());
		IJ.showProgress(progressi, max);

		if (isFirst) {
		    ImageProcessor ip = imp.getProcessor().createProcessor((int) fwidth, (int) fheight);
		    impfinal = new ImagePlus(title, ip);
		    isFirst = false;
		    impfinal.show();
		}
		impfinal.draw();
		impfinal.getProcessor().insert(imp.getProcessor(), x - xx, y - yy);
		y += HistoJLite_.MaxHeight;
		fheight -= HistoJLite_.MaxHeight;
		progressi++;
	    }
	    fheight = height * scale;
	    y = yy;
	    x += HistoJLite_.MaxWidth;
	    fwidth -= HistoJLite_.MaxWidth;
	}
	impfinal.getCalibration().setUnit("um");
	impfinal.getCalibration().pixelWidth = Ii.getMicrometresPerPixelX() / scale;
	impfinal.getCalibration().pixelHeight = Ii.getMicrometresPerPixelY() / scale;

	return impfinal;
    }

    public boolean showScaleDialog(long width, long height, Channel[] channels) {
	List<String> channelsNames = new ArrayList<String>();
	for (Channel ch1 : channels) {
	    channelsNames.add(ch1.getName());
	}

	double scalesmall;
	double scalemedium, scalelarge;
	Long maxDimension;
	if (width > height)
	    maxDimension = width;
	else
	    maxDimension = height;

	scalesmall = 1000.0 / maxDimension;
	if (scalesmall > 1.0)
	    scalesmall = 1.0;
	scalemedium = 2500.0 / maxDimension;
	scalelarge = 1.0;

	String defaultItem = channelsNames.get(0);

	GenericDialog gd1 = new GenericDialog("Choose Scale");
	gd1.addChoice("Available Channels", channelsNames.toArray(new String[channelsNames.size()]), defaultItem);

	NumberFormat nf = NumberFormat.getInstance();
	nf.setMaximumFractionDigits(0);
	nf.setGroupingUsed(false);
	nf.setParseIntegerOnly(true);
	String value = nf.format(width * scalesmall) + "x" + nf.format(height * scalesmall);
	gd1.addMessage(value);
	Label lb = (Label) gd1.getMessage();

	CheckboxGroup group = new CheckboxGroup();
	Checkbox chkSmall = new Checkbox("Small", group, true);
	Checkbox chkMedium = new Checkbox("Medium", group, false);
	Checkbox chkLarge = new Checkbox("Full Size", group, false);
	Panel panel = new Panel();
	panel.setLayout(new GridLayout(1, 3, 6, 0));
	panel.add(chkSmall);
	panel.add(chkMedium);
	panel.add(chkLarge);
	GridBagConstraints cc = new GridBagConstraints();
	cc.gridx = 0;
	cc.gridy = GridBagConstraints.RELATIVE;
	cc.gridwidth = 2;
	cc.anchor = GridBagConstraints.WEST;
	gd1.addPanel(panel);

	if (scalemedium > 1.0)
	    chkMedium.setEnabled(false);
	if (scalelarge > 1.0)
	    chkLarge.setEnabled(false);
	ItemListener Il = new myItemListener(lb, width, height, scalesmall, scalemedium, scalelarge);
	chkSmall.addItemListener(Il);
	chkMedium.addItemListener(Il);
	chkLarge.addItemListener(Il);
	group.setSelectedCheckbox(chkSmall);

	gd1.showDialog();
	if (gd1.wasCanceled())
	    return false;

	String channelselected = gd1.getNextChoice();
	Integer channelId = 0;
	for (Channel ch1 : channels) {
	    if (ch1.getName().equals(channelselected)) {
		channelId = ch1.getChannelID();
		break;
	    }
	}

	this.FinalChannel = channelId;

	String label = group.getSelectedCheckbox().getLabel();
	if (label == "Small") {
	    this.FinalScale = scalesmall;
	} else if (label == "Medium") {
	    this.FinalScale = scalemedium;
	} else if (label == "Large") {
	    this.FinalScale = scalelarge;
	}

	return true;
    }

    public class myTextListener implements TextListener {
	private Label m_lb = null;
	private Double w, h, scale = 1.0;

	myTextListener(Label lb, double width, double height) {
	    m_lb = lb;
	    w = width;
	    h = height;
	}

	@Override
	public void textValueChanged(TextEvent evt) {
	    TextField tf = (TextField) evt.getSource();
	    try {
		scale = Double.valueOf(tf.getText());
	    } catch (Exception e) {
		return;
	    }
	    Double w1 = 0.0;
	    Double h1 = 0.0;
	    if (scale != 0) {
		w1 = (scale * w);
		h1 = (h * scale);
	    }
	    NumberFormat nf = NumberFormat.getInstance();
	    nf.setMaximumFractionDigits(0);
	    nf.setGroupingUsed(false);
	    nf.setParseIntegerOnly(true);
	    String value = nf.format(w1) + "x" + nf.format(h1);
	    m_lb.setText(value);
	}
    }

    public class myItemListener implements ItemListener {
	private Label m_lb = null;
	private Double w, h, scalesmall = 1.0, scalemedium, scalelarge;

	myItemListener(Label lb, double width, double height, Double Scalesmall, Double Scalemedium, Double Scalelarge) {
	    m_lb = lb;
	    w = width;
	    h = height;
	    scalesmall = Scalesmall;
	    scalemedium = Scalemedium;
	    scalelarge = Scalelarge;
	}

	@Override
	public void itemStateChanged(ItemEvent e) {
	    Double scale = 1.0;
	    Double w1 = 0.0;
	    Double h1 = 0.0;

	    String label = (String) e.getItem();
	    if (label == "Small") {
		scale = scalesmall;
	    } else if (label == "Medium") {
		scale = scalemedium;
	    } else if (label == "Large") {
		scale = scalelarge;
	    }
	    /*
	     * switch ( (String) e.getItem()) { case "Small": scale =
	     * scalesmall; break; case "Medium": scale = scalemedium; break;
	     * case "Large": scale = scalelarge; break; }
	     */

	    if (scale != 0) {
		w1 = (scale * w);
		h1 = (h * scale);
	    }
	    NumberFormat nf = NumberFormat.getInstance();
	    nf.setMaximumFractionDigits(0);
	    nf.setGroupingUsed(false);
	    nf.setParseIntegerOnly(true);
	    String value = nf.format(w1) + "x" + nf.format(h1);
	    m_lb.setText(value);
	}

    }
}