/*
GanttProject is an opensource project management tool. License: GPL3
Copyright (C) 2012 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.webdav;

import java.awt.event.ActionEvent;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;

import javax.swing.Action;
import javax.swing.JComponent;

import net.sourceforge.ganttproject.GPLogger;
import net.sourceforge.ganttproject.IGanttProject;
import net.sourceforge.ganttproject.ProjectEventListener;
import net.sourceforge.ganttproject.action.CancelAction;
import net.sourceforge.ganttproject.action.OkAction;
import net.sourceforge.ganttproject.document.Document;
import net.sourceforge.ganttproject.document.DocumentStorageUi;
import net.sourceforge.ganttproject.gui.UIFacade;
import biz.ganttproject.core.option.BooleanOption;
import biz.ganttproject.core.option.ChangeValueEvent;
import biz.ganttproject.core.option.ChangeValueListener;
import biz.ganttproject.core.option.DefaultBooleanOption;
import biz.ganttproject.core.option.DefaultEnumerationOption;
import biz.ganttproject.core.option.DefaultIntegerOption;
import biz.ganttproject.core.option.DefaultStringOption;
import biz.ganttproject.core.option.EnumerationOption;
import biz.ganttproject.core.option.GPAbstractOption;
import biz.ganttproject.core.option.IntegerOption;
import biz.ganttproject.core.option.ListOption;
import biz.ganttproject.core.option.StringOption;

import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;

/**
 * Implements storage UI for WebDAV storages
 *
 * @author dbarashev (Dmitry Barashev)
 */
public class WebDavStorageImpl implements DocumentStorageUi {
  static class ServerListOption extends GPAbstractOption<WebDavServerDescriptor> implements ListOption<WebDavServerDescriptor> {
    private List<WebDavServerDescriptor> myServers = Lists.newArrayList();

    class EnumerationOptionImpl extends DefaultEnumerationOption<WebDavServerDescriptor> {
      public EnumerationOptionImpl(String id) {
        super(id, myServers.toArray(new WebDavServerDescriptor[0]));
      }
      @Override
      protected String objectToString(WebDavServerDescriptor obj) {
        return obj.name;
      }

      @Override
      public void setValue(String value) {
        super.setValue(value);
        ServerListOption.this.setValue(stringToObject(value));
      }

      void reload() {
        String curValue = getValue();
        int idxValue = Arrays.asList(getAvailableValues()).indexOf(curValue);
        super.reloadValues(myServers);
        if (idxValue >= 0) {
          setValueIndex(idxValue);
        }
      }
    };

    private EnumerationOptionImpl myEnumerationOption;

    public ServerListOption(String id) {
      super(id);
      myEnumerationOption = new EnumerationOptionImpl(id);
    }

    @Override
    public String getPersistentValue() {
      StringBuilder result = new StringBuilder();
      for (WebDavServerDescriptor server : myServers) {
        result.append("\n").append(server.name).append("\t").append(server.getRootUrl()).append("\t").append(server.username);
        if (server.savePassword) {
          result.append("\t").append(server.password);
        }
      }
      return result.toString();
    }

    @Override
    public void loadPersistentValue(String value) {
      for (String s : value.split("\\n")) {
        if (!Strings.isNullOrEmpty(s)) {
          String[] parts = s.split("\\t");
          WebDavServerDescriptor server = new WebDavServerDescriptor();
          if (parts.length >= 1) {
            server.name = parts[0];
          }
          if (parts.length >= 2) {
            server.setRootUrl(parts[1]);
          }
          if (parts.length >= 3) {
            server.username = parts[2];
          }
          if (parts.length >= 4) {
            server.password = parts[3];
            server.savePassword = true;
          }
          myServers.add(server);
        }
      }
      myEnumerationOption.reload();
    }

    @Override
    public void setValues(Iterable<WebDavServerDescriptor> values) {
      myServers = Lists.newArrayList(values);
      myEnumerationOption.reload();
    }

    @Override
    public Iterable<WebDavServerDescriptor> getValues() {
      return myServers;
    }

    @Override
    public void setValueIndex(int idx) {
      super.setValue(myServers.get(idx));
    }

    @Override
    public void setValue(WebDavServerDescriptor value) {
      if (!Objects.equal(value, getValue())) {
        super.setValue(value);
        myEnumerationOption.setSelectedValue(value);
      }
    }

    @Override
    public void addValue(WebDavServerDescriptor value) {
      myServers.add(value);
      myEnumerationOption.reload();
    }

    @Override
    public void updateValue(WebDavServerDescriptor oldValue, WebDavServerDescriptor newValue) {
      int idxOldValue = myServers.indexOf(oldValue);
      assert idxOldValue >= 0 : "Failed to find value=" + oldValue + " in the list=" + myServers;
      myServers.set(idxOldValue, newValue);
      myEnumerationOption.reload();
    }

    @Override
    public void removeValueIndex(int idx) {
      myServers.remove(idx);
      myEnumerationOption.reload();
    }

    @Override
    public EnumerationOption asEnumerationOption() {
      return myEnumerationOption;
    }
  }
  private final ListOption<WebDavServerDescriptor> myServers = new ServerListOption("servers");
  private final StringOption myLegacyLastWebDAVDocument = new DefaultStringOption("last-webdav-document", "");
  private final StringOption myLastWebDavDocumentOption = new DefaultStringOption("lastDocument", null);
  private final IntegerOption myWebDavLockTimeoutOption = new DefaultIntegerOption("webdav.lockTimeout", -1);
  private final BooleanOption myReleaseLockOption = new DefaultBooleanOption("lockRelease", true);
  private final StringOption myUsername = new DefaultStringOption("username", "");
  private final StringOption myPassword = new DefaultStringOption("password", "");
  private final StringOption myProxy = new DefaultStringOption("proxy", "");
  private final MiltonResourceFactory myWebDavFactory = new MiltonResourceFactory();
  private final UIFacade myUiFacade;
  private final IGanttProject myProject;

