
// ImageUtils.java
// Andrew Davison, April 2023, ad@coe.psu.ac.th

/* Utilities, subdivided into the categories:
      * file IO
      * coordinates
      * filename related
      * delay execution
      * pixels
      * others!

   Usage:
     > compile ImageUtils.java
*/

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;

import java.io.*;
import java.nio.file.*;
import javax.imageio.*;

import java.util.*;


import ij.*;
import ij.process.*; 


public class ImageUtils
{

  public static final String FILES_INFO_FNM = "filenames.txt";


  // --------------- file IO ----------------------


  public static BufferedImage loadImage(String fnm)
  // load the image stored in fnm
  {
    BufferedImage image = null;
    try {
      image = ImageIO.read(new File(fnm));
      System.out.println("Loaded " + fnm);
    }
    catch (IOException e) 
    {  System.out.println("Could not load " + fnm); }
    return image;
  }  // end of loadImage()


  public static void saveImage(BufferedImage im, String fnm)
  // save the image into the PNG file called fnm
  {
    try {
      ImageIO.write(im, "png", new File(fnm));
      System.out.println("Saved image to " + fnm); 
    }
    catch(IOException e)
    {  System.out.println("Could not save image to " + fnm);  }
  }  // end of saveImage()



  public static ImagePlus[] loadImages(String dirNm)
  {
    ArrayList<String> fnms = loadFnms(dirNm);
    if (fnms == null)
      return null;

    int count = 0;
    ImagePlus[] imps = new ImagePlus[fnms.size()];
    for (int i=0; i < imps.length; i++) {
      String fnm = fnms.get(i);
      try {
        imps[i] = new ImagePlus(fnm);
        count++;
      }
      catch(NullPointerException e)
      {  System.out.println("Could not load image from " + fnm); }
    }

    System.out.println("Loaded " + count  + " images");
    return imps;
  } // end of loadImages()



  public static String readFile(String fnm)
  {
    try {
      System.out.println("Reading text from " + fnm);
      return Files.readString(Path.of(fnm));
    }
    catch (IOException e) {
      System.out.println("Could not read " + fnm);
      return null;
    }
  } // end of readFile()



  public static ArrayList<String> loadFnms(String dirNm)
  /* The format of the input lines are:
        one filename per line
     and blank lines and comment lines.
  */
  { 
    String fiFnm = dirNm + FILES_INFO_FNM; 
    File file = new File(fiFnm);
    if (!file.isFile()) {
      System.out.println("No files info filename: " + fiFnm);
      return null;
    }

    ArrayList<String> fnms = new ArrayList<>();
    try {
      System.out.println("Loading info file: " + fiFnm);
      BufferedReader br = new BufferedReader( 
                               new FileReader(fiFnm));
      String line;
      char ch;
      while((line = br.readLine()) != null) {
        line = line.trim();
        if (line.length() == 0)  // blank line
          continue;
        if (line.startsWith("//"))   // comment
          continue;

        String fnm = dirNm + line.trim();
        if (!new File(fnm).isFile())
          System.out.println("No file: " + fnm);
        else {
          // System.out.println("Added " + fnm);
          fnms.add(fnm);
        }
      }
      br.close();
    } 
    catch (IOException e) 
    {  System.out.println("  " + e);  }

    System.out.println("Found " + fnms.size() + " file names");
    return fnms;
  }  // end of loadFnms()



  public static double getDouble(String s)
  { 
    double num = 0;
    try {
      num = Double.parseDouble(s);
    }
    catch (NumberFormatException ex){ 
      System.out.println("Incorrect format for double " + s); 
    }
    return num;
  }  // end of getDouble()



  public static int getInt(String s)
  { 
    int num = 0;
    try {
      num = Integer.parseInt(s);
    }
    catch (NumberFormatException ex){ 
      System.out.println("Incorrect format for int " + s); 
    }
    return num;
  }  // end of getInt()


  // ------------------- coordinates -------------------


  public static ArrayList<Point> loadCoords(String fnm)
  /* The format of the input lines are:
        two integers per line for a (x,y) coordinate
     and blank lines and comment lines.
  */
  { 
    ArrayList<Point> coords = new ArrayList<>();
    try {
      System.out.println("Loading coords file: " + 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;

        coords.add(getCoord(line));
      }
      br.close();
    } 
    catch (IOException e) 
    {  System.out.println("  " + e);  }

    System.out.println("Found " + coords.size() + " file names");
    return coords;
  }  // end of loadCoords()


  private static Point getCoord(String line)
  {
    int x = 0;
    int y = 0;
    String[] valStr = line.split("\\s+");
    if (valStr.length != 2)
      System.out.println("Wrong number of values (" 
                         + valStr.length + ") in " + line);
    else {
      x = getInt(valStr[0]);
      y = getInt(valStr[1]);
      if (x < 0) {
        System.out.println("x-coord " + x + "cannot be negative; using 0");
        x = 0;
      }
      if (y < 0) {
        System.out.println("y-coord " + y + "cannot be negative; using 0");
        y = 0;
      }
    }
    return new Point(x,y);
  }  // end of getCoord()



