/*
 *   Copyright (c) 2005, Richard Koolish 
 *   All Rights Reserved.
 *
 */


/*********************************************************************
*                                                                    * 
*  ScatterPlot - Create a JPG image of a scatter plot                * 
*                                                                    * 
*********************************************************************/  

import java.util.*;
import java.io.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.font.*;


public class ScatterPlot  {

    private static boolean verbose = false;
    private static String source = null;
    private static String dest = null;
    private static String indir = null;
    private static String outdir = null;
    private static String usage = "usage: java ScatterPlot -h <height> -w <width> -s <source> -d <dest> -indir <dir> -outdir <dir>";
    private static int canvas_w = 1024;
    private static int canvas_h = 768;

    private Hashtable colors = new Hashtable ();
    private Vector lines = new Vector ();
    private Vector points = new Vector ();
    private double x_min = Double.MAX_VALUE;
    private double x_max = Double.MIN_VALUE;
    private double y_min = Double.MAX_VALUE;
    private double y_max = Double.MIN_VALUE;
    private String plot_title = "";
    private String sub_title = "";
    private String x_axis_title = "";
    private String y_axis_title = "";
    private int graph_margin_top = 100;
    private int graph_margin_bot = 100;
    private int graph_margin_left = 100;
    private int graph_margin_right = 100;
    private int graph_org_x = graph_margin_left;
    private int graph_org_y = graph_margin_top;
    private int graph_w = 0;
    private int graph_h = 0;
    private BufferedImageCanvas canvas = null;

    public ScatterPlot () {

        canvas = new BufferedImageCanvas (canvas_w, canvas_h);

        graph_w = canvas_w - graph_margin_left - graph_margin_right;
        graph_h = canvas_h -graph_margin_top - graph_margin_bot;

        colors = new Hashtable ();
        colors.put ("black", Color.black);
        colors.put ("red", Color.red);
        colors.put ("blue", Color.blue);
        colors.put ("green", Color.green);
    }


