/*
GanttProject is an opensource project management tool. License: GPL3
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 net.sourceforge.ganttproject.document;

import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.Set;

import net.sourceforge.ganttproject.IGanttProject;
import net.sourceforge.ganttproject.gui.GPColorChooser;
import net.sourceforge.ganttproject.gui.UIFacade;
import net.sourceforge.ganttproject.io.GPSaver;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.parser.AbstractTagHandler;
import net.sourceforge.ganttproject.parser.AllocationTagHandler;
import net.sourceforge.ganttproject.parser.CustomPropertiesTagHandler;
import net.sourceforge.ganttproject.parser.DefaultWeekTagHandler;
import net.sourceforge.ganttproject.parser.DependencyTagHandler;
import net.sourceforge.ganttproject.parser.FileFormatException;
import net.sourceforge.ganttproject.parser.GPParser;
import net.sourceforge.ganttproject.parser.HolidayTagHandler;
import net.sourceforge.ganttproject.parser.OptionTagHandler;
import net.sourceforge.ganttproject.parser.ParserFactory;
import net.sourceforge.ganttproject.parser.ParsingListener;
import net.sourceforge.ganttproject.parser.PreviousStateTasksTagHandler;
import net.sourceforge.ganttproject.parser.ResourceTagHandler;
import net.sourceforge.ganttproject.parser.RoleTagHandler;
import net.sourceforge.ganttproject.parser.TagHandler;
import net.sourceforge.ganttproject.parser.TaskDisplayColumnsTagHandler;
import net.sourceforge.ganttproject.parser.TaskPropertiesTagHandler;
import net.sourceforge.ganttproject.parser.TaskTagHandler;
import net.sourceforge.ganttproject.parser.VacationTagHandler;
import net.sourceforge.ganttproject.parser.ViewTagHandler;
import net.sourceforge.ganttproject.resource.HumanResourceManager;
import net.sourceforge.ganttproject.roles.RoleManager;
import net.sourceforge.ganttproject.task.TaskManager;
import net.sourceforge.ganttproject.task.TaskManagerImpl;

import org.eclipse.core.runtime.IStatus;
import org.xml.sax.Attributes;

import com.google.common.collect.ImmutableSet;

import biz.ganttproject.core.calendar.GPCalendarCalc;
import biz.ganttproject.core.option.ListOption;
import biz.ganttproject.core.table.ColumnList;

/**
 * @author bard
 */
class ProxyDocument implements Document {
  private final Document myPhysicalDocument;

  private IGanttProject myProject;

  private UIFacade myUIFacade;

  private final ParserFactory myParserFactory;

  private final DocumentCreator myCreator;

  private PortfolioImpl myPortfolio;

  private final ColumnList myTaskVisibleFields;

  private final ColumnList myResourceVisibleFields;

  ProxyDocument(DocumentCreator creator, Document physicalDocument, IGanttProject project, UIFacade uiFacade,
      ColumnList taskVisibleFields, ColumnList resourceVisibleFields, ParserFactory parserFactory) {
    myPhysicalDocument = physicalDocument;
    myProject = project;
    myUIFacade = uiFacade;
    myParserFactory = parserFactory;
    myCreator = creator;
    myTaskVisibleFields = taskVisibleFields;
    myResourceVisibleFields = resourceVisibleFields;
  }

  @Override
  public String getFileName() {
    return myPhysicalDocument.getFileName();
  }

  @Override
  public boolean canRead() {
    return myPhysicalDocument.canRead();
  }

  @Override
  public IStatus canWrite() {
    return myPhysicalDocument.canWrite();
  }

  @Override
  public boolean isValidForMRU() {
    return myPhysicalDocument.isValidForMRU();
  }

  @Override
  public boolean acquireLock() {
    return myPhysicalDocument.acquireLock();
  }

  @Override
  public void releaseLock() {
    myPhysicalDocument.releaseLock();
  }

  @Override
  public InputStream getInputStream() throws IOException {
    return myPhysicalDocument.getInputStream();
  }

  @Override
  public OutputStream getOutputStream() throws IOException {
    return myPhysicalDocument.getOutputStream();
  }

  @Override
  public String getPath() {
    return myPhysicalDocument.getPath();
  }

  @Override
  public String getFilePath() {
    String result = myPhysicalDocument.getFilePath();
    if (result == null) {
      try {
        result = myCreator.createTemporaryFile();
      } catch (IOException e) {
        myUIFacade.showErrorDialog(e);
      }
    }
    return result;
  }

  @Override
  public String getUsername() {
    return myPhysicalDocument.getUsername();
  }

  @Override
  public String getPassword() {
    return myPhysicalDocument.getPassword();
  }

  @Override
  public String getLastError() {
    return myPhysicalDocument.getLastError();
  }

  @Override
  public void read() throws IOException, DocumentException {
    FailureState failure = new FailureState();
    SuccessState success = new SuccessState();
    ParsingState parsing = new ParsingState(success, failure);
    // OpenCopyConfirmationState confirmation = new OpenCopyConfirmationState(
    // parsing, failure);
    // AcquireLockState lock = new AcquireLockState(parsing, confirmation);
    try {
      getTaskManager().setEventsEnabled(false);
      parsing.enter();
    } finally {
      getTaskManager().setEventsEnabled(true);
    }
    // lock.enter();
  }

