/*
 * Copyright 2003-2016 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.util;

import jetbrains.mps.library.ModulesMiner;
import jetbrains.mps.project.AbstractModule;
import jetbrains.mps.project.MPSExtentions;
import jetbrains.mps.vfs.FileSystem;
import jetbrains.mps.vfs.IFile;
import jetbrains.mps.vfs.IFileUtils;
import jetbrains.mps.vfs.path.Path;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class MacrosFactory {
  public static final String MODULE = "${module}";
  public static final String PROJECT_LEGACY = "${project}";
  public static final String MPS_HOME = "${mps_home}";

  static final char SEPARATOR_CHAR = Path.UNIX_SEPARATOR_CHAR;

  private MacrosFactory() {
  }

  public static MacroHelper forModuleFile(IFile moduleFile) {
    String[] extensions = new String[]{MPSExtentions.DOT_SOLUTION, MPSExtentions.DOT_LANGUAGE, MPSExtentions.DOT_IDEMODULE, MPSExtentions.PACKAGED_MODULE};
    String name = moduleFile.getPath().toLowerCase();
    for (String ext : extensions) {
      if (name.endsWith(ext)) {
        return new MacroHelperImpl(moduleFile, new ModuleMacros());
      }
    }

    return null;
  }

  public static MacroHelper forModule(AbstractModule module) {
    // todo: if descriptor file == null?
    IFile file = module.getDescriptorFile();
    return file == null ? null : forModuleFile(file);
  }

  public static MacroHelper forProjectFile(IFile projectFile) {
    return new MacroHelperImpl(projectFile, new ProjectMacros());
  }

  public static MacroHelper getGlobal() {
    return new MacroHelperImpl(null, new HomeMacros());
  }

  /**
   * Checks whether {@code path} contains a macro.
   * @param path a non-null string
   * @return {@code true} if {@code path} starts with "${" and contains "}", {@code false} otherwise.
   * FIXME AP contains or equals? Does MacroHelpers and others replace macros in the middle of a path?
   */
  public static boolean containsMacro(@NotNull String path) {
    return path.startsWith("${") && path.contains("}");
  }

  private static class ModuleMacros extends HomeMacros {
    @Override
    protected String expand(String path, IFile anchorFile) {
      if (path.startsWith(MODULE)) {
        IFile anchorFolder = anchorFile.getParent();
        if (anchorFile.toPath().endsWith(ModulesMiner.META_INF_MODULE_XML)) {
          anchorFolder = anchorFolder.getParent();
        }
        String modelRelativePath = removePrefix(path);
        return IFileUtils.getCanonicalPath(anchorFolder.getDescendant(modelRelativePath));
      }

      return super.expand(path, anchorFile);
    }

    @Override
    protected String shrink(String absolutePath, IFile anchorFile) {
      IFile anchorFolder = anchorFile.getParent();
      if (anchorFile.toPath().endsWith(ModulesMiner.META_INF_MODULE_XML)) {
        anchorFolder = anchorFolder.getParent();
      }
      String prefix = IFileUtils.getCanonicalPath(anchorFolder);
      if (pathStartsWith(absolutePath, prefix)) {
        String relationalPath = shrink(absolutePath, prefix);
        return MODULE + relationalPath;
      }
      return super.shrink(absolutePath, anchorFile);
    }
  }

  private static class ProjectMacros extends HomeMacros {
    public static final String PROJECT = "$PROJECT_DIR$";

    @Override
    protected String expand(String path, IFile anchorFile) {
      path = path.replace(PROJECT, PROJECT_LEGACY);
      if (path.contains(PROJECT_LEGACY)) {
        IFile projectDir = getProjectDir(anchorFile);
        String modelRelativePath = removePrefix(path);
        return IFileUtils.getCanonicalPath(projectDir.getDescendant(modelRelativePath));
      }

      return super.expand(path, anchorFile);
    }

    @Override
    protected String shrink(String absolutePath, IFile anchorFile) {
      String prefix = IFileUtils.getCanonicalPath(getProjectDir(anchorFile));

      if (pathStartsWith(absolutePath, prefix)) {
        String relationalPath = shrink(absolutePath, prefix);
        return PROJECT + relationalPath;
      }
      return super.shrink(absolutePath, anchorFile);
    }

    /**
     * Project description is kept either as {project-root}/name.mpr file or as a directory structure, with {project-root}/.mps/modules.xml.
     * Perhaps, this knowledge shall be external to the macro handling code (i.e. ProjectDescriptorPersistence shall care about the way project get persisted),
     * although the fact we are in project-related handling makes the code legitimate, too.
     */
    private static IFile getProjectDir(@NotNull IFile anchorFile) {
      return anchorFile.isDirectory() ? anchorFile : anchorFile.getParent();
    }
  }

  private static class HomeMacros extends Macros {
    @Override
    protected String expand(String path, @Nullable IFile anchorFile) {
      if (path.startsWith(MPS_HOME)) {
        String relativePath = removePrefix(path);
        IFile file = FileSystem.getInstance().getFile(PathManager.getHomePath()).getDescendant(relativePath);
        return IFileUtils.getCanonicalPath(file);
      }

      return super.expand(path, anchorFile);
    }

    @Override
    protected String shrink(String absolutePath, IFile anchorFile) {
      if (pathStartsWith(absolutePath, PathManager.getHomePath())) {
        String relationalPath = shrink(absolutePath, PathManager.getHomePath());
        return MPS_HOME + relationalPath;
      }

      return super.shrink(absolutePath, anchorFile);
    }
  }

}