    private void process_file () {

        String line;
        String new_line;
        Color c = Color.black;
        int x_org = 100;
        int y_org = 100;
        int x_width = canvas_w - 200;
        int y_height = canvas_h - 200;


        // read the file into a vector

        String filename = indir + "/" + source;
        Vector file = read_file (filename);
        if (file == null || file.size() == 0)
            return;

        // read file, compute min, max, save points, get titles

        for (int line_cnt = 0; line_cnt < file.size(); line_cnt++) {
            line = (String)file.elementAt (line_cnt);
            Vector tokens = tokenize (line, "\t");
            if (tokens.size() < 1)
                continue;
            String keyword = (String)tokens.elementAt (0);
            if (keyword.equalsIgnoreCase ("title")) {
                plot_title = (String)tokens.elementAt (1);
            } else if (keyword.equalsIgnoreCase ("subtitle")) {
                sub_title = (String)tokens.elementAt (1);
            } else if (keyword.equalsIgnoreCase ("xtitle")) {
                x_axis_title = (String)tokens.elementAt (1);
            } else if (keyword.equalsIgnoreCase ("ytitle")) {
                y_axis_title = (String)tokens.elementAt (1);
            } else if (keyword.equalsIgnoreCase ("data")) {
                double x1 = Double.parseDouble ((String)tokens.elementAt (1));
                double y1 = Double.parseDouble ((String)tokens.elementAt (2));
		System.out.println("x1: " + x1 + ", y1: " + y1);
                Point2D.Double p = new Point2D.Double (x1, y1);
                points.add (p);
                if (x1 < x_min)
                    x_min = x1;
                if (x1 > x_max)
                    x_max = x1;
                if (y1 < y_min)
                    y_min = y1;
                if (y1 > y_max)
                    y_max = y1;
            }
        }

        // compute grid

        // ---------- new test stuff ----------
        compute_grid (x_min, x_max, y_min, y_max);
        canvas.write (outdir + "/" + dest);
        if (true)
            return;
        // ---------- end of new test stuff ----------

        // original grid computation

        double dx = x_max - x_min;
        double dy = y_max - y_min;

        int i = 0;
        double n = 5.0;

        while (true) {
            i++;
            if ((dx / (i * n)) <= 12.0)
                break;
        }

        double x_incr = i * n;

        i = 0;
        while (true) {
            i++;
            if ((dy / (i * n)) <= 12.0)
                break;
        }

        double y_incr = i * n;

        x_min = Math.floor (x_min - x_incr);
        x_max = Math.ceil (x_max + x_incr);
        y_min = Math.floor (y_min - y_incr);
        y_max = Math.ceil (y_max + y_incr);

        // draw the bounding box and the labels

        canvas.drawLine (x_org, y_org, x_org + x_width, y_org, 1, c);
        canvas.drawLine (x_org, y_org, x_org, y_org + y_height, 1, c);
        canvas.drawLine (x_org + x_width, y_org + y_height, x_org + x_width, y_org, 1, c);
        canvas.drawLine (x_org + x_width, y_org + y_height, x_org, y_org + y_height, 1, c);

        canvas.setFontStyle (Font.BOLD);
        int sl = canvas.stringLength (plot_title);
        canvas.drawString (plot_title, x_org + (x_width / 2) - (sl / 2), y_org - 50, c);
        sl = canvas.stringLength (sub_title);
        canvas.drawString (sub_title, x_org + (x_width / 2) - (sl / 2), y_org - 30, c);
        sl = canvas.stringLength (x_axis_title);
        canvas.drawString (x_axis_title, x_org + (x_width / 2) - (sl / 2), y_org + y_height + 50, c);
        sl = canvas.stringLength (y_axis_title);
        canvas.drawVertString (y_axis_title, 50, y_org + (y_height / 2) + (sl / 2), c);
        canvas.restoreFont ();

        // draw the grid

        double x_scale = (canvas_w - 200) / (x_max - x_min);
        double y_scale = (canvas_h - 200) / (y_max - y_min);

        int x1 = 0;
        int y1 = 0;
        int x2 = 0;
        int y2 = 0;
        int lw = 0;

        for (double xx = x_min; xx <= x_max; xx += x_incr) {
            int xx1 = (int)(((xx - x_min) * x_scale) + x_org);
            canvas.drawLine (xx1, y_org, xx1, y_org + y_height, 1, c);
            String s = "" + xx;
            int len = canvas.stringLength (s);
            canvas.drawString (s, xx1 - len / 2, y_org + y_height + 15, c);
        }

        for (double yy = y_min; yy <= y_max; yy += y_incr) {
            int yy1 = (int)(((yy - y_min) * y_scale) + y_org);
            canvas.drawLine (x_org, yy1, x_org + x_width, yy1, 1, c);
            String s = "" + yy;
            int len = canvas.stringLength (s);
            canvas.drawString ("" + yy, x_org - len - 5, yy1 + 3, c);
        }

        // plot the points

        for (int pi = 0; pi < points.size(); pi++) {
            Point2D.Double p = (Point2D.Double)points.elementAt (pi);
            double x = p.getX ();
            double y = p.getY ();
            x1 = (int)(((x - x_min) * x_scale) + x_org);
            y1 = (int)(((y_max - y) * y_scale) + x_org);
            canvas.drawRect (x1, y1, 4, 4, 1, c);
        }

        // write the image

        canvas.write (outdir + "/" + dest);
    }


    // compute_grid

