/***************************************************************************
 GanttXFIGSaver.java
 -------------------
 begin                : 15 juin 2004
 copyright            : (C) 2004 by Thomas Alexandre
 email                : alexthomas(at)ganttproject.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.                                   *
 *                                                                         *
 ***************************************************************************/

package net.sourceforge.ganttproject.io;

import java.awt.Color;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import javax.swing.tree.DefaultMutableTreeNode;

import net.sourceforge.ganttproject.GanttCalendar;
import net.sourceforge.ganttproject.GanttTask;
import net.sourceforge.ganttproject.IGanttProject;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskManager;

/**
 * @author athomas This class is based on the octave program of A S Hodel
 *         (hodelas(at)ieee.org) from Auburn University check the URL at
 *         http://www.eng.auburn.edu/users/hodelas/LaTeX/ganttProject_mn.html It
 *         allow to export a project into an xfig scalable vector image.
 */
public class GanttXFIGSaver {

    // fields
    // private GanttTree tree;
    // private GanttGraphicArea area;
    // private PrjInfos prjInfos;

    List lot = new ArrayList(); // list of tasks

    // ArrayList lots = new ArrayList();
    ArrayList loc = new ArrayList(); // list of colors

    ArrayList atl = new ArrayList(); // list of text object

    ArrayList abl = new ArrayList(); // list of box object

    // store the start of the project
    GanttCalendar dateShift = new GanttCalendar();

    float scale = 0, chwidth = 0;

    // width of the text
    float fTtextwidth = 0.0f;

    // debug value to print some text on standrad output..
    boolean debug = true;

    private TaskManager myTaskManager;

    /** Constructor. */
    public GanttXFIGSaver(IGanttProject project) {
        // this.area = area;
        // this.prjInfos = prjInfos;
        myTaskManager = project.getTaskManager();
    }

    /** Save the project as XFIG on a stream */
    public void save(OutputStream stream) {
        try {
            OutputStreamWriter fout = new OutputStreamWriter(stream);
            beginToSave(fout);
            fout.close(); // close the stream
        } catch (IOException e) {
            System.out.println(e);
            System.out.println("Error in saving the xfig file");
        }
    }

    /** Start saving the xfig image. */
    public void beginToSave(OutputStreamWriter fout) throws IOException {
        if (debug)
            System.out.println("beginToSave begin");

        float depthval = 50; // depth level - 999 is the lowest level.

        // targetWidthPoints is in pixels, 1200 pixels per inch,
        // or about 472 pixels/cm
        float targetWidthPoints = 1200;

        // get the list of tasks on an array list
        // lot = tree.getAllTasks();
        lot = Arrays.asList(myTaskManager.getTasks());
        fTtextwidth = getProjectTextWidth();
        if (debug)
            System.out.println("Text Size : " + fTtextwidth);

        // write the xfig header
        xfigheader(fout);

        // Search for the user colors
        searchUserColor();

        // Need to give user control of date range created in plot
        setProjectPlotTimes(targetWidthPoints, 50);

        // Write the list of tasks
        drawTasks(fout);

        // Write the axes
        labelAxes(fout);

        if (debug)
            System.out.println("beginToSave end");
    }

    /**
     * @return the with of the text. Determine width of text labels (task names)
     *         in this project assume fixed width, 14 letters to the inch, 1200
     * ppi each level is indented 1/4 inch (300 points @ 1200ppi)
     */
    public float getProjectTextWidth() {
        if (debug)
            System.out.println("getProjectTextWidth begin");

        float res = 0.0f;
        for (Iterator it = lot.iterator(); it.hasNext();) {
            // get the name of the task
            Task task = (Task) it.next();
            float taskTextWidth = getTaskTextWidth(task);
            if (taskTextWidth > res) {
                res = taskTextWidth;
            }
        }
        if (debug)
            System.out.println("getProjectTextWidth end");
        return res;
    }

