import java.util.prefs.*;
import java.awt.Color;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.IOException;

import org.apache.commons.cli.*;
import com.lowagie.text.Font;

import com.xinapse.apps.cardiac.CardiacAnalysis;
import com.xinapse.apps.cardiac.SelectableCardiacAnalysis;
import com.xinapse.apps.cardiac.CardiacFrame;
import com.xinapse.image.PixelDataType;
import com.xinapse.image.ComplexMode;
import com.xinapse.image.ReadableImage;
import com.xinapse.image.InvalidImageException;
import com.xinapse.util.MonitorWorker;
import com.xinapse.util.InvalidArgumentException;
import com.xinapse.util.CancelledException;
import com.xinapse.multisliceimage.roi.ROI;
import com.xinapse.multisliceimage.roi.ROIStats;
import com.xinapse.multisliceimage.roi.RadialDivider;
import com.xinapse.multisliceimage.roi.ROIException;

/**
   Classes that perform calculations on cardiac segments must extend SelectableCardiacAnalysis.

   This example class simply caluclates the areas of the cardiac segments,
   and outputs them to a file called "SegmentAreas.txt" in the user's home folder.
*/
public class SegmentArea extends SelectableCardiacAnalysis {

  /** The calculated segment areas. First index is the slice number; second index is the
      time-point; third index is the segment number. */
  private double[][][] segmentAreas;

  /** A record of the endo- epicardium split percentage, or null if there is no split. */
  private Integer endoEpicardiumSplitPercent;

  /**
     Returns the name of this analysis, which appears in the tabbed pane to select the
     segmental analysis.

     @return the name of this analysis.
  */
  @Override public String getAnalysisName() {
    return("Segment Areas");
  }

  /**
     Returns a short (normally one word) description of the analysis.

     @return a short description of the analysis.
  */
  @Override public String getAnalysisDescription() {
    return("segment areas");
  }

  /**
     Returns the name to be used when selecting this analysis from the command line.

     @return the name used for selecting this analysis from the command line.
  */
  public static String getOptionName() {
    return("areas");
  }

  /**
     Returns the array of options that may be selected with this analysis.
     In this simple case, there are no options, so it returns an array of length zero.

     @return the array of options that may be selected with this analysis.
  */
  public static Option[] getAnalysisOptions() {
    return(new Option[] {});
  }

  /**
     A public constructor that takes no arguments must be supplied.
  */
  public SegmentArea() {
  }

  /**
     Constructor used by command-line version of the tool.

     @param commandLine the command line (for the command-line version of the tool).
     @param contiguousTimes whether time points are contiguous in the input image(s).
     @param nTimePoints the number of time points in the input image(s).
     @param pdfReport whether a PDF repost has been requested.
     @param verbose whether verbose reprting to System.out is turned on.
  */
  public SegmentArea(CommandLine commandLine, Boolean contiguousTimes, Integer nTimePoints,
                     Boolean pdfReport, Boolean verbose) {
    // Ignore any command-line arguments - none are applicable to SegmentArea.
    super(contiguousTimes, nTimePoints, pdfReport, verbose);
  }

  /**
     Performs the segmental analysis on the ROIs and reports the results.

     @param parentFrame if non-null, the CardiacFrame from which the segmental analysis was
     inititated.
     @param radialDividers the array of RadialDivders - one for every every physical slice location.
     @param endoEpicardiumSplitPercent if non-null, the percentage split between enocardium and
     epicardium.
     @param rois and array of ROIs. The first index refers to the slice number; the second
     index to the time point; and the third index to the ROI.
     @param inputImages the input images.
     @param contiguousTimes  whether time points are contiguous in the input image(s).
     @param nTimePoints the number of time points in the input image(s).
     @param nCols the number of columns of pixels in the input images.
     @param nRows the number of rows of pixels in the input images.
     @param pixelXSize the pixel width in mm.
     @param pixelYSize the pixel height in mm.
     @param pixelZSize the slice thickness in mm.
     @param worker the MonitorWorker that may be used to check whether the operation has been
     cancelled.
  */
  @Override public void doAnalysis(CardiacFrame parentFrame, RadialDivider[] radialDividers,
                                   Integer endoEpicardiumSplitPercent,
                                   ROI[][][] rois,
                                   ReadableImage[] inputImages, boolean contiguousTimes,
                                   int nTimePoints,
                                   int nCols, int nRows,
                                   float pixelXSize, float pixelYSize, float pixelZSize,
                                   MonitorWorker worker)
    throws InvalidImageException, InvalidArgumentException, ROIException, CancelledException {

    int nSliceLocations = rois.length;
    int nTimes = rois[0].length;
    // Allocate the segment areas for all slices and time-points.
    segmentAreas = new double[nSliceLocations][nTimes][];
    // Record the  endo- epicardium split percentage, so that the segment areas can be
    // outputted correctly if there is a split.
    this.endoEpicardiumSplitPercent = endoEpicardiumSplitPercent;

    for (int physicalSlice = 0; physicalSlice < nSliceLocations; physicalSlice++) {

      if (verbose) {
        System.out.println("Slice " + Integer.toString(physicalSlice+1));
      }

      // See if there are any ROIs for the first time point at this physical slice location.
      if (rois[physicalSlice][0].length > 0) {

        for (int time = 0; time < nTimes; time++) {

          if (verbose) {
            System.out.println("Time point " + Integer.toString(time+1));
          }

          // Get the radial divider for this physical slice.
          RadialDivider radialDivider = radialDividers[physicalSlice];
          // The number of segments will be the same for all time points in this slice.
          int nSegments = radialDivider.getNSegments();

          // Get the stats for each segment. If there is an endo/epicardial split, then
          // the number of ROIStats returned is twice the number of segments.
          // The first nSegments stats will refer to the endocardium, and the second
          // nSegments stats will refer to the epicardium.
          // In this example, we are only calculating the segment area, so we do not
          // need to supply pixel intensity information.
          ROIStats[] segmentStats = radialDivider.getStats(rois[physicalSlice][time], time,
                                                           (Object) null, (PixelDataType) null,
                                                           nCols, nRows, 0,
                                                           pixelXSize, pixelYSize,
                                                           (ComplexMode) null);

          if (endoEpicardiumSplitPercent == null) {
            // No endo/epicardial split.
            segmentAreas[physicalSlice][time] = new double[nSegments];
            for (int segment = 0; segment < nSegments; segment++) {
              segmentAreas[physicalSlice][time][segment] = segmentStats[segment].area;
              if (verbose) {
                System.out.println("S" + Integer.toString(segment+1) + " area=" +
                                   segmentStats[segment].area);
              }
            }
          }
          else {
            // There is an endo/epicardial split. Double the number of segment areas calculated.
            segmentAreas[physicalSlice][time] = new double[nSegments * 2];
            for (int segment = 0; segment < nSegments; segment++) {
              if (verbose) {
                System.out.println("S" + Integer.toString(segment+1) + "endo area=" +
                                   segmentStats[segment].area);
                System.out.println("S" + Integer.toString(segment+1) + "epi area=" +
                                   segmentStats[segment+nSegments].area);
              }
              segmentAreas[physicalSlice][time][segment] = segmentStats[segment].area;
              segmentAreas[physicalSlice][time][segment+nSegments] =
                segmentStats[segment+nSegments].area;
            }
          }
        }
      }
    }
  }

