package com.jetbrains.python.documentation;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.intellij.openapi.components.*;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.impl.PyQualifiedName;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author yole
 */
@State(
  name = "PythonDocumentationMap",
  storages = {
  @Storage(
    file = StoragePathMacros.APP_CONFIG + "/other.xml")
    }
)
public class PythonDocumentationMap implements PersistentStateComponent<PythonDocumentationMap.State> {
  public static PythonDocumentationMap getInstance() {
    return ServiceManager.getService(PythonDocumentationMap.class);
  }

  public static class Entry {
    private String myPrefix;
    private String myUrlPattern;

    public Entry() {
    }

    public Entry(String prefix, String urlPattern) {
      myPrefix = prefix;
      myUrlPattern = urlPattern;
    }

    public String getPrefix() {
      return myPrefix;
    }

    public String getUrlPattern() {
      return myUrlPattern;
    }

    public void setPrefix(String prefix) {
      myPrefix = prefix;
    }

    public void setUrlPattern(String urlPattern) {
      myUrlPattern = urlPattern;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      Entry entry = (Entry)o;

      if (!myPrefix.equals(entry.myPrefix)) return false;
      if (!myUrlPattern.equals(entry.myUrlPattern)) return false;

      return true;
    }

    @Override
    public int hashCode() {
      int result = myPrefix.hashCode();
      result = 31 * result + myUrlPattern.hashCode();
      return result;
    }
  }

  public static class State {
    private List<Entry> myEntries = new ArrayList<Entry>();

    public State() {
      addEntry("PyQt4", "http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/{class.name.lower}.html#{function.name}");
      addEntry("PySide", "http://www.pyside.org/docs/pyside/{module.name.slashes}/{class.name}.html#{module.name}.{element.qname}");
      addEntry("gtk", "http://library.gnome.org/devel/pygtk/stable/class-gtk{class.name.lower}.html#method-gtk{class.name.lower}--{function.name.dashes}");
      addEntry("wx", "http://www.wxpython.org/docs/api/{module.name}.{class.name}-class.html#{function.name}");
      addEntry("numpy", "http://docs.scipy.org/doc/numpy/reference/{}generated/{module.name}.{element.name}.html");
      addEntry("scipy", "http://docs.scipy.org/doc/scipy/reference/{}generated/{module.name}.{element.name}.html");
      addEntry("kivy", "http://kivy.org/docs/api-{module.name}.html");
    }

    public List<Entry> getEntries() {
      return myEntries;
    }

    public void setEntries(List<Entry> entries) {
      myEntries = entries;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      State state = (State)o;
      return Sets.newHashSet(myEntries).equals(Sets.newHashSet(state.getEntries()));
    }

    @Override
    public int hashCode() {
      return myEntries != null ? myEntries.hashCode() : 0;
    }

    private void addEntry(String qName, String pattern) {
      myEntries.add(new Entry(qName, pattern));
    }
  }

  private State myState = new State();

  @Override
  public State getState() {
    return myState;
  }

  @Override
  public void loadState(State state) {
    myState = state;
  }

  public List<Entry> getEntries() {
    return ImmutableList.copyOf(myState.getEntries());
  }

  public void setEntries(List<Entry> entries) {
    myState.setEntries(entries);
  }

  @Nullable
  public String urlFor(PyQualifiedName moduleQName, @Nullable PsiNamedElement element, String pyVersion) {
    for (Entry entry : myState.myEntries) {
      if (moduleQName.matchesPrefix(PyQualifiedName.fromDottedString(entry.myPrefix))) {
        return transformPattern(entry.myUrlPattern, moduleQName, element, pyVersion);
      }
    }
    return null;
  }

  @Nullable
  public String rootUrlFor(PyQualifiedName moduleQName) {
    for (Entry entry : myState.myEntries) {
      if (moduleQName.matchesPrefix(PyQualifiedName.fromDottedString(entry.myPrefix))) {
        return rootForPattern(entry.myUrlPattern);
      }
    }
    return null;
  }

  private static String rootForPattern(String urlPattern) {
    int pos = urlPattern.indexOf('{');
    return pos >= 0 ? urlPattern.substring(0, pos) : urlPattern;
  }

  @Nullable
  private static String transformPattern(@NotNull String urlPattern, PyQualifiedName moduleQName, @Nullable PsiNamedElement element,
                                         String pyVersion) {
    Map<String, String> macros = new HashMap<String, String>();
    macros.put("element.name", element == null ? null : element.getName());
    PyClass pyClass = element == null ? null : PsiTreeUtil.getParentOfType(element, PyClass.class, false);
    macros.put("class.name", pyClass == null ? null : pyClass.getName());
    if (element != null) {
      StringBuilder qName = new StringBuilder(moduleQName.toString()).append(".");
      if (element instanceof PyFunction && ((PyFunction)element).getContainingClass() != null) {
        qName.append(((PyFunction)element).getContainingClass().getName()).append(".");
      }
      qName.append(element.getName());
      macros.put("element.qname", qName.toString());
    }
    else {
      macros.put("element.qname", "");
    }
    macros.put("function.name", element instanceof PyFunction ? element.getName() : "");
    macros.put("module.name", moduleQName.toString());
    macros.put("python.version", pyVersion);
    final String pattern = transformPattern(urlPattern, macros);
    if (pattern == null) {
      return rootForPattern(urlPattern);
    }
    return pattern;
  }

  @Nullable
  private static String transformPattern(@NotNull String urlPattern, Map<String, String> macroValues) {
    for (Map.Entry<String, String> entry : macroValues.entrySet()) {
      if (entry.getValue() == null) {
        if (urlPattern.contains("{" + entry.getKey())) {
          return null;
        }
        continue;
      }
      urlPattern = urlPattern
        .replace("{" + entry.getKey() + "}", entry.getValue())
        .replace("{" + entry.getKey() + ".lower}", entry.getValue().toLowerCase())
        .replace("{" + entry.getKey() + ".slashes}", entry.getValue().replace(".", "/"))
        .replace("{" + entry.getKey() + ".dashes}", entry.getValue().replace("_", "-"));

    }
    return urlPattern.replace("{}", "");
  }
}

@@ -2280,34 +2280,39 @@
 p://
-www.pyside.org/docs
+srinikom.github.com
 /pyside
+-docs
 /%7Bmo

change pyside docs url (PY-7819)