    /**
     * @return the textwith of this task for and its children tasks. Determine
     *         width of text labels (task names) in this task assume fixed
     *         width, 14 letters to the inch, 1200 ppi each level is indented
     * 1/4 inch (300 points @ 1200ppi)
     */
    private float getTaskTextWidth(Task task) {
        if (debug)
            System.out.println("getTaskTextWidth begin");
        Task t = task;
        float res = (float) t.getName().length() * (1.0f / 14.0f) * (1200.0f);

        Task[] children = task.getManager().getTaskHierarchy().getNestedTasks(
                task);

        for (int i = 0; i < children.length; i++) {
            float subTaskTextWidth = 0.25f + getTaskTextWidth((children[i]));
            if (subTaskTextWidth > res)
                res = subTaskTextWidth;
        }
        if (debug)
            System.out.println("getTaskTextWidth end");
        return res;
    }

    /**
     * Write the header of the XFIG FILE Based on the xfig file specification.
     */
    public void xfigheader(OutputStreamWriter fout) throws IOException {
        if (debug)
            System.out.println("xfigheader begin");
        fout.write("#FIG 3.2\n"); // version
        fout.write("Landscape\n"); // orientation
        fout.write("Center\n"); // justification
        fout.write("Inches\n"); // units
        fout.write("Letter\n"); // papersize
        fout.write("100.0\n"); // magnification
        fout.write("Single\n"); // multiplePage
        fout.write("-2\n"); // transparentColor
        fout.write("1200 2\n"); // resolutionPpi origin
        if (debug)
            System.out.println("xfigheader end");
    }

