/*
 * 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.reloading;

import jetbrains.mps.util.FlattenIterable;
import jetbrains.mps.util.iterable.IterableEnumeration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

public class CompositeClassPathItem extends AbstractClassPathItem {
  private List<IClassPathItem> myChildren = new ArrayList<IClassPathItem>();

  public void add(IClassPathItem item) {
    assert item != null;
    myChildren.add(item);
  }

  @Override
  public boolean hasClass(String name) {
    for (IClassPathItem item : myChildren) {
      if (item.hasClass(name)) {
        return true;
      }
    }
    return false;
  }

  @Override
  public boolean hasPackage(@NotNull String name) {
    for (IClassPathItem item : myChildren) {
      if (item.hasPackage(name)) {
        return true;
      }
    }
    return false;
  }

  @Nullable
  @Override
  public ClassBytes getClassBytes(String name) {
    for (IClassPathItem item : myChildren) {
      ClassBytes result = item.getClassBytes(name);
      if (result != null) return result;
    }
    return null;
  }

  @Override
  public URL getResource(String name) {
    for (IClassPathItem item : myChildren) {
      URL resource = item.getResource(name);
      if (resource != null) {
        return resource;
      }
    }
    return null;
  }

  @Override
  public Enumeration<URL> getResources(String name) {
    List<URL> result = new ArrayList<URL>();
    for (IClassPathItem item : myChildren) {
      Enumeration<URL> resources = item.getResources(name);
      while (resources.hasMoreElements()) {
        result.add(resources.nextElement());
      }
    }
    return new IterableEnumeration<URL>(result);
  }

  @Override
  public Iterable<String> getAvailableClasses(String namespace) {
    FlattenIterable<String> result = new FlattenIterable<String>();
    for (IClassPathItem item : myChildren) {
      //todo rewrite using mapping iterable
      result.add(item.getAvailableClasses(namespace));
    }
    return result;
  }

  @Override
  public Iterable<String> getSubpackages(String namespace) {
    FlattenIterable<String> result = new FlattenIterable<String>();
    for (IClassPathItem item : myChildren) {
      //todo rewrite using mapping iterable
      result.add(item.getSubpackages(namespace));
    }
    return result;
  }

  public List<IClassPathItem> getChildren() {
    return new ArrayList<IClassPathItem>(myChildren);
  }

  @Override
  public List<RealClassPathItem> flatten() {
    List<RealClassPathItem> result = new ArrayList<RealClassPathItem>();

    for (IClassPathItem child : myChildren) {
      result.addAll(child.flatten());
    }

    return result;
  }

  @Override
  public CompositeClassPathItem optimize() {
    List<RealClassPathItem> flattenedItems = flatten();
    Iterator<RealClassPathItem> it = flattenedItems.iterator();

    Set<String> alreadyVisited = new HashSet<String>();

    while (it.hasNext()) {
      IClassPathItem item = it.next();
      if (item instanceof FileClassPathItem) {
        FileClassPathItem fcp = (FileClassPathItem) item;
        if (alreadyVisited.contains(fcp.getPath())) {
          it.remove();
        } else {
          alreadyVisited.add(fcp.getPath());
        }
      }

      if (item instanceof JarFileClassPathItem) {
        JarFileClassPathItem jfcp = (JarFileClassPathItem) item;
        String path = jfcp.getFile().getAbsolutePath();
        if (alreadyVisited.contains(path)) {
          it.remove();
        } else {
          alreadyVisited.add(path);
        }
      }
    }

    CompositeClassPathItem result = new CompositeClassPathItem();
    for (IClassPathItem item : flattenedItems) {
      result.add(item);
    }

    return result;
  }

  @Override
  public void accept(IClassPathItemVisitor visitor) {
    visitor.visit(this);
  }

  public String toString() {
    StringBuilder result = new StringBuilder("classpath {\n");

    for (IClassPathItem child : myChildren) {
      for (String s : child.toString().split("/[\n]/")) {
        result.append('\t').append(s).append("\n");
      }
    }

    result.append("}");
    return result.toString();
  }
}