  public WebDavStorageImpl(final IGanttProject project, UIFacade uiFacade) {
    myProject = project;
    myUiFacade = uiFacade;
    project.addProjectEventListener(new ProjectEventListener.Stub() {
      @Override
      public void projectClosed() {
        if (myReleaseLockOption.isChecked() && project.getDocument() != null) {
          project.getDocument().releaseLock();
        }
      }
    });
    myPassword.setScreened(true);
  }

  @Override
  public Components open(Document currentDocument, final DocumentReceiver receiver) {
    final GanttURLChooser chooser = createChooser(currentDocument);
    final OkAction openAction = createNoLockAction("storage.action.open", chooser, receiver);
    //final OkAction openAndLockAction = createLockAction("storage.action.openAndLock", chooser, receiver);
//    chooser.setSelectionListener(new GanttURLChooser.SelectionListener() {
//      @Override
//      public void setSelection(WebDavResource resource) {
//        if (resource == null) {
//          return;
//        }
//        try {
//          openAndLockAction.setEnabled(resource.canLock());
//        } catch (WebDavException e) {
//          chooser.showError(e);
//        }
//      }
//    });
    JComponent contentPane = chooser.createOpenDocumentUi(openAction);
    chooser.getPathOption().addChangeValueListener(new ChangeValueListener() {
      @Override
      public void changeValue(ChangeValueEvent event) {
        boolean empty = "".equals(event.getNewValue());
        openAction.setEnabled(!empty);
//        openAndLockAction.setEnabled(!empty);
      }
    });
    return new Components(contentPane, new Action[] {openAction, /*openAndLockAction,*/ new CancelAction() {
      @Override
      public void actionPerformed(ActionEvent e) {
        receiver.setDocument(null);
      }
    }});
  }

  @Override
  public Components save(Document currentDocument, final DocumentReceiver receiver) {
    final GanttURLChooser chooser = createChooser(currentDocument);
    OkAction saveAction = createNoLockAction("storage.action.save", chooser, receiver);
    JComponent contentPane = chooser.createSaveDocumentUi(saveAction);
    return new Components(contentPane, new Action[] {saveAction, new CancelAction() {
      @Override
      public void actionPerformed(ActionEvent e) {
        receiver.setDocument(null);
      }
    }});
  }

  private GanttURLChooser createChooser(Document currentDocument) {
    WebDavUri currentUri;
    if (currentDocument instanceof HttpDocument) {
      currentUri = ((HttpDocument)currentDocument).getWebdavResource().getWebDavUri();
      myUsername.setValue(currentDocument.getUsername());
      myPassword.setValue(currentDocument.getPassword());
    } else {
      String lastDocument = Objects.firstNonNull(
          getLastWebDavDocumentOption().getValue(), getLegacyLastWebDAVDocumentOption().getValue());
      if (lastDocument == null) {
        currentUri = null;
      } else {
        String[] savedComponents = lastDocument.split("\\t");
        if (savedComponents.length == 1) {
          currentUri = new WebDavUri(savedComponents[0]);
        } else {
          try {
            URL rootUrl = new URL(savedComponents[0]);
            currentUri = new WebDavUri(rootUrl.getHost(), savedComponents[0], savedComponents[1]);
          } catch (MalformedURLException e) {
            GPLogger.logToLogger(e);
            currentUri = null;
          }
        }
      }
    }
    myWebDavFactory.clearCache();
    return new GanttURLChooser(myProject, myUiFacade, myServers, currentUri, myUsername, myPassword, getWebDavLockTimeoutOption(), getWebDavReleaseLockOption(), myWebDavFactory);
  }

  private OkAction createNoLockAction(String key, final GanttURLChooser chooser, final DocumentReceiver receiver) {
    return new OkAction(key) {
      {
        setDefault(false);
      }
      @Override
      public void actionPerformed(ActionEvent event) {
        try {
          myWebDavFactory.setCredentials(chooser.getUsername(), chooser.getPassword());
          WebDavUri webDavUri = chooser.getUrl();
          if (webDavUri != null) {
            receiver.setDocument(new HttpDocument(
                myWebDavFactory.createResource(webDavUri), chooser.getUsername(), chooser.getPassword(), HttpDocument.NO_LOCK));
            myLastWebDavDocumentOption.setValue(webDavUri.buildRootUrl() + "\t" + webDavUri.path);
            myLegacyLastWebDAVDocument.setValue(webDavUri.buildUrl());
          }
          chooser.dispose();
        } catch (IOException e) {
          chooser.showError(e);
        }
      }
    };
  }

  public ListOption<WebDavServerDescriptor> getServersOption() {
    return myServers;
  }

  public StringOption getWebDavUsernameOption() {
    return myUsername;
  }

  public StringOption getLegacyLastWebDAVDocumentOption() {
    return myLegacyLastWebDAVDocument;
  }

  public StringOption getLastWebDavDocumentOption() {
    return myLastWebDavDocumentOption;
  }

  public IntegerOption getWebDavLockTimeoutOption() {
    return myWebDavLockTimeoutOption;
  }

  public BooleanOption getWebDavReleaseLockOption() {
    return myReleaseLockOption;
  }

  public WebDavServerDescriptor findServer(String path) {
    WebDavUri uri = new WebDavUri(path);
    for (WebDavServerDescriptor server : myServers.getValues()) {
      if (server.getRootUrl().equals(uri.buildRootUrl())) {
        return server;
      }
    }
    return null;
  }

  public StringOption getProxyOption () {
    return myProxy;
  }
}
