/*
GanttProject is an opensource project management tool.
Copyright (C) 2005-2011 GanttProject team

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 3
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.ganttproject.impex.htmlpdf;

import java.net.URI;
import java.net.URLDecoder;
import java.util.List;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamSource;

import net.sourceforge.ganttproject.CustomProperty;
import net.sourceforge.ganttproject.CustomPropertyDefinition;
import net.sourceforge.ganttproject.document.Document;
import net.sourceforge.ganttproject.export.ExportException;
import net.sourceforge.ganttproject.export.TaskVisitor;
import net.sourceforge.ganttproject.gui.UIFacade;
import net.sourceforge.ganttproject.io.SaverBase;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.resource.HumanResource;
import net.sourceforge.ganttproject.resource.HumanResourceManager;
import net.sourceforge.ganttproject.task.CustomColumnsValues;
import net.sourceforge.ganttproject.task.ResourceAssignment;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskManager;

import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import biz.ganttproject.core.table.ColumnList;

/**
 * Serializes project data into XML for GanttProject's HTML/FOP stylesheets.
 *
 * @author dbarashev (Dmitry Barashev)
 */
public class XmlSerializer extends SaverBase {
  private final SAXTransformerFactory myFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();

  protected void startPrefixedElement(String name, AttributesImpl attrs, TransformerHandler handler)
      throws SAXException {
    handler.startElement("http://ganttproject.sf.net/", name, "ganttproject:" + name, attrs);
    attrs.clear();
  }

  protected void endPrefixedElement(String name, TransformerHandler handler) throws SAXException {
    handler.endElement("http://ganttproject.sf.net/", name, "ganttproject:" + name);
  }

  protected void textElement(String name, AttributesImpl attrs, String text, TransformerHandler handler)
      throws SAXException {
    if (text != null) {
      startElement(name, attrs, handler);
      handler.startCDATA();
      handler.characters(text.toCharArray(), 0, text.length());
      handler.endCDATA();
      endElement(name, handler);
      attrs.clear();
    }
  }

  protected SAXTransformerFactory getTransformerFactory() {
    return myFactory;
  }

  protected TransformerHandler createHandler(String xsltPath) throws TransformerConfigurationException {
    TransformerHandler result = getTransformerFactory().newTransformerHandler(new StreamSource(xsltPath));
    Transformer transformer = result.getTransformer();
    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
    return result;
  }

  protected String i18n(String key) {
    String text = GanttLanguage.getInstance().getText(key);
    return GanttLanguage.getInstance().correctLabel(text);
  }

  protected void writeColumns(ColumnList visibleFields, TransformerHandler handler) throws SAXException {
    AttributesImpl attrs = new AttributesImpl();
    int totalWidth = 0;
    for (int i = 0; i < visibleFields.getSize(); i++) {
      if (visibleFields.getField(i).isVisible()) {
        totalWidth += visibleFields.getField(i).getWidth();
      }
    }
    for (int i = 0; i < visibleFields.getSize(); i++) {
      ColumnList.Column field = visibleFields.getField(i);
      if (field.isVisible()) {
        addAttribute("id", field.getID(), attrs);
        addAttribute("name", field.getName(), attrs);
        addAttribute("width", field.getWidth() * 100 / totalWidth, attrs);
        emptyElement("field", attrs, handler);
      }
    }
  }

  protected void writeViews(UIFacade facade, TransformerHandler handler) throws SAXException {
    AttributesImpl attrs = new AttributesImpl();
    addAttribute("id", "task-table", attrs);
    startElement("view", attrs, handler);
    writeColumns(facade.getTaskTree().getVisibleFields(), handler);
    endElement("view", handler);

    addAttribute("id", "resource-table", attrs);
    startElement("view", attrs, handler);
    writeColumns(facade.getResourceTree().getVisibleFields(), handler);

    endElement("view", handler);
  }

