/*
 * Copyright 2003-2011 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.mps.ide.projectPane.favorites;

import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.DefaultJDOMExternalizer;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMExternalizable;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.util.ArrayUtil;
import org.jetbrains.mps.openapi.module.SModuleReference;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.persistence.PersistenceFacade;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class MPSFavoritesManager implements ProjectComponent, JDOMExternalizable {
  private static final String ELEMENT_FAVORITES_LIST = "favorites_list";
  private static final String FAVORITES_ROOT = "favorite_root";
  private static final String ATTRIBUTE_NAME = "name";
  private static final String MODULE_REFERENCE = "module_ref";
  private static final String SNODE_REFERENCE = "node_ref";
  private final Map<String, List<Object>> myName2FavoritesRoots = new LinkedHashMap<String, List<Object>>();
  private final Project myProject;
  private List<MPSFavoritesListener> myListeners = new ArrayList<MPSFavoritesListener>();

  // Do not delete this attribute!!
  // It was added here to save at least some information into XML element inside writeExternal() method.
  // Without this HACK new IDEA platform will remove misc.xml file (nothing to save there)
  // and later fail with the exception: misc.xml file not found.
  public boolean keepMe = true;

  public interface MPSFavoritesListener {
    void rootsChanged(String listName);

    void listAdded(String listName);

    void listRemoved(String listName);
  }

  public MPSFavoritesManager(Project project) {
    myProject = project;
  }

  private static void writeRoots(Element element, List<Object> roots) {
    for (Object root : roots) {
      final Element favoriteRoot = new Element(FAVORITES_ROOT);
      if (root instanceof SModelReference) {
        favoriteRoot.setAttribute("model_ref", root.toString());
      } else if (root instanceof SModuleReference) {
        favoriteRoot.setAttribute(MODULE_REFERENCE, root.toString());
      } else if (root instanceof SNodeReference) {
        SNodeReference nodePointer = (SNodeReference) root;
        favoriteRoot.setAttribute(SNODE_REFERENCE, jetbrains.mps.smodel.SNodePointer.serialize(nodePointer));
      }
      element.addContent(favoriteRoot);
    }
  }

  private static List<Object> readRoots(final Element list, Project project) {
    List<Object> result = new ArrayList<Object>();
    for (Object favorite : list.getChildren(FAVORITES_ROOT)) {
      Element favoriteElement = (Element) favorite;
      final String moduleRef = favoriteElement.getAttributeValue(MODULE_REFERENCE);
      if (moduleRef != null) {
        result.add(PersistenceFacade.getInstance().createModuleReference(moduleRef));
        continue;
      }
      String snodeRef = favoriteElement.getAttributeValue(SNODE_REFERENCE);
      if (snodeRef != null) {
        result.add(jetbrains.mps.smodel.SNodePointer.deserialize(snodeRef));
      } else {
        //todo obsolete, remove after 3.0
        final String modelRef = favoriteElement.getAttributeValue("model_ref");
        if (modelRef != null) {
          final String nodeId = favoriteElement.getAttributeValue("node_pointer");
          if (nodeId == null) {
            SModelReference modelReference = PersistenceFacade.getInstance().createModelReference(modelRef);
            result.add(modelReference);
          } else {
            SNodeReference nodePointer = new jetbrains.mps.smodel.SNodePointer(modelRef, nodeId);
            result.add(nodePointer);
          }
        }
      }
    }
    return result;
  }

  private void onListAdded(String name) {
    //copy operation is necessary cause we can add/remove listeners in listeners
    for (MPSFavoritesListener listener : new ArrayList<MPSFavoritesListener>(myListeners)) {
      listener.listAdded(name);
    }
  }

  private void onListChanged(String name) {
    //copy operation is necessary cause we can add/remove listeners in listeners
    for (MPSFavoritesListener listener : new ArrayList<MPSFavoritesListener>(myListeners)) {
      listener.rootsChanged(name);
    }
  }

  private void onListRemoved(String name) {
    //copy operation is necessary cause we can add/remove listeners in listeners
    for (MPSFavoritesListener listener : new ArrayList<MPSFavoritesListener>(myListeners)) {
      listener.listRemoved(name);
    }
  }

  @Override
  public void projectOpened() {

  }

  @Override
  public void projectClosed() {

  }

  @Override
  @NotNull
  public String getComponentName() {
    return "MPSFavoritesManager";
  }

  @Override
  public void initComponent() {

  }

  @Override
  public void disposeComponent() {

  }

  @Override
  public void readExternal(Element element) throws InvalidDataException {
    myName2FavoritesRoots.clear();
    for (Object list : element.getChildren(ELEMENT_FAVORITES_LIST)) {
      final String name = ((Element) list).getAttributeValue(ATTRIBUTE_NAME);
      List<Object> roots = readRoots((Element) list, myProject);
      myName2FavoritesRoots.put(name, roots);
    }
    DefaultJDOMExternalizer.readExternal(this, element);
  }

  @Override
  public void writeExternal(Element element) throws WriteExternalException {
    for (final String name : myName2FavoritesRoots.keySet()) {
      Element list = new Element(ELEMENT_FAVORITES_LIST);
      list.setAttribute(ATTRIBUTE_NAME, name);
      writeRoots(list, myName2FavoritesRoots.get(name));
      element.addContent(list);
    }
    DefaultJDOMExternalizer.writeExternal(this, element);
  }

  public String[] getFavoriteNames() {
    Set<String> favariteNames = new LinkedHashSet<String>();
    favariteNames.addAll(myName2FavoritesRoots.keySet());
    return ArrayUtil.toStringArray(favariteNames);
  }

  public void addNewFavoritesList(String name) {
    myName2FavoritesRoots.put(name, new ArrayList<Object>());
    onListAdded(name);
  }

  public void removeFavoritesList(String name) {
    myName2FavoritesRoots.remove(name);
    onListRemoved(name);
  }

  public void addRoots(String name, List<Object> roots) {
    if (!myName2FavoritesRoots.containsKey(name)) return;
    myName2FavoritesRoots.get(name).addAll(roots);
    onListChanged(name);
  }

  public void removeRoots(String name, List<Object> roots) {
    if (!myName2FavoritesRoots.containsKey(name)) return;
    myName2FavoritesRoots.get(name).removeAll(roots);
    onListChanged(name);
  }

  public void addListener(MPSFavoritesListener listener) {
    myListeners.add(listener);
  }

  public void removeListener(MPSFavoritesListener listener) {
    myListeners.remove(listener);
  }

  public List<Object> getRoots(String name) {
    return myName2FavoritesRoots.get(name);
  }
}
