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

import jetbrains.mps.project.MPSExtentions;
import jetbrains.mps.project.SModuleOperations;
import jetbrains.mps.vfs.FileSystem;
import jetbrains.mps.vfs.IFile;
import org.jetbrains.mps.openapi.module.SModule;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

// FIXME AP refactor
public class ModuleSources {
  private final Map<SModule, ModuleSources> myAvailableSources;
  private Dependencies myDependencies;
  private SModule myModule;
  private Map<String, JavaFile> myJavaFiles = new HashMap<String, JavaFile>();
  private Map<String, ResourceFile> myResourceFiles = new HashMap<String, ResourceFile>();

  private List<File> myFilesToDelete = new ArrayList<File>();
  private List<JavaFile> myFilesToCompile = new LinkedList<JavaFile>();
  private List<ResourceFile> myResourcesToCopy = new LinkedList<ResourceFile>();

  /**
   * @param module Module with JavaModuleFacet
   */
  ModuleSources(SModule module, Dependencies deps) {
    this(module, Collections.<SModule, ModuleSources>emptyMap(), deps);
  }

  /**
   * @param module Module with JavaModuleFacet
   */
  ModuleSources(SModule module, Map<SModule, ModuleSources> availableSources, Dependencies deps) {
    myModule = module;
    myAvailableSources = availableSources;
    myDependencies = deps;

    collectInputFilesInfo();
    collectOutputFilesInfo();
  }

  public Collection<File> getFilesToDelete() {
    return myFilesToDelete;
  }

  public Collection<JavaFile> getFilesToCompile() {
    return myFilesToCompile;
  }

  public Collection<ResourceFile> getResourcesToCopy() {
    return myResourcesToCopy;
  }

  public boolean isUpToDate() {
    return isJavaUpToDate() && isResourcesUpToDate();
  }

  public boolean isJavaUpToDate() {
    return myFilesToCompile.isEmpty();
  }

  public boolean isResourcesUpToDate() {
    return myFilesToDelete.isEmpty() && myResourcesToCopy.isEmpty();
  }


  public JavaFile getJavaFile(String fqName) {
    return myJavaFiles.get(fqName);
  }

  private void collectInputFilesInfo() {
    for (String source : SModuleOperations.getAllSourcePaths(myModule)) {
      File dir = new File(source);
      collectInput(dir, dir.list(), new StringBuilder(), new StringBuilder());
    }
  }

  private void collectInput(File dir, String[] list, StringBuilder path, StringBuilder package_) {
    if (list == null) return;
    int initialLength = path.length();

    for (String childName : list) {
      if (isIgnoredFileName(childName)) continue;

      File child = new File(dir, childName);
      if (childName.endsWith(MPSExtentions.DOT_JAVAFILE)) {
        long lastModified = child.lastModified();
        if (lastModified > 0) {
          String className = childName.substring(0, childName.length() - MPSExtentions.DOT_JAVAFILE.length());
          package_.setLength(initialLength);
          if (initialLength > 0) {
            package_.append('.');
          }
          package_.append(className);
          String fqName = package_.toString();
          myJavaFiles.put(fqName, new JavaFile(child, fqName, lastModified));
          continue;
        }
      }

      String[] subList = child.list();

      if (subList != null) {
        path.setLength(initialLength);
        package_.setLength(initialLength);
        if (initialLength > 0) {
          path.append('/');
          package_.append('.');
        }
        path.append(childName);
        package_.append(childName);
        collectInput(child, subList, path, package_);

      } else if (isResourceFileName(childName)) {
        path.setLength(initialLength);
        if (initialLength > 0) {
          path.append('/');
        }
        path.append(childName);
        String childPath = path.toString();
        myResourceFiles.put(childPath, new ResourceFile(child, childPath));
      }
    }
  }

  private void collectOutputFilesInfo() {
    myFilesToCompile.addAll(myJavaFiles.values());
    myResourcesToCopy.addAll(myResourceFiles.values());

    IFile classesGen = SModuleOperations.getJavaFacet(myModule).getClassesGen();
    if (classesGen == null) return;
    File outputDir = new File(classesGen.getPath());
    collectOutput(outputDir, outputDir.list(), new StringBuilder(), new StringBuilder());
  }

  private boolean isFileUpToDate(JavaFile javaFile, long classFileLastModified) {
    if (javaFile.getLastModified() >= classFileLastModified) {
      return false;
    }

    for (String fqName : myDependencies.getAllDependencies(javaFile.getClassName())) {
      final SModule module = myDependencies.getModule(fqName);
      if (module != null) {
        JavaFile file = myJavaFiles.get(fqName);
        if (file == null) {
          final ModuleSources targetModule = myAvailableSources.get(module);
          if (targetModule != null) {
            file = targetModule.getJavaFile(fqName);
          }
        }
        long javaFileLastModified = file != null ? file.getLastModified() : myDependencies.getJavaFileLastModified(fqName);
        if (javaFileLastModified == 0 || javaFileLastModified > classFileLastModified) {
          return false;
        }
      }
    }
    return true;
  }

  private void collectOutput(File outputDir, String[] files, StringBuilder path, StringBuilder package_) {
    if (files == null) return;
    int initialLength = path.length();

    for (String childName : files) {
      if (isIgnoredFileName(childName)) continue;

      File file = new File(outputDir, childName);
      if (childName.endsWith(MPSExtentions.DOT_CLASSFILE)) {
        boolean isInnerClass = false;
        String containerName = childName.substring(0, childName.length() - MPSExtentions.DOT_CLASSFILE.length());
        int indexOfDollar = containerName.indexOf("$");
        if (indexOfDollar > 0) {
          containerName = containerName.substring(0, indexOfDollar);
          isInnerClass = true;
        }
        package_.setLength(initialLength);
        if (initialLength > 0) {
          package_.append('.');
        }
        package_.append(containerName);
        String fqName = package_.toString();
        JavaFile javaFile = myJavaFiles.get(fqName);
        if (javaFile == null) {
          myFilesToDelete.add(file);
        } else if (!isInnerClass && isFileUpToDate(javaFile, file.lastModified())) {
          myFilesToCompile.remove(javaFile);
        }
        continue;
      }

      String[] subList = file.list();

      if (subList != null) {
        path.setLength(initialLength);
        package_.setLength(initialLength);
        if (initialLength > 0) {
          path.append('/');
          package_.append('.');
        }
        path.append(childName);
        package_.append(childName);
        collectOutput(file, subList, path, package_);

      } else if (isResourceFileName(childName)) {
        path.setLength(initialLength);
        if (initialLength > 0) {
          path.append('/');
        }
        path.append(childName);
        String childPath = path.toString();
        ResourceFile resourceFile = myResourceFiles.get(childPath);
        if (resourceFile == null) {
          myFilesToDelete.add(file);
        } else if (resourceFile.getFile().lastModified() < file.lastModified()) {
          myResourcesToCopy.remove(resourceFile);
        }
      }
    }
  }

  private boolean isIgnoredFileName(String fileName) {
    return FileSystem.getInstance().isFileIgnored(fileName);
  }

  private boolean isResourceFileName(String fileName) {
    int extPos = fileName.lastIndexOf('.');
    return extPos == -1 || extPos > 0 && !fileName.endsWith(MPSExtentions.DOT_JAVAFILE) &&
      !fileName.endsWith(MPSExtentions.DOT_CLASSFILE);
  }
}