  public static ImagePlus drawCoords(ArrayList<Point> coords)
  {
    int[] maxs = ImageUtils.findMaxs(coords);
    int xMax = maxs[0]; 
    int yMax = maxs[1];
    // System.out.println("Max values: " + xMax + ", " + yMax);

    ImageProcessor imProc = new ColorProcessor(xMax+10, yMax+10);
    imProc.setRoi(0, 0, xMax+10, yMax+10);
    imProc.setColor(Color.WHITE);
    imProc.fill();

    // draw the curve as lines
    imProc.setColor(Color.BLACK);
    imProc.setLineWidth(6);
    Point fromPt = coords.get(0);
    for (int i=1; i < coords.size(); i++) {
      Point toPt = coords.get(i);
      imProc.drawLine(fromPt.x, fromPt.y, toPt.x, toPt.y);
      fromPt = toPt;
    }

    // add red points
    imProc.setColor(Color.RED);
    imProc.setLineWidth(5);
    for (int i=0; i < coords.size(); i++) {
      Point pt = coords.get(i);
      imProc.drawDot(pt.x, pt.y);
    }

    return new ImagePlus("Draw Coords", imProc);
  }  // end of drawCoords()


  public static int[] findMaxs(ArrayList<Point> coords)
  // return array containing {xMax, yMax}
  {
    int xMax = 0; 
    int yMax = 0;
    for(Point coord : coords) {
      if (coord.x > xMax)
        xMax = coord.x;
      if (coord.y > yMax)
        yMax = coord.y;
    }
    // System.out.println("Max values: " + xMax + ", " + yMax);
    return new int[] { xMax, yMax};
  }  // end of findMaxs()



  // ------------- filename related -------------------


  public static String getName(String fnm)
  // remove path and extension from filename
  {
    Path p = Paths.get(fnm);
    String fileName = p.getFileName().toString();
    return stripExt(fileName);
  }  // end of getName()



  public static String stripExt(String fnm)
  {
    int dotPos = fnm.lastIndexOf('.');
    if (dotPos == -1)
      return fnm;
    else
      return fnm.substring(0, dotPos);
  } // end of stripExt()



  public static String getExt(String fnm)
  {
    int dotPos = fnm.lastIndexOf('.');
    if (dotPos == -1)
      return "txt";
    else
      return fnm.substring(dotPos+1);
  } // end of getExt()


  // ------------- delay execution -------------------


  public static void waitGone(ImagePlus imp)
  {  waitGone(imp.getTitle());  }


  public static void waitGone(String name)
  {
    String nm = name.toLowerCase();
    ArrayList<String> matches = findMatches(nm);
    int numMatches = matches.size();
    if (numMatches == 0)
      return;

    if (numMatches > 1) {
      System.out.println(numMatches + " windows match \"" + nm + "\"");
      System.out.println("  " + matches);
    }

    int num = 0;
    do {
      IJ.wait(500);
      num = findMatches(nm).size();
    } while (num == numMatches);
  }  // end of waitGone()


  public static  ArrayList<String> findMatches(String nm)
  {
    ArrayList<String> matches = getIPTitles(nm);
    ArrayList<String> matches2 = getFrameTitles(nm);
    matches.addAll(matches2);
    return new ArrayList<>(new HashSet<>(matches));  // no duplicates
  }

  public static ArrayList<String> getIPTitles(String name)
  // look for ImagePlus titles using (part of) this name
  {
    ArrayList<String> IPNms = new ArrayList<>();
    String[] titles = WindowManager.getImageTitles();
    for(String t : titles) {
      String title = t.toLowerCase();
      if (title.contains(name))
        IPNms.add(title);
    }
    return IPNms;
  }  // end of getIPTitles()


  public static ArrayList<String> getFrameTitles(String name)
  // look for visible Frame titles using (part of) this name
  {
    ArrayList<String> frameNms = new ArrayList<>();
    Window[] windows = Window.getWindows();
    for (Window w : windows) {
      if (w instanceof Frame) {
        Frame f = (Frame)w;
        if (f.isShowing()) {
          String title = f.getTitle().toLowerCase();
          if (title.contains(name))
            frameNms.add(title);
        }
      }
    }
    return frameNms;
  } // end of getFrameTitles()



  public static void waitEnter() 
  {  waitEnter("Press <enter> to continue...");  }


  public static void waitEnter(String msg) 
  {
    Console c = System.console();
    if (c != null) {
      c.format(msg + "\n");
      c.readLine();
    }  
  }


  // -------------------- pixels ----------------------

  // assume that pixel is an RGB color integer

  public static int getRed(int pixel)
  {  return ((pixel & 0xff0000) >> 16);  }

  public static int getGreen(int pixel)
  {  return ((pixel & 0x00ff00) >> 8);  }

  public static int getBlue(int pixel)
  {  return (pixel & 0x0000ff);  }


  public static int makePixel(int red, int green, int blue)
  {
    return ( ((red & 0xff) << 16) + ((green & 0xff) << 8) +
             (blue & 0xff) );
  }


  public static int[] color2Array(Color c)
  {
    int red = c.getRed();
    int green = c.getGreen();
    int blue = c.getBlue();
    return new int[]{ red, green, blue};
  }


  // --------------- others! -------------------


  public static void reverse(int[] arr) 
  {
    int last = arr.length - 1;
    int middle = arr.length / 2;
    for (int i = 0; i <= middle; i++) {
      int temp = arr[i];
      arr[i] = arr[last - i];
      arr[last - i] = temp;
    }
  }  // end of reverse()

}  // end of ImageUtils class