    /** Search for the corresponding colors. */
    public void searchUserColor() // TODO continue to write this method
    {
        if (debug)
            System.out.println("searchUserColor begin");
        loc.clear(); // clear the list

        for (Iterator it = lot.iterator(); it.hasNext();) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) it.next();
            if (!node.isRoot()) {
                GanttTask task = (GanttTask) (node.getUserObject());
                Color color = task.getColor();
                String hexaColor = getHexaColor(color);
            }
        }
        if (debug)
            System.out.println("searchUserColor end");
    }

    /**
     * get project time range, compute box scaling values and shifts so that the
     * total chart fits in targetWidthPoints units: targetWidthPoints is in
     * pixels, 1200 pixels per inch, or about 472 pixels/cm this also adds box
     * and text objects to the project tasks for plotting later
     */
    public void setProjectPlotTimes(float targetWidthPoints, float depthval) {
        if (debug)
            System.out.println("setProjectPlotTimes begin");
        float targetwidth = targetWidthPoints - fTtextwidth;

        GanttCalendar startDate = null, endDate = null;

        // Get project start, end times
        for (Iterator it = lot.iterator(); it.hasNext();) {
            // get the task
            Task task = (Task) (((DefaultMutableTreeNode) it.next())
                    .getUserObject());
            if (startDate == null && endDate == null) {
                startDate = task.getStart();
                endDate = task.getEnd();
            } else {
                if (task.getStart().compareTo(startDate) == -1) // before
                    startDate = task.getStart();
                if (task.getEnd().compareTo(startDate) == 1) // after
                    endDate = task.getEnd();
            }
        }
        // shift all dates by this amount to plot
        dateShift = startDate;

        scale = targetwidth / Math.max(1.0f, (float) (endDate.diff(startDate)));
        chwidth = scale * (float) (endDate.diff(startDate));

        System.out.println("Chart width =" + chwidth + " points = "
                + (chwidth / 1200) + " inches\n");

        int index = 0;

        // now add text and box objects to the tasks
        for (Iterator it = lot.iterator(); it.hasNext();) {
            DefaultMutableTreeNode node = ((DefaultMutableTreeNode) it.next());
            if (!node.isRoot()) {
                // get the task
                GanttTask task = (GanttTask) node.getUserObject();

                // get the text infos of the task
                TextObject textLabel = task2text(task, index, node.getLevel(),
                        (int) depthval);
                if (debug)
                    System.out.println("    add TEXT");
                atl.add(textLabel);

                // get the box object of the task
                BoxObject boxObject = task2box(task, (int) depthval, index,
                        node.isLeaf());
                if (debug)
                    System.out.println("    add BOX");
                abl.add(boxObject);

                index++;
            }
        }
        if (debug)
            System.out.println("setProjectPlotTimes end + index=" + index);
    }

    /** convert task structureto an xfig text data structure */
    public TextObject task2text(GanttTask task, int number, int level,
            int depthval) {
        if (debug)
            System.out.println("task2text begin");

        /*
         * System.out.println("task : "+task+ " number : "+number+ " level :
         * "+level+ " depthval : "+depthval);
         */

        TextObject taskText = new TextObject();
        taskText.sub_type = 0; // left justified
        taskText.color = 0; // black
        taskText.depth = depthval;
        taskText.pen_style = 0; // unused
        taskText.font = 0; // Times Roman
        taskText.font_size = 10; // fonts size in points
        taskText.angle = 0; // text angle in radians
        taskText.font_flags = 4; // not rigid, not special, postscript, not
        // hidden.
        taskText.height = 0.25f * 1200f;
        taskText.length = 0.125f * (float) (task.getName().length()) * 1200.0f;
        taskText.y = (int) (1200f * 0.25f * (float) number + 5.0f - 1200.0f / 16.0f);
        taskText.x = (int) (1200.0f * 0.25f * ((float) level + 1.0f));
        taskText.str = task.getName();
        if (debug)
            System.out.println("task2text end");
        return taskText;
    }

    /** get the task box. */
    public BoxObject task2box(GanttTask task, int depthval, int number,
            boolean isLeaf) {
        // boxObject: total box
        // boxObjectPct: black line in center of box object
        // min box width: 1.

        // object to return
        BoxObject boxObject = new BoxObject();

        if (isLeaf) {
            boxObject.sub_type = 2; // box
            boxObject.npoints = 5;
        } else {
            boxObject.sub_type = 3; // polygon
            boxObject.npoints = 7;
        }

        boxObject.depth = depthval;

        if (!isLeaf)
            boxObject.depth -= 2; // ?????
        // See BoxObject class for other default values

        // Construct box corners
        float boxLen = Math.max(1.0f, (task.getLength()) * scale);
        float boxLenPct = Math.max(1.0f, boxLen
                * task.getCompletionPercentage() / 100.0f);
        float boxHeight = 1200.0f / 8.0f;
        float pLo = boxHeight * 0.25f;
        float pHi = boxHeight * 0.75f;
        float[] pointsPct = { 0, pLo, boxLenPct, pLo, boxLenPct, pHi, 0, pHi,
                0, pLo };

        if (boxObject.sub_type == 2) {
            boxObject.points = new float[10];
            boxObject.points[0] = 0;
            boxObject.points[1] = 0;
            boxObject.points[2] = boxLen;
            boxObject.points[3] = 0;
            boxObject.points[4] = boxLen;
            boxObject.points[5] = boxHeight;
            boxObject.points[6] = 0;
            boxObject.points[7] = boxHeight;
            boxObject.points[8] = 0;
            boxObject.points[9] = 0;
        } else {
            float boxLo = boxHeight / 4;
            boxObject.points = new float[14];
            boxObject.points[0] = 0;
            boxObject.points[1] = boxHeight;
            boxObject.points[2] = 0;
            boxObject.points[3] = 0;
            boxObject.points[4] = boxLen;
            boxObject.points[5] = 0;
            boxObject.points[6] = boxLen;
            boxObject.points[7] = boxHeight; // top bow
            boxObject.points[8] = boxLen;
            boxObject.points[9] = boxLo;
            boxObject.points[10] = 0;
            boxObject.points[11] = boxLo;
            boxObject.points[10] = 0;
            boxObject.points[13] = boxHeight;
        }
        int blen = boxObject.points.length;

        // shift to this row
        float xShift = 0;
        float yShift = 1200.0f * 0.25f * ((float) number + 4) + 1200.0f / 16.0f;

        /*
         * boxObject.points(2:2:blen) += yShift; boxObject.points(1:2:blen) +=
         * xShift;
         */
        for (int i = 1; i < blen; i += 2)
            boxObject.points[i] += yShift;
        for (int i = 0; i < blen; i += 2)
            boxObject.points[i] += xShift;
        // pointsPct(2:2:10) += yShift;
        // pointsPct(1:2:9) += xShift;

        // boxObjectPct = boxObject; // percent completed
        // boxObjectPct.points = pointsPct;
        // boxObjectPct.npoints = 5;
        // boxObjectPct.area_fill = 20;
        // boxObjectPct.fill_color = 0;

        // shift boxes by start date
        /* boxObject.points(1:2:blen) += task.start*boxScale; */
        for (int i = 0; i < blen; i += 2)
            boxObject.points[i] += task.getStart().diff(dateShift) * scale;
        // boxObjectPct.points(1:2:9) += task.start*boxScale;

        // now shift again by text width (must be calculated and set in
        // task.boxindent)
        /* boxObject.points(1:2:blen) += task.boxindent; */
        for (int i = 0; i < blen; i += 2)
            boxObject.points[i] += fTtextwidth + 120;
        // boxObjectPct.points(1:2:9) += task.boxindent;
        // boxObjectPct.depth--;

        // fix angle brackets in polygon object
        /*
         * if(boxObject.sub_type == 3) { angwid = min(60,boxLen/2);
         * boxObject.points([9,11]) += [-angwid,angwid]; }
         */
        if (boxObject.sub_type == 3) {
            float angwid = Math.min(60, boxLen / 2);
            boxObject.points[9] += -angwid;
            boxObject.points[11] += angwid;
        }

        return boxObject;
    }

    /** Draw the list of tasks. */
    public void drawTasks(OutputStreamWriter fout) throws IOException {
        try {
            if (debug)
                System.out.println("drawTasks begin");

            // loop on tasks
            int i = 0;
            for (Iterator it = lot.iterator(); it.hasNext();) {
                DefaultMutableTreeNode node = ((DefaultMutableTreeNode) it
                        .next());
                // get the task
                if (!node.isRoot()) {
                    GanttTask task = (GanttTask) (node.getUserObject());

                    TextObject txtObj = (TextObject) (atl.get(i));
                    BoxObject boxObject = (BoxObject) (abl.get(i));

                    // print the text of the task
                    drawtext(fout, txtObj);

                    // print the box of the task
                    drawbox(fout, boxObject);

                    // print the percent complete

                    // print relations

                    // update index
                    i++;
                }
            }
            if (debug)
                System.out.println("drawTasks end");
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    /** Draw text for the taskObject. */
    public void drawtext(OutputStreamWriter fout, TextObject txtObj)
            throws IOException {
        if (debug)
            System.out.println("drawtext begin");
        fout.write("4 ");
        fout.write(txtObj.sub_type + " ");
        fout.write(txtObj.color + " ");
        fout.write(txtObj.depth + " ");
        fout.write(txtObj.pen_style + " ");
        fout.write(txtObj.font + " ");
        fout.write(txtObj.font_size + " ");
        fout.write(txtObj.angle + " ");
        fout.write(txtObj.font_flags + " ");
        fout.write(txtObj.height + " " + txtObj.length + " ");
        fout.write(txtObj.x + " " + txtObj.y + " ");
        fout.write(txtObj.str + "\\001\n");
        if (debug)
            System.out.println("drawtext end");
    }

    /** draw the box of the task. */
    public void drawbox(OutputStreamWriter fout, BoxObject boxObject)
            throws IOException {
        if (debug)
            System.out.println("drawbox begin");
        // draw an xfig box object;
        fout.write("2 ");
        fout.write(boxObject.sub_type + " ");
        fout.write(boxObject.line_style + " ");
        fout.write(boxObject.thickness + " ");
        fout.write(boxObject.pen_color + " ");
        fout.write(boxObject.fill_color + " ");
        fout.write(boxObject.depth + " ");
        fout.write(boxObject.pen_style + " ");
        fout.write(boxObject.area_fill + " ");
        fout.write(" " + boxObject.style_val + "  ");
        fout.write(boxObject.join_style + " ");
        fout.write(boxObject.cap_style + " ");
        fout.write(boxObject.radius + " ");
        fout.write(boxObject.forward_arrow + " ");
        fout.write(boxObject.backward_arrow + " ");
        fout.write(boxObject.npoints + "\n\t");

        int arrow_type = 1;
        int arrow_style = 1;
        float arrow_thickness = 1.00f;
        float arrow_width = 60.0f;
        float arrow_height = 60.0f;

        if (boxObject.sub_type == 3) {
            if (boxObject.forward_arrow != 0) {
                System.out.println("forward");
                fout.write("      " + (int) arrow_type + " "
                        + (int) arrow_style + " " + (int) arrow_thickness
                        + (int) arrow_width + " " + (int) arrow_height + "\n");
            }
            if (boxObject.backward_arrow != 0) {
                fout.write("      " + (int) arrow_type + " "
                        + (int) arrow_style + " " + (int) arrow_thickness
                        + (int) arrow_width + " " + (int) arrow_height + "\n");
                System.out.println("back");
            }
        }

        for (int i = 0; i < boxObject.points.length; i++)
            fout.write((int) (boxObject.points[i]) + " ");

        fout.write("\n");
        if (debug)
            System.out.println("drawbox end");
    }

    /** Draw the axes. */
    public void labelAxes(OutputStreamWriter fout) {
        // TODO write this method
        if (debug)
            System.out.println("labelAxes begin");
        if (debug)
            System.out.println("labelAxes end");
    }

    /** @return a color as a string like #00FF00 for green color. */
    private String getHexaColor(Color color) {
        if (debug)
            System.out.println("getHexaColor begin");
        String sColor = "#"; // result string

        if (color.getRed() <= 15)
            sColor += "0";
        sColor += Integer.toHexString(color.getRed());
        if (color.getGreen() <= 15)
            sColor += "0";
        sColor += Integer.toHexString(color.getGreen());
        if (color.getBlue() <= 15)
            sColor += "0";
        sColor += Integer.toHexString(color.getBlue());

        if (debug)
            System.out.println("getHexaColor end");
        return sColor;
    }

    /** Class to store text informations. */
    private class TextObject {
        int sub_type;

        int color;

        int depth;

        int pen_style;

        int font;

        float font_size;

        float angle;

        int font_flags;

        float height;

        float length;

        int x;

        int y;

        String str = "";
    }

    /** Class to store box informations. */
    private class BoxObject {
        int code = 2; // polyline always

        int npoints;

        int sub_type;

        int fill_color = 1; // blue color by default

        /*
         * case ("Black"), boxObject.fill_color = 0; case ("Blue"),
         * boxObject.fill_color = 1; case ("Green"), boxObject.fill_color = 2;
         * case ("Cyan"), boxObject.fill_color = 3; case ("Red"),
         * boxObject.fill_color = 4; case ("Magenta"), boxObject.fill_color = 5;
         * case ("Yellow"), boxObject.fill_color = 6; case ("White"),
         * boxObject.fill_color = 7;
         */

        int line_style = 0;

        int thickness = 1;

        int pen_color = 0;

        int depth;

        int pen_style = 0;

        int area_fill = 20;

        int style_val = 0;

        int join_style = 0;

        int cap_style = 0;

        int radius = 0;

        int forward_arrow = 0;

        int backward_arrow = 0;

        float[] points;
    }
}