  @Override public void reportAnalysis(CardiacFrame parentFrame) {

    int nSliceLocations = segmentAreas.length;
    int nTimes = segmentAreas[0].length;

    // Create the output file in the current folder.
    PrintStream areasOutputStream;
    try {
      areasOutputStream =
        new PrintStream(new FileOutputStream(new File(System.getProperty("user.home"),
                                                      "SegmentAreas.txt")));
    }
    catch (IOException ioE) {
      if (parentFrame != null) {
        javax.swing.JOptionPane.showMessageDialog(parentFrame, ioE.getMessage());
      }
      else {
        System.err.println("ERROR: " + ioE.getMessage());
      }
      return;
    }

    for (int physicalSlice = 0; physicalSlice < nSliceLocations; physicalSlice++) {

      areasOutputStream.println("Slice " + Integer.toString(physicalSlice+1));

      if (segmentAreas[physicalSlice][0] != null) {

        // The number of segments will be the same for all time points in this slice.
        int nSegments = segmentAreas[physicalSlice][0].length;
        if (endoEpicardiumSplitPercent != null) {
          nSegments /= 2;
        }

        for (int time = 0; time < nTimes; time++) {
          areasOutputStream.println("Time point " + Integer.toString(time+1));
          if (endoEpicardiumSplitPercent == null) {
            // No endo/epicardial split.
            for (int segment = 0; segment < nSegments; segment++) {
              areasOutputStream.println("S" + Integer.toString(segment+1) + " area=" +
                                        segmentAreas[physicalSlice][time][segment]);
            }
          }
          else {
            // There is an endo/epicardial split. Double the number of segment areas calculated.
            segmentAreas[physicalSlice][time] = new double[nSegments * 2];
            for (int segment = 0; segment < nSegments; segment++) {
              areasOutputStream.println("S" + Integer.toString(segment+1) + "endo area=" +
                                        segmentAreas[physicalSlice][time][segment]);
              areasOutputStream.println("S" + Integer.toString(segment+1) + "epi area=" +
                                        segmentAreas[physicalSlice][time][segment+nSegments]);
            }
          }
        }
      }
    }
    areasOutputStream.close();
  }

  /**
     This panel will appear as one of the selectable cardiac segment tabs.

     @param parentFrame the CardiacFrame in which the tab will appear.
     @param preferencesNodeName the preferences node name to used if this analysis needs
     to save any of its settings.
  */
  @Override public CardiacAnalysis.SpecifierPanel getSpecifierPanel(CardiacFrame parentFrame,
                                                                    String preferencesNodeName) {
    return(new Panel(parentFrame, preferencesNodeName));
  }

  /**
     The CardiacAnalysis.SpecifierPanel used to setup this segmental analysis.
     In this simple case there is nothing to setup, so it just contains a simple label
     to say what the analysis does.
     It could, for example, have a text field to select the output file name.
  */
  static class Panel extends CardiacAnalysis.SpecifierPanel {

    /**
       Creates a new SegmentArea.Panel.

       @param parentFrame the CardiacFrame in which the panel tab will appear.
       @param preferencesNodeName the preferences node name to used if this calculation needs
       to save any of its settings.
    */
    Panel(CardiacFrame parentFrame, String preferencesNodeName) {
      super(parentFrame);
      this.add(new javax.swing.JLabel("Extracts the area of each segment"));
    }

    /**
       Returns the analysis as set up in this panel.

       @erturn the calculation.
    */
    @Override public SegmentArea getAnalysis() {
      return(new SegmentArea());
    }

    /**
       Sets the defaults for this analysis.
       In this case, there are no options that can be changed,
    */
    @Override public void setDefaults() {
    }

    /**
       Saves the settings for this calculation.
       In this case, there are no options that can be saved,

       @param prefs the Preferences where the settings are to be saved.
    */
    @Override public void savePreferences(Preferences prefs) {
    }

    /**
       Shows an error message using the parent frame's showError method.

       @param message the error message to show.
    */
    @Override public void showError(String message) {
      parentFrame.showError(message);
    }
  }
}
