/*
 * Copyright 2000-2013 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 com.intellij.psi.util;

import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.stubs.StubInputStream;
import com.intellij.psi.stubs.StubOutputStream;
import com.intellij.util.io.StringRef;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

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

/**
 * @author yole
 */
public class QualifiedName implements Comparable<QualifiedName> {
  @NotNull private final List<String> myComponents;

  private QualifiedName(int count) {
    myComponents = new ArrayList<>(count);
  }

  public static QualifiedName fromComponents(Collection<String> components) {
    for (String component : components) {
      assertNoDots(component);
    }
    QualifiedName qName = new QualifiedName(components.size());
    qName.myComponents.addAll(components);
    return qName;
  }

  @NotNull
  public static QualifiedName fromComponents(String... components) {
    for (String component : components) {
      assertNoDots(component);
    }
    QualifiedName result = new QualifiedName(components.length);
    Collections.addAll(result.myComponents, components);
    return result;
  }

  public QualifiedName append(String name) {
    QualifiedName result = new QualifiedName(myComponents.size()+1);
    result.myComponents.addAll(myComponents);
    result.myComponents.add(name);
    return result;
  }

  public QualifiedName append(QualifiedName qName) {
    QualifiedName result = new QualifiedName(myComponents.size()+qName.getComponentCount());
    result.myComponents.addAll(myComponents);
    result.myComponents.addAll(qName.getComponents());
    return result;
  }

  @NotNull
  public QualifiedName removeLastComponent() {
    return removeTail(1);
  }

  @NotNull
  public QualifiedName removeTail(int count) {
    int size = myComponents.size();
    QualifiedName result = new QualifiedName(size);
    result.myComponents.addAll(myComponents);
    for (int i = 0; i < count && !result.myComponents.isEmpty(); i++) {
      result.myComponents.remove(result.myComponents.size()-1);
    }
    return result;
  }

  @NotNull
  public QualifiedName removeHead(int count) {
    int size = myComponents.size();
    QualifiedName result = new QualifiedName(size);
    result.myComponents.addAll(myComponents);
    for (int i = 0; i < count && !result.myComponents.isEmpty(); i++) {
      result.myComponents.remove(0);
    }
    return result;
  }

  @NotNull
  public List<String> getComponents() {
    return myComponents;
  }

  public int getComponentCount() {
    return myComponents.size();
  }

  public boolean matches(String... components) {
    if (myComponents.size() != components.length) {
      return false;
    }
    for (int i = 0; i < myComponents.size(); i++) {
      if (!myComponents.get(i).equals(components[i])) {
        return false;
      }
    }
    return true;
  }

  public boolean matchesPrefix(QualifiedName prefix) {
    if (getComponentCount() < prefix.getComponentCount()) {
      return false;
    }
    for (int i = 0; i < prefix.getComponentCount(); i++) {
      final String component = getComponents().get(i);
      if (component == null || !component.equals(prefix.getComponents().get(i))) {
        return false;
      }
    }
    return true;
  }

  public boolean endsWith(@NotNull String suffix) {
    return suffix.equals(getLastComponent());
  }

  public static void serialize(@Nullable QualifiedName qName, StubOutputStream dataStream) throws IOException {
    if (qName == null) {
      dataStream.writeVarInt(0);
    }
    else {
      dataStream.writeVarInt(qName.getComponentCount());
      for (String s : qName.myComponents) {
        dataStream.writeName(s);
      }
    }
  }

  @Nullable
  public static QualifiedName deserialize(StubInputStream dataStream) throws IOException {
    QualifiedName qName;
    int size = dataStream.readVarInt();
    if (size == 0) {
      qName = null;
    }
    else {
      qName = new QualifiedName(size);
      for (int i = 0; i < size; i++) {
        final StringRef name = dataStream.readName();
        qName.myComponents.add(name == null ? null : name.getString());
      }
    }
    return qName;
  }

  @Nullable
  public String getFirstComponent() {
    if (myComponents.isEmpty()) {
      return null;
    }
    return myComponents.get(0);
  }

  @Nullable
  public String getLastComponent() {
    if (myComponents.isEmpty()) {
      return null;
    }
    return myComponents.get(myComponents.size()-1);
  }

  @Override
  public String toString() {
    return join(".");
  }

  public String join(final String separator) {
    return StringUtil.join(myComponents, separator);
  }

  @NotNull
  public static QualifiedName fromDottedString(@NotNull String refName) {
    return fromComponents(refName.split("\\."));
  }

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

  @Override
  public int hashCode() {
    return myComponents.hashCode();
  }

  public QualifiedName subQualifiedName(int fromIndex, int toIndex) {
    return fromComponents(myComponents.subList(fromIndex, toIndex));
  }

  @Override
  public int compareTo(@NotNull QualifiedName other) {
    return toString().compareTo(other.toString());
  }

  private static void assertNoDots(@NotNull String component) {
    if (component.contains(".")) {
      throw new IllegalArgumentException("Components of QualifiedName cannot contain dots inside them, but got: " + component);
    }
  }
}
