
// Utils.java
// Andrew Davison, ad@coe.psu.ac.th, March 2023

/* Various utilities for use with the "Simple Java Plot"
   library by yuriy-g
   http://yuriy-g.github.io/simple-java-plot/

   The main features are:
      * simple line, scatter, and bar plots
      * view a plot in a window
      * load Plot data from a file
      * calculate coordinate data ranges
*/


import java.io.*;
import java.util.*;
import java.nio.file.*;
import java.awt.*;
import java.awt.geom.*;

import com.github.plot.Plot;
import com.github.plot.Plot.*;


public class Utils
{
  private static final int SCATTER = 0;
  private static final int LINE = 1;
  private static final int COLUMN = 2;


  // ------------- simple line, scatter, and bar plots ------------


  public static void scatter(Plot.Data plotData)
  {   simplePlot("Scatter Plot", plotData, SCATTER);  }

  public static void scatter(String title, Plot.Data plotData)
  {   simplePlot(title, plotData, SCATTER);  }


  public static void line(Plot.Data plotData)
  {   simplePlot("Line Plot", plotData, LINE);  }

  public static void line(String title, Plot.Data plotData)
  {   simplePlot(title, plotData, LINE);  }


  public static void bar(Plot.Data plotData)
  {   simplePlot("Bar Chart", plotData, COLUMN);  }

  public static void bar(String title, Plot.Data plotData)
  {   simplePlot(title, plotData, COLUMN);  }



  private static void simplePlot(String title, 
                         Plot.Data plotData, int chartType)
  { 
    // configure options
    PlotOptions pOpts = Plot.plotOpts().
                     title(title).
                     width(600).height(400);

    Plot.Line lineType = Plot.Line.NONE;
    if (chartType == LINE)
      lineType = Plot.Line.SOLID;

    Plot.Marker markerType = Plot.Marker.CIRCLE;
    if (chartType == COLUMN)
      markerType = Plot.Marker.COLUMN;

    DataSeriesOptions dsOpts = Plot.seriesOpts().
                       line(lineType).
                       marker(markerType).
                       color(Color.BLACK).
                       markerColor(Color.BLACK);

    // build plot
    Plot plot = Plot.plot(pOpts);
    plot.series(null, plotData, dsOpts);
    viewPlot(title, plot, null);
  } // end of simplePlot()


  // ------------- view a plot in a window ---------------------------


  public static void viewPlot(Plot plot)
  {  viewPlot("Plot", plot, null);  }

  public static void viewPlot(String title, Plot plot)
  {  viewPlot(title, plot, null);  }


  public static void viewPlot(Plot plot, String fnm)
  {  viewPlot("Plot", plot, fnm);  }


  public static void viewPlot(String title, 
                             Plot plot, String fnm)
  /* Use Plot.save() to save a plot image to a temporary
     file, and then load it into ImageViewer
  */
  {
    if (fnm == null)
      fnm = makeTemp(".png");
    else
      System.out.println("Saving plot to " + fnm);
    //System.out.println("fnm: " + fnm);

    try {
      String nm = getPathName(fnm);
      String ext = getExtension(fnm);
      //System.out.println("name: " + nm + "; ext: " + ext);
      plot.save(nm, ext);
      ImageViewer.view(title, fnm);
    }
    catch (IOException e)
    {  System.out.println("Unable to generate plot: \"" +
                                   title + "\"");
    }
  }  // end of viewPlot()



  public static String makeTemp(String ext)
  // create a temporary file with the given extension,
  // and return the file name
  {
	try {
      File f = File.createTempFile("temp", ext);
      f.deleteOnExit();   // Delete when JVM exits
      return f.getAbsolutePath();
	}
	catch (IOException e) 
    {  System.out.println("Could not create temp file with extension " + ext);  }
    return null;
  }  // end of makeTemp()



  public static String getPathName(String pathNm)
  // get filename and its path without the extension
  {
    try {
      File f = new File(pathNm);
      String fnm = f.getAbsolutePath();
      int lastIndex = fnm.lastIndexOf('.');
      if (lastIndex != -1)
        fnm = fnm.substring(0, lastIndex);
      return fnm;
    } 
    catch (Exception e) {
      System.out.println("Could not extract filename: " + e);
      return null;
    }
  }  // end of getPathName()


