/*
 * Copyright 2003-2015 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.generator.impl.dependencies;

import org.jdom.Attribute;
import org.jdom.Element;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SNode;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * FIXME if we'd like to keep using this object, shall refactor all storage from string to native objects (e.g. SModelReference, SNodeId, etc)
 * Strings consume quite a lot of memory on startup
 * Evgeny Gryaznov, Jun 1, 2010
 */
public class GenerationRootDependencies {

  private static final String ATTR_ID = "id";
  private static final String ATTR_NAME = "name";
  private static final String ATTR_HASH = "hash";
  private static final String ATTR_DEPENDS_ON_CONDITIONALS = "dependsOnCreated";
  private static final String ATTR_DEPENDS_ON_NODES = "dependsOnNodes";
  private static final String NODE_DEPENDS_ON = "dep";
  private static final String NODE_FILE = "file";
  private static final String ATTR_ROOT_ID = "root";
  private static final String ATTR_MODEL_ID = "model";
  private static final String TRUE = "true";

  private String myRootId;
  private String myRootName;
  private String myHash;
  private boolean myDependsOnConditionals;
  private boolean myDependsOnNodes;
  private List<String> myLocal;
  private List<String> myExternal;
  private List<String> myGeneratedFiles;

  public GenerationRootDependencies(String rootId, String rootName, String rootHash, boolean dependsOnConditionals, boolean dependsOnNodes, List<String> local, List<String> external, List<String> files) {
    this.myRootId = rootId;
    this.myRootName = rootName;
    this.myHash = rootHash;
    this.myDependsOnConditionals = dependsOnConditionals;
    this.myDependsOnNodes = dependsOnNodes;
    this.myLocal = local;
    this.myExternal = external;
    this.myGeneratedFiles = files;
  }

  public String getRootId() {
    return myRootId;
  }

  public String getRootName() {
    return myRootName;
  }

  public String getHash() {
    return myHash;
  }

  public boolean isDependsOnConditionals() {
    return myDependsOnConditionals;
  }

  public boolean isDependsOnNodes() {
    return myDependsOnNodes;
  }

  public List<String> getLocal() {
    return Collections.unmodifiableList(myLocal);
  }

  public List<String> getExternal() {
    return Collections.unmodifiableList(myExternal);
  }

  public List<String> getFiles() {
    return myGeneratedFiles == null ? Collections.<String>emptyList() : Collections.unmodifiableList(myGeneratedFiles);
  }

  public void addGeneratedFile(String name) {
    if (name == null) {
      throw new IllegalArgumentException("name is null");
    }
    if (myGeneratedFiles == null) {
      myGeneratedFiles = new ArrayList<String>(2);
    }
    if (!myGeneratedFiles.contains(name)) {
      myGeneratedFiles.add(name);
    }
  }

  public void saveTo(Element element) {
    if (myRootId != null) {
      element.setAttribute(ATTR_ID, this.myRootId);
    }
    if (myRootName != null) {
      element.setAttribute(ATTR_NAME, this.myRootName);
    }
    if (myHash != null) {
      element.setAttribute(ATTR_HASH, this.myHash);
    }
    if (myDependsOnConditionals) {
      element.setAttribute(ATTR_DEPENDS_ON_CONDITIONALS, TRUE);
    }
    if (myDependsOnNodes) {
      element.setAttribute(ATTR_DEPENDS_ON_NODES, TRUE);
    }
    for (String id : myLocal) {
      Element node = new Element(NODE_DEPENDS_ON);
      node.setAttribute(ATTR_ROOT_ID, id);
      element.addContent(node);
    }
    for (String id : myExternal) {
      Element node = new Element(NODE_DEPENDS_ON);
      node.setAttribute(ATTR_MODEL_ID, id);
      element.addContent(node);
    }
    if (myGeneratedFiles != null) {
      Collections.sort(myGeneratedFiles);
      for (String file : myGeneratedFiles) {
        Element node = new Element(NODE_FILE);
        node.setAttribute(ATTR_NAME, file);
        element.addContent(node);
      }
    }
  }

  public static String getValue(Element e, String attrId) {
    Attribute attr = e.getAttribute(attrId);
    return attr == null ? null : attr.getValue();
  }

  public static GenerationRootDependencies fromXml(Element element, boolean isCommon, Intern intern) {
    String rootId = isCommon ? null : intern.value(getValue(element, ATTR_ID));
    String rootName = getValue(element, ATTR_NAME);
    String rootHash = getValue(element, ATTR_HASH);
    boolean dependsOnConditionals = "true".equals(getValue(element, ATTR_DEPENDS_ON_CONDITIONALS));
    boolean dependsOnNodes = "true".equals(getValue(element, ATTR_DEPENDS_ON_NODES));
    ArrayList<String> local = new ArrayList<String>();
    ArrayList<String> external = new ArrayList<String>();
    ArrayList<String> files = new ArrayList<String>();
    for (Element e : element.getChildren(NODE_DEPENDS_ON)) {
      Attribute attr = e.getAttribute(ATTR_ROOT_ID);
      if (attr != null) {
        local.add(intern.value(attr.getValue()));
      } else if ((attr = e.getAttribute(ATTR_MODEL_ID)) != null) {
        external.add(intern.value(attr.getValue()));
      }
    }
    for (Element e : element.getChildren(NODE_FILE)) {
      String v = getValue(e, ATTR_NAME);
      if (v != null) {
        files.add(intern.value(v));
      }
    }
    local.trimToSize();
    external.trimToSize();
    files.trimToSize();
    return new GenerationRootDependencies(rootId, rootName, rootHash, dependsOnConditionals, dependsOnNodes, local, external, files);
  }

  public static GenerationRootDependencies fromData(RootDependenciesBuilder l) {
    final Collection<SNode> localNodes = l.getDependsOn();
    final Collection<SModel> externalModels = l.getDependsOnModels();

    List<String> local = new ArrayList<String>(localNodes.size());
    for (SNode n : localNodes) {
      local.add(n.getNodeId().toString());
    }
    Collections.sort(local);

    List<String> external = new ArrayList<String>(externalModels.size());
    for (SModel m : externalModels) {
      final SModelReference modelRef = m.getReference();
      external.add(modelRef.toString());
    }
    Collections.sort(external);

    final SNode originalRoot = l.getOriginalRoot();
    return new GenerationRootDependencies(
      originalRoot != null ? originalRoot.getNodeId().toString() : null,
      originalRoot != null ? originalRoot.getName() : null,
      l.getHash(), l.isDependsOnConditionals(), l.isDependsOnModelNodes(),
      local, external, null);
  }
}