  @Override
  public void write() throws IOException {
    GPSaver saver = myParserFactory.newSaver();
    byte[] buffer;
    try {
      ByteArrayOutputStream bufferStream = new ByteArrayOutputStream();
      saver.save(bufferStream);
      bufferStream.flush();
      buffer = bufferStream.toByteArray();
    } catch (IOException e) {
      getUIFacade().showErrorDialog(e);
      return;
    }
    OutputStream output = getOutputStream();
    try {
      output.write(buffer);
      output.flush();
    } finally {
      output.close();
    }
  }

  private TaskManagerImpl getTaskManager() {
    return (TaskManagerImpl) myProject.getTaskManager();
  }

  private RoleManager getRoleManager() {
    return myProject.getRoleManager();
  }

  private HumanResourceManager getHumanResourceManager() {
    return myProject.getHumanResourceManager();
  }

  private GPCalendarCalc getActiveCalendar() {
    return myProject.getActiveCalendar();
  }

  private UIFacade getUIFacade() {
    return myUIFacade;
  }

  // class AcquireLockState {
  // OpenCopyConfirmationState myConfirmationState;
  //
  // ParsingState myParsingState;
  //
  // public AcquireLockState(ParsingState parsing,
  // OpenCopyConfirmationState confirmation) {
  // myParsingState = parsing;
  // myConfirmationState = confirmation;
  // }
  //
  // void enter() throws IOException, DocumentException {
  // boolean locked = acquireLock();
  // if (!locked) {
  // myConfirmationState.enter();
  // } else {
  // myParsingState.enter();
  // }
  // }
  // }
  //
  // class OpenCopyConfirmationState {
  // private final ParsingState myParsingState;
  //
  // private final FailureState myExitState;
  //
  // public OpenCopyConfirmationState(ParsingState parsing,
  // FailureState failure) {
  // myParsingState = parsing;
  // myExitState = failure;
  // }
  //
  // void enter() throws IOException, DocumentException {
  // String message = GanttLanguage.getInstance().getText("msg13");
  // String title = GanttLanguage.getInstance().getText("warning");
  // if (UIFacade.Choice.YES==getUIFacade().showConfirmationDialog(message,
  // title)) {
  // myParsingState.enter();
  // } else {
  // myExitState.enter();
  // }
  // }
  // }

  class ParsingState {
    private final FailureState myFailureState;

    private final SuccessState mySuccessState;

    public ParsingState(SuccessState success, FailureState failure) {
      mySuccessState = success;
      myFailureState = failure;
    }

    void enter() throws IOException, DocumentException {
      GPParser opener = myParserFactory.newParser();
      HumanResourceManager hrManager = getHumanResourceManager();
      RoleManager roleManager = getRoleManager();
      TaskManager taskManager = getTaskManager();
      ResourceTagHandler resourceHandler = new ResourceTagHandler(hrManager, roleManager,
          myProject.getResourceCustomPropertyManager());
      DependencyTagHandler dependencyHandler = new DependencyTagHandler(opener.getContext(), taskManager, getUIFacade());
      AllocationTagHandler allocationHandler = new AllocationTagHandler(hrManager, getTaskManager(), getRoleManager());
      VacationTagHandler vacationHandler = new VacationTagHandler(hrManager);
      PreviousStateTasksTagHandler previousStateHandler = new PreviousStateTasksTagHandler(myProject.getBaselines());
      RoleTagHandler rolesHandler = new RoleTagHandler(roleManager);
      TaskTagHandler taskHandler = new TaskTagHandler(taskManager, opener.getContext(), myUIFacade.getTaskTree());
      DefaultWeekTagHandler weekHandler = new DefaultWeekTagHandler(getActiveCalendar());
      OnlyShowWeekendsTagHandler onlyShowWeekendsHandler = new OnlyShowWeekendsTagHandler(getActiveCalendar());
      ViewTagHandler viewHandler = new ViewTagHandler(getUIFacade());

      TaskPropertiesTagHandler taskPropHandler = new TaskPropertiesTagHandler(myProject.getTaskCustomColumnManager());
      opener.addTagHandler(taskPropHandler);
      CustomPropertiesTagHandler customPropHandler = new CustomPropertiesTagHandler(opener.getContext(),
          getTaskManager());
      opener.addTagHandler(customPropHandler);
      TaskDisplayColumnsTagHandler taskDisplayHandler = new TaskDisplayColumnsTagHandler(myTaskVisibleFields);
      opener.addTagHandler(taskDisplayHandler);

      TaskDisplayColumnsTagHandler resourceViewHandler = new TaskDisplayColumnsTagHandler(
          myResourceVisibleFields, "field", "id", "order", "width", "visible");
      opener.addTagHandler(resourceViewHandler);
      opener.addParsingListener(resourceViewHandler);

      opener.addTagHandler(taskHandler);
      opener.addParsingListener(taskHandler);

      opener.addParsingListener(taskDisplayHandler);
      opener.addParsingListener(customPropHandler);

      opener.addTagHandler(opener.getDefaultTagHandler());
      opener.addTagHandler(opener.getTimelineTagHandler());
      opener.addParsingListener((ParsingListener)opener.getTimelineTagHandler());
      opener.addTagHandler(resourceHandler);
      opener.addTagHandler(dependencyHandler);
      opener.addTagHandler(allocationHandler);
      opener.addParsingListener(allocationHandler);
      opener.addTagHandler(vacationHandler);
      opener.addTagHandler(previousStateHandler);
      opener.addTagHandler(rolesHandler);
      opener.addTagHandler(weekHandler);
      opener.addTagHandler(onlyShowWeekendsHandler);
      opener.addTagHandler(viewHandler);
      opener.addTagHandler(new OptionTagHandler<ListOption<Color>>(GPColorChooser.getRecentColorsOption()));
      opener.addParsingListener(dependencyHandler);
      opener.addParsingListener(resourceHandler);


      HolidayTagHandler holidayHandler = new HolidayTagHandler(myProject.getActiveCalendar());
      opener.addTagHandler(new AbstractTagHandler("calendars") {
        @Override
        protected boolean onStartElement(Attributes attrs) {
          myProject.getActiveCalendar().setBaseCalendarID(attrs.getValue("base-id"));
          return true;
        }
      });
      opener.addTagHandler(holidayHandler);

      PortfolioTagHandler portfolioHandler = new PortfolioTagHandler();
      opener.addTagHandler(portfolioHandler);
      InputStream is;
      try {
        is = getInputStream();
      } catch (IOException e) {
        myFailureState.enter();
        throw new DocumentException(GanttLanguage.getInstance().getText("msg8") + ": " + e.getLocalizedMessage(), e);
      }
      if (opener.load(is)) {
        mySuccessState.enter();
      } else {
        myFailureState.enter();
      }
    }
  }