  public static String getName(String pathNm)
  // get filename of path without the extension
  {
    try {
      File f = new File(pathNm);
      String fnm = f.getName();
      int lastIndex = fnm.lastIndexOf('.');
      if (lastIndex != -1)
        fnm = fnm.substring(0, lastIndex);
      return fnm;
    } 
    catch (Exception e) {
      System.out.println("Could not extract filename: " + e);
      return null;
    }
  }  // end of getName()


  public static String getExtension(String pathNm)
  // get extension of a filename in lowercase
  {
    try {
      File f = new File(pathNm);
      String fnm = f.getName();
      int lastIndex = fnm.lastIndexOf('.');
      if (lastIndex != -1)
        return fnm.substring(lastIndex+1).toLowerCase();
    } 
    catch (Exception e) {
      System.out.println("Could not extract filename extension: " + e);
    }
    return null;
  }  // end of getExtension()



  // -------------- load Plot data from a file -------------------


  public static Plot.Data loadData(String fnm)
  {  return getCoordsData(loadCoords(fnm)); }  



  public static Plot.Data getCoordsData(
                      ArrayList<Point2D.Double> coords)
  /* Convert a list of coordinates into Plot.Data
     by adding them as two double arrays.
  */
  {
    if (coords == null) {
      System.out.println("No coordinates data");
      return null;
    }

    double[] xs = new double[coords.size()];
    double[] ys = new double[coords.size()];
    for (int i=0;  i < coords.size(); i++) {
      Point2D.Double coord = coords.get(i);
      xs[i] = coord.x;
      ys[i] = coord.y;
    } 

    return Plot.data().xy(xs, ys);
  }  // end of loadData()



  public static ArrayList<Point2D.Double> loadCoords(String fnm)
  /* The format is two double per line separated by spaces,
     which are treated as a single coordinate (a Point2D.Double).
     Blank lines and comments (lines starting with //) are also
     accepted (and ignored).

     The coordinates are returned in a list.
  */
  { 
    File file = new File(fnm);
    if (!file.isFile()) {
      System.out.println(fnm + " is not a file");
      return null;
    }

    ArrayList<Point2D.Double> coords = new ArrayList<>();
    try {
      System.out.println("Loading " + fnm);
      BufferedReader br = new BufferedReader( new FileReader(fnm));
      String line;
      char ch;
      while((line = br.readLine()) != null) {
        line = line.trim();
        if (line.length() == 0)  // blank line
          continue;
        if (line.startsWith("//"))   // comment
          continue;
        else {
          String[] vals = line.split("\\s+");
          if (vals.length != 2)
            System.out.println("Incorrect number of arguments");
          else   // two doubles become a single coordinate
            coords.add( new Point2D.Double(
                          Double.parseDouble(vals[0]), 
                          Double.parseDouble(vals[1]) ) );
        }
      }
      br.close();
      return coords;
    } 
    catch (IOException e) 
    { System.out.println("Unable to load " + fnm);  }

    return null;
  }  // end of loadCoords()



  // ------------------ coordinate data ranges ----------------


  public static double[] getRanges(
                      ArrayList<Point2D.Double> coords)
  /* Find the ranges of the x- and y- data supplied as 
     a list of coordinates. The results are returned
     in an array with the format: 
          minX, maxX, minY, maxY
  */
  {
    if (coords == null) {
      System.out.println("No coordinatees data");
      return null;
    }

    Point2D.Double coord = coords.get(0);
    double minX = (coord.x < 0) ? coord.x : 0;
    double maxX = coord.x;
    double minY = (coord.y < 0) ? coord.y : 0;
    double maxY = coord.y;
 
    for (int i=1;  i < coords.size(); i++) {
      coord = coords.get(i);
      if (minX > coord.x)
        minX = coord.x;
      if (maxX < coord.x)
        maxX = coord.x;
      if (minY > coord.y)
        minY = coord.y;
      if (maxY < coord.y)
        maxY = coord.y;
    } 

    return new double[] {minX, maxX, minY, maxY};
  }  // end of getRanges()


}  // end of Utils class