  protected void writeTasks(final TaskManager taskManager, final TransformerHandler handler) throws ExportException,
      SAXException {
    AttributesImpl attrs = new AttributesImpl();
    addAttribute("xslfo-path", "", attrs);
    addAttribute("title", i18n("tasksList"), attrs);
    addAttribute("name", i18n("name"), attrs);
    addAttribute("begin", i18n("start"), attrs);
    addAttribute("end", i18n("end"), attrs);
    addAttribute("milestone", i18n("meetingPoint"), attrs);
    addAttribute("progress", "%", attrs);
    addAttribute("assigned-to", i18n("human"), attrs);
    addAttribute("notes", i18n("notes"), attrs);
    addAttribute("duration", i18n("duration"), attrs);
    startPrefixedElement("tasks", attrs, handler);
    TaskVisitor visitor = new TaskVisitor() {
      AttributesImpl myAttrs = new AttributesImpl();

      @Override
      protected String serializeTask(Task t, int depth) throws Exception {
        addAttribute("depth", depth, myAttrs);
        startPrefixedElement("task", myAttrs, handler);
        {
          addAttribute("id", "tpd1", myAttrs);
          textElement("priority", myAttrs, i18n(t.getPriority().getI18nKey()), handler);
        }

        addAttribute("id", "tpd3", myAttrs);
        textElement("name", myAttrs, t.getName(), handler);

        addAttribute("id", "tpd4", myAttrs);
        textElement("begin", myAttrs, t.getStart().toString(), handler);

        addAttribute("id", "tpd5", myAttrs);
        textElement("end", myAttrs, t.getDisplayEnd().toString(), handler);
        textElement("milestone", myAttrs, Boolean.valueOf(t.isMilestone()).toString(), handler);

        addAttribute("id", "tpd7", myAttrs);
        textElement("progress", myAttrs, String.valueOf(t.getCompletionPercentage()), handler);

        addAttribute("id", "tpd6", myAttrs);
        textElement("duration", myAttrs, String.valueOf(t.getDuration().getLength()), handler);

        final List<Document> attachments = t.getAttachments();
        for (int i = 0; i < attachments.size(); i++) {
          Document nextAttachment = attachments.get(i);
          URI nextUri = nextAttachment.getURI();
          if (nextUri != null) {
            String strUri = URLDecoder.decode(nextUri.toString(), "utf-8");
            if (strUri.startsWith("file:")) {
              if (strUri.endsWith("/")) {
                strUri = strUri.replaceAll("/+$", "");
              }
              int lastSlash = strUri.lastIndexOf('/');
              if (lastSlash >= 0) {
                addAttribute("display-name", strUri.substring(lastSlash + 1), myAttrs);
              }
            }
            textElement("attachment", myAttrs, strUri, handler);
          } else {
            textElement("attachment", myAttrs, nextAttachment.getPath(), handler);
          }
        }
        {
          HumanResource coordinator = t.getAssignmentCollection().getCoordinator();
          if (coordinator != null) {
            addAttribute("id", "tpd8", myAttrs);
            textElement("coordinator", myAttrs, coordinator.getName(), handler);
          }
        }
        StringBuffer usersS = new StringBuffer();
        ResourceAssignment[] assignments = t.getAssignments();
        if (assignments.length > 0) {
          for (int j = 0; j < assignments.length; j++) {
            addAttribute("resource-id", assignments[j].getResource().getId(), myAttrs);
            emptyElement("assigned-resource", myAttrs, handler);
            usersS.append(assignments[j].getResource().getName());
            if (j < assignments.length - 1) {
              usersS.append(getAssignedResourcesDelimiter());
            }
          }
        }

        addAttribute("id", "tpdResources", myAttrs);
        textElement("assigned-to", myAttrs, usersS.toString(), handler);
        if (t.getNotes() != null && t.getNotes().length() > 0) {
          textElement("notes", myAttrs, t.getNotes(), handler);
        }
        if (t.getColor() != null) {
          textElement("color", myAttrs, getHexaColor(t.getColor()), handler);
        }
        {
          AttributesImpl attrs = new AttributesImpl();
          CustomColumnsValues customValues = t.getCustomValues();
          for (CustomPropertyDefinition def : taskManager.getCustomPropertyManager().getDefinitions()) {
            Object value = customValues.getValue(def);
            String valueAsString = value == null ? "" : value.toString();
            addAttribute("id", def.getID(), attrs);
            textElement("custom-field", attrs, valueAsString, handler);
          }
        }
        endPrefixedElement("task", handler);
        return "";
      }
    };
    try {
      visitor.visit(taskManager);
    } catch (Exception e) {
      throw new ExportException("Failed to write tasks", e);
    }
    endPrefixedElement("tasks", handler);
  }

  protected String getAssignedResourcesDelimiter() {
    return " ";
  }

  protected void writeResources(HumanResourceManager resourceManager, TransformerHandler handler) throws SAXException {
    AttributesImpl attrs = new AttributesImpl();
    addAttribute("title", i18n("resourcesList"), attrs);
    addAttribute("name", i18n("colName"), attrs);
    addAttribute("role", i18n("colRole"), attrs);
    addAttribute("mail", i18n("colMail"), attrs);
    addAttribute("phone", i18n("colPhone"), attrs);
    startPrefixedElement("resources", attrs, handler);
    {
      List<HumanResource> resources = resourceManager.getResources();

      // String[] function =
      // RoleManager.Access.getInstance().getRoleNames();
      for (int i = 0; i < resources.size(); i++) {
        HumanResource p = resources.get(i);
        addAttribute("id", p.getId(), attrs);
        startPrefixedElement("resource", attrs, handler);
        addAttribute("id", "0", attrs);
        textElement("name", attrs, p.getName(), handler);
        addAttribute("id", "1", attrs);
        textElement("role", attrs, p.getRole().getName(), handler);
        addAttribute("id", "2", attrs);
        textElement("mail", attrs, p.getMail(), handler);
        addAttribute("id", "3", attrs);
        textElement("phone", attrs, p.getPhone(), handler);

        List<CustomProperty> customFields = p.getCustomProperties();
        for (int j = 0; j < customFields.size(); j++) {
          CustomProperty nextProperty = customFields.get(j);
          addAttribute("id", nextProperty.getDefinition().getID(), attrs);
          String value = nextProperty.getValueAsString();
          textElement("custom-field", attrs, value, handler);
        }
        endPrefixedElement("resource", handler);
      }
    }
    endPrefixedElement("resources", handler);
  }

  protected static String getHexaColor(java.awt.Color color) {
    StringBuffer out = new StringBuffer();
    out.append("#");
    if (color.getRed() <= 15) {
      out.append("0");
    }
    out.append(Integer.toHexString(color.getRed()));
    if (color.getGreen() <= 15) {
      out.append("0");
    }
    out.append(Integer.toHexString(color.getGreen()));
    if (color.getBlue() <= 15) {
      out.append("0");
    }
    out.append(Integer.toHexString(color.getBlue()));

    return out.toString();
  }

}