  class SuccessState {
    void enter() {
    }
  }

  class FailureState {
    void enter() {
    }
  }

  @Override
  public URI getURI() {
    return myPhysicalDocument.getURI();
  }

  @Override
  public boolean isLocal() {
    return myPhysicalDocument.isLocal();
  }

  @Override
  public boolean equals(Object doc) {
    if (false == doc instanceof ProxyDocument) {
      return false;
    }
    return getPath().equals(((Document) doc).getPath());
  }

  @Override
  public Portfolio getPortfolio() {
    return myPortfolio;
  }

  private PortfolioImpl getPortfolioImpl() {
    if (myPortfolio == null) {
      myPortfolio = new PortfolioImpl();
    }
    return myPortfolio;
  }

  private class PortfolioImpl implements Portfolio {
    private Document myDefaultDocument;

    @Override
    public Document getDefaultDocument() {
      return myDefaultDocument;
    }

    void setDefaultDocument(Document document) {
      if (myDefaultDocument != null) {
        throw new IllegalStateException("Don't set default document twice");
      }
      myDefaultDocument = document;
    }
  }

  private class PortfolioTagHandler extends AbstractTagHandler {
    private static final String PORTFOLIO_TAG = "portfolio";
    private static final String PROJECT_TAG = "project";
    private final Set<String> TAGS = ImmutableSet.of(PORTFOLIO_TAG, PROJECT_TAG);
    private static final String LOCATION_ATTR = "location";
    private boolean isReadingPortfolio = false;

    public PortfolioTagHandler() {
      super(null, false);
    }
    @Override
    public void startElement(String namespaceURI, String sName, String qName, Attributes attrs)
        throws FileFormatException {
      if (!TAGS.contains(qName)) {
        return;
      }
      setTagStarted(true);
      if (PORTFOLIO_TAG.equals(qName)) {
        isReadingPortfolio = true;
        return;
      }
      if (PROJECT_TAG.equals(qName) && isReadingPortfolio) {
        String locationAsString = attrs.getValue(LOCATION_ATTR);
        if (locationAsString != null) {
          Document document = myCreator.getDocument(locationAsString);
          getPortfolioImpl().setDefaultDocument(document);
        }

      }
    }

    @Override
    public void endElement(String namespaceURI, String sName, String qName) {
      if (!TAGS.contains(qName)) {
        return;
      }
      if (PORTFOLIO_TAG.equals(qName)) {
        isReadingPortfolio = false;
      }
      setTagStarted(false);
    }
  }

  private static class OnlyShowWeekendsTagHandler extends AbstractTagHandler {

    private final GPCalendarCalc calendar;

    public OnlyShowWeekendsTagHandler(GPCalendarCalc calendar) {
      super("only-show-weekends");
      this.calendar = calendar;
    }

    @Override
    protected boolean onStartElement(Attributes attrs) {
      calendar.setOnlyShowWeekends(Boolean.parseBoolean(attrs.getValue("value")));
      return true;
    }
  }
}