    private void compute_grid (double xmin, double xmax, double ymin, double ymax) {

        if (points.size() == 1) {
            System.out.println ("Only one data point");
            xmin = xmin - 10.0;
            xmax = xmax + 10.0;
            ymin = ymin - 10.0;
            ymax = ymax + 10.0;
        }

        if ((xmax - xmin) < 1.0) {
            System.out.println ("Separating xmin, xmax");
            xmax = xmax + 1.0;
            xmin = xmin - 1.0;
        }
        if ((ymax - ymin) < 1.0) {
            System.out.println ("Separating ymin, ymax");
            ymax = ymax + 1.0;
            ymin = ymin - 1.0;
        }

        System.out.println ("data min/max: " + xmin + " " + xmax 
			    + ", " + ymin + " " + ymax);

        // minimum grid square is of size 5.0

        double min_box = 5.0;

        // what size grid square to use in multiples of minimum size

        int x_box = box_size (xmin, xmax, min_box);
        int y_box = box_size (ymin, ymax, min_box);
        int box_n = Math.max (x_box, y_box);		// use largest box in both directions
        if (box_n < 1)		// just make sure it's not 0
            box_n = 1;

        // now get the min, max grid lines in both dimensions

        double box = box_n * min_box;
        double x_min_g = next_incr (xmin, box);
        double x_max_g = next_incr (xmax, box);
        double y_min_g = next_incr (ymin, box);
        double y_max_g = next_incr (ymax, box);

        System.out.println ("grid min/max: " + x_min_g + " " + x_max_g 
			    + ", " + y_min_g + " " + y_max_g);

        // get the number of grid squares in each dimension

        double x_n_boxes = (x_max_g - x_min_g) / box;
        double y_n_boxes = (y_max_g - y_min_g) / box;

        // scale the grid so one dimension at least is maximum possible

        double x_box_pixels = graph_w / x_n_boxes;
        double y_box_pixels = graph_h / y_n_boxes;
        double pixels_per_box = Math.floor (Math.min (x_box_pixels, y_box_pixels));
        double pixels_per_v = pixels_per_box / box;

        System.out.println ("pixels_per_box: " + pixels_per_box);

        // draw the grid
 
        Color c = Color.black;

        int g_width = (int)(x_n_boxes * pixels_per_box);
        int g_height = (int)(y_n_boxes * pixels_per_box);

        for (double xx = x_min_g; xx <= x_max_g; xx += box) {
            int xx1 = (int)(((xx - x_min_g) * pixels_per_v) + graph_org_x);
            canvas.drawLine (xx1, graph_org_y, xx1, (int)(graph_org_y + g_height), 1, c);
            String s = "" + xx;
            int len = canvas.stringLength (s);
            canvas.drawString (s, xx1 - len / 2, (int)(graph_org_y + g_height + 15), c);
        }

        for (double yy = y_min_g; yy <= y_max_g; yy += box) {
            int yy1 = (int)(((yy - y_min_g) * pixels_per_v) + graph_org_y);
            canvas.drawLine (graph_org_x, yy1, (int)(graph_org_x + g_width), yy1, 1, c);
            String s = "" + yy;
            int len = canvas.stringLength (s);
            canvas.drawString ("" + yy, graph_org_x - len - 5, yy1 + 3, c);
        }

        // plot the points

        double x_scale = g_width / (x_max_g - x_min_g);
        double y_scale = g_height / (y_max_g - y_min_g);

        for (int pi = 0; pi < points.size(); pi++) {
            Point2D.Double p = (Point2D.Double)points.elementAt (pi);
            double x = p.getX ();
            double y = p.getY ();
            int x1 = (int)(((x - x_min_g) * x_scale) + graph_org_x);
            int y1 = (int)(((y_max_g - y) * y_scale) + graph_org_y);
            canvas.drawRect (x1, y1, 4, 4, 1, c);
        }

        // draw titles and labels

        canvas.setFontStyle (Font.BOLD);
        int sl = canvas.stringLength (plot_title);
        canvas.drawString (plot_title, graph_org_x + (g_width / 2) - (sl / 2), graph_org_y - 50, c);
        sl = canvas.stringLength (sub_title);
        canvas.drawString (sub_title, graph_org_x + (g_width / 2) - (sl / 2), graph_org_y - 30, c);
        sl = canvas.stringLength (x_axis_title);
        canvas.drawString (x_axis_title, graph_org_x + (g_width / 2) - (sl / 2), graph_org_y + g_height + 50, c);
        sl = canvas.stringLength (y_axis_title);
        canvas.drawVertString (y_axis_title, 50, graph_org_y + (g_height / 2) + (sl / 2), c);
        canvas.restoreFont ();
    }


