/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * 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.android.idegen;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.io.Files;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

/**
 * Build Intellij projects.
 */
public class IntellijProject {

    public static final String FRAMEWORK_MODULE_DIR = "frameworks/base";
    public static final Charset CHARSET = Charset.forName("UTF-8");

    private static final Logger logger = Logger.getLogger(IntellijProject.class.getName());

    private static final String MODULES_TEMPLATE_FILE_NAME = "modules.xml";
    private static final String VCS_TEMPLATE_FILE_NAME = "vcs.xml";

    ModuleCache cache = ModuleCache.getInstance();

    boolean buildFramework;
    File indexFile;
    File projectPath;
    ArrayList<String> moduleDirs;

    public IntellijProject(String indexFile, String projectPath, ArrayList<String> moduleDirs,
            boolean buildFramework) {
        this.indexFile = new File(Preconditions.checkNotNull(indexFile));
        this.projectPath = new File(Preconditions.checkNotNull(projectPath));
        this.moduleDirs = Preconditions.checkNotNull(moduleDirs);
        this.buildFramework = buildFramework;
        DirectorySearch.findAndInitRepoRoot(this.indexFile);
    }

    public void build() throws IOException {
        cache.init(indexFile);
        File repoRoot = DirectorySearch.getRepoRoot();
        if (buildFramework) {
            File frameworkDir = new File(repoRoot, FRAMEWORK_MODULE_DIR);
            // Some unbundled apps/branches do not include the framework.
            if (frameworkDir.exists()) {
                buildFrameWorkModule(new File(repoRoot, FRAMEWORK_MODULE_DIR));
            }
        }

        for (String moduleDir : moduleDirs) {
            // First pass, find all dependencies and cache them.
            File dir = new File(repoRoot, moduleDir);
            if (!dir.exists()) {
                logger.info("Directory " + moduleDir + " does not exist in " + repoRoot +
                        ". Are you sure the directory is correct?");
                return;
            }
            Module module = cache.getAndCacheByDir(dir);
            if (module == null) {
                logger.info("Module '" + dir.getPath() + "' not found." +
                        " Module names are case senstive.");
                return;
            }
        }

        // Finally create iml files for dependencies
        Iterable<Module> modules = cache.getModules();
        for (Module mod : modules) {
            mod.buildImlFile();
        }

        createProjectFiles();
    }

    private void createProjectFiles() throws IOException {
        File ideaDir = new File(projectPath, ".idea");
        ideaDir.mkdirs();
        copyTemplates(ideaDir);
        createModulesFile(ideaDir, cache.getModules());
        createVcsFile(ideaDir, cache.getModules());
        createNameFile(ideaDir, projectPath.getName());
    }

    /**
     * Framework module needs special handling due to one off resource path:
     * frameworks/base/Android.mk
     */
    private void buildFrameWorkModule(File frameworkModuleDir) throws IOException {
        FrameworkModule frameworkModule = new FrameworkModule(frameworkModuleDir);
        frameworkModule.build();
        cache.put(frameworkModule);
    }

    private void createModulesFile(File ideaDir, Iterable<Module> modules) throws IOException {
        String modulesContent = Files.toString(new File(DirectorySearch.findTemplateDir(),
                "idea" + File.separator + MODULES_TEMPLATE_FILE_NAME), CHARSET);
        StringBuilder sb = new StringBuilder();
        for (Module mod : modules) {
            File iml = mod.getImlFile();
            sb.append("      <module fileurl=\"file://").append(iml.getCanonicalPath()).append(
                    "\" filepath=\"").append(iml.getCanonicalPath()).append("\" />\n");
        }
        modulesContent = modulesContent.replace("@MODULES@", sb.toString());

        File out = new File(ideaDir, "modules.xml");
        logger.info("Creating " + out.getCanonicalPath());
        Files.write(modulesContent, out, CHARSET);
    }

    private void createVcsFile(File ideaDir, Iterable<Module> modules) throws IOException {
        String vcsTemplate = Files.toString(new File(DirectorySearch.findTemplateDir(),
                "idea" + File.separator + VCS_TEMPLATE_FILE_NAME), CHARSET);

        Set<String> gitRoots = new HashSet<>();
        for (Module mod : modules) {
            File dir = mod.getDir();
            // Look for git root in the module directory and its parents.
            while (dir != null) {
                File gitRoot = new File(dir, ".git");
                if (gitRoot.exists()) {
                    gitRoots.add(dir.getCanonicalPath());
                    break;
                } else {
                    dir = dir.getParentFile();
                }
            }
        }
        StringBuilder sb = new StringBuilder();
        for (String root : gitRoots) {
            sb.append("    <mapping directory=\"").append(root).append("\" vcs=\"Git\" />\n");
        }

        vcsTemplate = vcsTemplate.replace("@VCS@", sb.toString());
        Files.write(vcsTemplate, new File(ideaDir, "vcs.xml"), CHARSET);
    }

    private void createNameFile(File ideaDir, String name) throws IOException {
        File out = new File(ideaDir, ".name");
        Files.write(name, out, CHARSET);
    }

    private void copyTemplates(File ideaDir) throws IOException {
        File templateDir = DirectorySearch.findTemplateDir();
        copyTemplates(new File(templateDir, "idea"), ideaDir);
    }

    private void copyTemplates(File fromDir, File toDir) throws IOException {
        toDir.mkdir();
        File[] files = fromDir.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                File destDir = new File(toDir, file.getName());
                if (!destDir.exists()) {
                    destDir.mkdirs();
                }
                copyTemplates(file, destDir);
            } else {
                File toFile = new File(toDir, file.getName());
                logger.info("copying " + file.getCanonicalPath() + " to " +
                        toFile.getCanonicalPath());
                Files.copy(file, toFile);
            }
        }
    }

    public static void main(String[] args) {
        logger.info("Args: " + Arrays.toString(args));

        if (args.length < 3) {
            logger.severe("Not enough input arguments. Aborting");
            return;
        }

        boolean buildFramework = true;
        int argIndex = 0;
        String arg = args[argIndex];
        while (arg.startsWith("--")) {
            if  (arg.equals("--no-framework")) {
                buildFramework = false;
            }
            argIndex++;
            arg = args[argIndex];
        }

        String indexFile = args[argIndex++];
        String projectPath = args[argIndex++];
        // Remaining args are module directories
        ArrayList<String> moduleDirs = Lists.newArrayList();
        for (int i = argIndex; i < args.length; i++) {
            moduleDirs.add(args[i]);
        }

        IntellijProject intellij = new IntellijProject(indexFile, projectPath, moduleDirs,
                buildFramework);
        try {
            intellij.build();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
