/* Marey.java -- creates graphical timetables.
   Copyright (C) 2003  Casey Marshall <rsdio@metastatic.org>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

In addition, as a special exception, the copyright holders give
permission to link the code of this program with the Batik library (or
with modified versions of Batik that use the same license as Batik),
and distribute linked combinations including the two. You must obey
the GNU General Public License in all respects for all of the code
used other than Batik. If you modify this file, you may extend this
exception to your version of the file, but you are not obligated to do
so. If you do not wish to do so, delete this exception statement from
your version.  */


package org.metastatic.graphics;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

import java.text.ParseException;
import java.text.SimpleDateFormat;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.StringTokenizer;

import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.dom.GenericDOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DOMImplementation;

public class Marey
{
  public static final String VERSION = "2003-12-27";

  public static void main(String[] argv)
  {
    for (int i = 0; i < argv.length; i++)
      {
        if (argv[i].equals("-h") || argv[i].equals("--help"))
          usage();
        else if (argv[i].equals("-v") || argv[i].equals("--version"))
          version();
      }
    Reader in = null;
    Writer out = null;
    if (argv.length == 0 || argv[0].equals("-"))
      {
        try
          {
            in = new InputStreamReader(System.in, "UTF-8");
          }
        catch (UnsupportedEncodingException uee)
          {
            System.err.println("marey: no such encoding \"UTF-8\". This is a serious problem.");
            System.exit(1);
          }
      }
    else
      {
        try
          {
            in = new FileReader(argv[0]);
          }
        catch (IOException ioe)
          {
            System.err.println("marey: "+argv[0]+": "+ioe.getMessage());
            System.exit(1);
          }
      }
    if (argv.length <= 1 || argv[1].equals("-"))
      {
        try
          {
            out = new OutputStreamWriter(System.out, "UTF-8");
          }
        catch (UnsupportedEncodingException uee)
          {
            System.err.println("marey: no such encoding \"UTF-8\". This is a serious problem.");
            System.exit(1);
          }
      }
    else
      {
        try
          {
            out = new FileWriter(argv[1]);
          }
        catch (IOException ioe)
          {
            System.err.println("marey: "+argv[1]+": "+ioe.getMessage());
            System.exit(1);
          }
      }

    try
      {
        Calendar[][] table = readTable(in);
        in.close();
        writeTable(out, table);
        out.close();
      }
    catch (Exception e)
      {
        e.printStackTrace();
        System.err.println("marey: "+e.getMessage());
        System.exit(1);
      }
  }

  private static Calendar[][] readTable(Reader in)
    throws IOException, ParseException
  {
    LinkedList l = new LinkedList();
    BufferedReader in2 = new BufferedReader(in);
    SimpleDateFormat fmt = new SimpleDateFormat("h:mm a");
    for (String line = in2.readLine(); line != null; line = in2.readLine())
      {
        if (line.indexOf('#') >= 0)
          line = line.substring(0, line.indexOf('#'));
        if (line.trim().length() == 0)
          continue;
        StringTokenizer tok = new StringTokenizer(line, ",");
        Calendar[] c = new Calendar[tok.countTokens()];
        int i = 0;
        while (tok.hasMoreTokens())
          {
            String s = tok.nextToken();
            if (s.length() > 0)
              {
                c[i] = new GregorianCalendar();
                c[i].setTime(fmt.parse(s));
              }
            i++;
          }
        l.add(c);
      }
    return (Calendar[][]) l.toArray(new Calendar[l.size()][]);
  }

  private static void writeTable(Writer out, Calendar[][] table) throws IOException
  {
    DOMImplementation dom = GenericDOMImplementation.getDOMImplementation();
    Document doc = dom.createDocument(null, "svg", null);
    SVGGraphics2D g = new SVGGraphics2D(doc);
    g.setBackground(Color.white);

    int nStations = 0;
    for (int i = 0; i < table.length; i++)
      nStations = Math.max(nStations, table[i].length-1);
    int y1 = 20*nStations;
    g.setSVGCanvasSize(new Dimension(1441, y1+1));
    g.setPaint(Color.lightGray);
    g.setStroke(new BasicStroke(1.0f));
    g.draw(new Rectangle(0, 0, 1440, y1));

    // Draw verticals for the hour.
    for (int i = 1; i < 24; i++)
      {
        int x = i * 60;
        g.drawLine(x, 0, x, y1);
      }

    // Draw horizontals for stations.
    for (int i = 1; i < nStations; i++)
      {
        int yy1 = i * 20;
        g.drawLine(0, yy1, 1440, yy1);
      }

    // Draw thin verticals for the 10-minute intervals.
    g.setStroke(new BasicStroke(0.5f));
    for (int i = 1; i < 144; i++)
      {
        if (i % 6 == 0)
          continue;
        int x = i * 10;
        g.drawLine(x, 0, x, y1);
      }

    g.setStroke(new BasicStroke(1.0f));
    g.setPaint(Color.black);
    for (int i = 0; i < table.length; i++)
      {
        Point p1 = null;
        Point p2 = null;
        for (int j = 0; j < table[i].length; j++)
          {
            if (table[i][j] != null)
              {
                int x = table[i][j].get(Calendar.HOUR_OF_DAY)*60 +
                  table[i][j].get(Calendar.MINUTE);
                int y = j*20;
                p2 = new Point(x, y);
                if (p1 != null)
                  {
                    if (table[i][j-1] == null)
                      g.setPaint(Color.red);
                    else
                      g.setPaint(Color.black);
                    g.draw(new Line2D.Double(p1, p2));
                  }
                p1 = p2;
              }
          }
      }

    g.stream(out);
  }

  private static void usage()
  {
    System.out.println("usage: marey [in-file [out-file]]");
    System.out.println();
    System.out.println("Translate a comma-separated timetable read from <in-file> into a graphical");
    System.out.println("timetable in SVG format, writing to <out-file>. If either file is not given");
    System.out.println("or is `-', then read/write to standard input/output.");
    System.out.println();
    System.out.println("  -h, --help     Prints this help and exits.");
    System.out.println("  -v, --version  Prints a version message and exits.");
    System.exit(0);
  }

  private static void version()
  {
    System.out.println("marey version " + VERSION + ", Copyright (C) 2003  Casey Marshall.");
    System.out.println();
    System.out.println("marey is free software, and you are welcome to copy and distribute it");
    System.out.println("under the terms of the GNU General Public License, version 2 or any");
    System.out.println("any later version. marey comes WITHOUT ANY WARRANTY, without even the");
    System.out.println("implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.");
    System.exit(0);
  }
}