    // box_size - return the smallest appropriate box multiple

    private int box_size (double vmin, double vmax, double min_box) {
        int i = 0;
        while (true) {
            i++;
            double box = i * min_box;
            double min_limit = next_incr (vmin, box);
            double max_limit = next_incr (vmax, box);
            double n_boxes = (max_limit - min_limit) / box;
            if (n_boxes <= 10.0)
                return i;
        }
    }


    // next_incr - return the next multiple of incr above/below value

    private double next_incr (double value, double incr) {

        boolean neg = false;

        if (value < 0.0) {
            neg = true;
            value = -value;
        }

        double n = Math.ceil (value / incr);

        if (neg)
            return -(n * incr);
        else
            return n * incr;
    }


    // tokenize - tokenise a String into a Vector

    private static Vector tokenize (String s, String delim) {

        StringTokenizer st;
        if (delim == null)
            st = new StringTokenizer (s);
        else
            st = new StringTokenizer (s, delim);

        Vector tokens = new Vector ();
        while (st.hasMoreTokens ())        
            tokens.add (st.nextToken ());

        return tokens;
    }


    // read_file - read a file into a Vector

    private Vector read_file (String filename) {

        Vector file = new Vector ();
        BufferedReader in;
        String line;

        // open the file

        System.out.println ("ScatterPlot processing '" + filename + "'");

        try {
            in = new BufferedReader (new FileReader (filename));
        } catch (Exception e) {
            System.out.println ("ScatterPlot can't open input file '" + filename + "'");
            e.printStackTrace ();
            return null;
        }

        // read the file into a vector, throw out blank lines and comments

       while (true) {
            try {
                line = in.readLine ();
                if (line == null)
                    break;
                if (line.trim().length() == 0)
                    continue;
                if (line.startsWith ("#"))
                    continue;
                file.add (line);
            } catch (Exception e) {
                e.printStackTrace ();
                break;
            }
        }

        try {
            in.close ();
        } catch (Exception e) {
        }

        return file;
    }


    // list_files

    public void list_files (String dir) {

        try {
            File f = new File (dir);
            if ( ! f.isDirectory ()) {
                System.out.println (dir + " is not a directory");
                return;
            }

            String[] files = f.list ();
            System.out.println (dir + " contains: ");
            for (int i = 0; i < files.length; i++)
                System.out.println ("    " + files[i]);
        } catch (Exception e) {
            e.printStackTrace ();
        }
    }


    // parseArgs

    private static void parseArgs(String[] args) {

        for (int i = 0; i < args.length; i++) {
	      String arg = args[i];

	      if (arg.equals("-v")) {
		    verbose = true;
            } else if (arg.equals ("-s")) {
                i++;
                source = args[i];
            } else if (arg.equals ("-d")) {
                i++;
                dest = args[i];
            } else if (arg.equals ("-indir")) {
                i++;
                indir = args[i];
            } else if (arg.equals ("-outdir")) {
                i++;
                outdir = args[i];
            } else if (arg.equals ("-w")) {
                i++;
                canvas_w = Integer.parseInt(args[i]);
            } else if (arg.equals ("-h")) {
                i++;
                canvas_h = Integer.parseInt(args[i]);
            } else {
                System.out.println ("Unknown arg: " + arg);
            }
	  } 

        if (source == null) {
            System.err.println ("No source file specified");
            System.err.println (usage);
            System.exit (-1);
        }

        if (dest == null) {
            System.err.println ("No destination file specified");
            System.err.println (usage);
            System.exit (-1);
        }

        if (indir == null) {
            System.err.println ("No input directory specified");
            System.err.println (usage);
            System.exit (-1);
        }

        if (outdir == null) {
            System.err.println ("No output directory specified");
            System.err.println (usage);
            System.exit (-1);
        }
    }


    // main

    public static void main (String[] args) { 

        parseArgs (args);

        ScatterPlot sp = new ScatterPlot ();
        sp.process_file ();

        System.exit (0);
    }

    


}
   
