/*
 Copyright (c) 2009-2013 Olivier Chafik, All Rights Reserved
	
 This file is part of JNAerator (http://jnaerator.googlecode.com/).
	
 JNAerator is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
	
 JNAerator is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
	
 You should have received a copy of the GNU General Public License
 along with JNAerator.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.ochafik.admin.visualstudio;

import java.io.File;
import java.io.FileFilter;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.ochafik.io.ReadText;

public class Solution {

    private static final boolean verbose = true;
    public File solutionFile;
    public Map<String, Project> idToProject;
    public Set<File> allFiles = new HashSet<File>(10000);
    static Pattern solutionIdPattern = Pattern.compile("Project\\(\"\\{[^}]+\\}\"\\) = \"([^\"]+)\", \"([^\"]+)\", [^{]+\\{([^}]+)\\}\""
            + ".*?ProjectSection\\(ProjectDependencies\\)(.*?)EndProjectSection", Pattern.MULTILINE);
    static Pattern projectDependencyPattern = Pattern.compile("\\{[^}]+\\} = \\{([^}]+)\\}");
    static Pattern projectConfigsForSolutionConfigPattern = Pattern.compile("^\\s*\\{([^}]+)\\}\\.([^\\.]+)\\.ActiveCfg\\s*=\\s*(.*)\\s*$");

    public Solution(File solutionFile) {
        this.solutionFile = solutionFile;
    }

    public void parse() throws Exception {
        if (idToProject == null) {
            idToProject = new LinkedHashMap<String, Project>();

            String solutionContent = ReadText.readText(solutionFile);
            File solutionPath = solutionFile.getParentFile();

            for (Matcher solutionIdMatcher = solutionIdPattern.matcher(solutionContent.replace('\n', ' ')); solutionIdMatcher.find();) {
                Project p = new Project(this, relFile(solutionPath, solutionIdMatcher.group(2)), solutionIdMatcher.group(1), solutionIdMatcher.group(3));
                for (Matcher depM = projectDependencyPattern.matcher(solutionIdMatcher.group(4)); depM.find();) {
                    p.depsIds.add(depM.group(1));
                }
                idToProject.put(p.id, p);
            }

            for (Matcher m = projectConfigsForSolutionConfigPattern.matcher(solutionContent); m.find();) {
                String id = m.group(1), slnConfigName = m.group(2), projConfigName = m.group(3);
                Project p = idToProject.get(id);
                if (p == null) {
                    continue;
                }

                p.activeConfigurationNameBySolutionConfigurationName.put(slnConfigName, projConfigName);
            }
        }
    }

    public String getName() {
        return solutionFile == null ? null : solutionFile.getName().replaceAll("\\.[^.]+$", "");
    }

    public void parseProjects(final FileFilter fileFilter) throws Exception {
        parse();
        int nThreads = Runtime.getRuntime().availableProcessors() * 2;
        if (verbose) {
            System.out.println("Parsing Solution: " + solutionFile.getName() + " (" + nThreads + " threads)");
        }

        final Semaphore semaphore = new Semaphore(0);
        final LinkedList<Project> projects = new LinkedList<Project>(idToProject.values());
        class Worker extends Thread {

            public void run() {
                for (;;) {
                    try {
                        Project project = null;
                        synchronized (projects) {
                            if (projects.isEmpty()) {
                                break;
                            }

                            project = projects.removeLast();
                        }
                        if (verbose) {
                            synchronized (System.out) {
                                System.out.println("Parsing Project: " + project.name);
                            }
                        }

                        project.parse(fileFilter, false, Solution.this);
                        synchronized (allFiles) {
                            allFiles.addAll(project.files);
                        }
                    } catch (Exception ex) {
                        synchronized (System.err) {
                            ex.printStackTrace(System.err);
                        }

                    }
                }
                semaphore.release();
            }
        };
        for (int i = nThreads; i-- != 0;) {
            new Worker().start();
        }

        semaphore.acquire(nThreads);
    }

    static File relFile(File base, String relPath) {
        relPath = relPath.replace('\\', File.separatorChar).replace('/', File.separatorChar);
        return new File(base, relPath);
    }

    public Collection<Project> getProjects() {
        return idToProject.values();
    }
}
