/*
 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 Lesser 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 Lesser General Public License for more details.
	
 You should have received a copy of the GNU Lesser General Public License
 along with JNAerator.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.ochafik.lang.jnaerator;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import com.ochafik.admin.visualstudio.Configuration;
import com.ochafik.admin.visualstudio.Project;
import com.ochafik.admin.visualstudio.Solution;
import com.ochafik.admin.visualstudio.VisualStudioUtils;
import com.ochafik.lang.jnaerator.parser.Function;
import com.ochafik.lang.jnaerator.parser.Modifier;
import com.ochafik.lang.jnaerator.parser.ModifierType;
import com.ochafik.util.SystemUtils;
import com.ochafik.util.listenable.Adapter;
import com.ochafik.util.string.RegexUtils;
import com.ochafik.util.string.StringUtils;

public class JNAeratorConfigUtils {

    public static Logger logger = Logger.getLogger(JNAeratorConfigUtils.class.getName());

    public static final class MSC_VER {

        public final int VC4 = 1000,
                VC5 = 1100,
                VC6 = 1200,
                VC70 = 1300,
                VC71 = 1310;
    }

    static String getProp(String name, String defVal, boolean verbose) {
        String v = System.getenv(name);
        v = v == null ? System.getProperty(name, defVal) : v;
        if (verbose) {
            logger.log(Level.INFO, "[environment] " + name + "=" + v);
        }
        return v;
    }
    //this.frameworkspath = new ArrayList<String>(Arrays.asList(new String[] {"/System/Library/Frameworks/", "/Library/Frameworks/", "/Local/Library/Frameworks/"})); 
    static List<String> DEFAULT_FRAMEWORKS_PATH = Arrays.asList(
            "/System/Library/Frameworks/CoreServices.framework/Versions/Current/Frameworks",
            "/System/Library/Frameworks/ApplicationServices.framework/Versions/Current/Frameworks",
            "/System/Library/Frameworks",
            "/Library/Frameworks",
            "/Local/Library/Frameworks/",
            System.getProperty("user.home") + "/Library/Frameworks");
    static List<String> DEFAULT_INCLUDE_PATH;

    static {
        if (SystemUtils.isMacOSX()) {
            DEFAULT_INCLUDE_PATH = new ArrayList<String>();
            for (String s : new String[]{
                "/Developer/SDKs/MacOSX10.5.sdk/usr/include",
                "/Developer/SDKs/MacOSX10.4u.sdk/usr/include"
            }) {
                if (new File(s).exists()) {
                    DEFAULT_INCLUDE_PATH.add(s);
                    break;
                }
            }

            DEFAULT_INCLUDE_PATH.add(".");
        } else if (SystemUtils.isWindows()) {
            ArrayList<String> list = new ArrayList<String>(VisualStudioUtils.getMicrosoftIncludes());
            list.add(".");
            DEFAULT_INCLUDE_PATH = list;
        } else {
            DEFAULT_INCLUDE_PATH = new ArrayList<String>(Arrays.asList("."));
        }
        if (SystemUtils.isUnix()) {
            DEFAULT_INCLUDE_PATH.add("/usr/include");
            DEFAULT_INCLUDE_PATH.add("/usr/local/include");

            /*
             * /usr/include/c++ is likely to contain directories with versions such as 4.0.0, 4.4.0...
             * We try to take the greatest version (in lexicographic order of matching dir names) and check that it contains "new" and "map" files
             */
            File cppi = new File("/usr/include/c++");
            TreeSet<String> versions = new TreeSet<String>();
            if (cppi.isDirectory()) {
                for (File f : cppi.listFiles()) {
                    if (!f.isDirectory()) {
                        continue;
                    }
                    String n = f.getName();
                    if (!n.matches("[\\d+](\\.[\\d+])*")) {
                        continue;
                    }
                    if (!(new File(f, "new").exists() && new File(f, "map").exists())) {
                        continue;
                    }
                    versions.add(n);
                }
            }
            if (!versions.isEmpty()) {
                File d = new File(cppi, versions.last());
                DEFAULT_INCLUDE_PATH.add(d.toString());
                DEFAULT_INCLUDE_PATH.add(new File(d, "tr1").toString());
            }
        }
        DEFAULT_INCLUDE_PATH = Collections.unmodifiableList(DEFAULT_INCLUDE_PATH);
    }

    public static class FileExtensionFilter implements FileFilter {

        final Collection<String> allowedExtensions;

        public FileExtensionFilter(Collection<String> allowedExtensions) {
            this.allowedExtensions = allowedExtensions;
        }

        public FileExtensionFilter(String[] split) {
            this(Arrays.asList(split));
        }

        public boolean accept(File file) {
            String name = file.getName().toLowerCase();
            int i = name.lastIndexOf('.');
            return accept(file, i > 0 ? name.substring(i + 1) : "");
        }

        public boolean accept(File file, String extension) {
            return allowedExtensions.contains(extension);
        }
    }

    public static void addCPlusPlus(JNAeratorConfig.PreprocessorConfig config) {
        config.implicitMacros.put("__cplusplus", null);
    }

    public static void addGCCPredefinedMacros(JNAeratorConfig.PreprocessorConfig config) {
        //gcc -dM -E - < /dev/null
        config.implicitMacros.put("__DBL_MIN_EXP__", "(-1021)");
        config.implicitMacros.put("__FLT_MIN__", "1.17549435e-38F");
        config.implicitMacros.put("__CHAR_BIT__", "8");
        config.implicitMacros.put("__WCHAR_MAX__", "2147483647");
        config.implicitMacros.put("__DBL_DENORM_MIN__", "4.9406564584124654e-324");
        config.implicitMacros.put("__FLT_EVAL_METHOD__", "0");
        config.implicitMacros.put("__DBL_MIN_10_EXP__", "(-307)");
        config.implicitMacros.put("__FINITE_MATH_ONLY__", "0");
        config.implicitMacros.put("__SHRT_MAX__", "32767");
        config.implicitMacros.put("__LDBL_MAX__", "1.18973149535723176502e+4932L");
        if (com.sun.jna.Platform.isMac()) {
            config.implicitMacros.put("__APPLE_CC__", "5484");
            config.implicitMacros.put("__MACH__", "1");
            config.implicitMacros.put("__APPLE__", "1");
        }
        config.implicitMacros.put("__UINTMAX_TYPE__", "long long unsigned int");
        config.implicitMacros.put("__SCHAR_MAX__", "127");
        config.implicitMacros.put("__USER_LABEL_PREFIX__", "_");
        config.implicitMacros.put("__STDC_HOSTED__", "1");
        config.implicitMacros.put("__DBL_DIG__", "15");
        config.implicitMacros.put("__FLT_EPSILON__", "1.19209290e-7F");
        config.implicitMacros.put("__LDBL_MIN__", "3.36210314311209350626e-4932L");
        config.implicitMacros.put("__strong", "");
        config.implicitMacros.put("__DECIMAL_DIG__", "21");
        config.implicitMacros.put("__LDBL_HAS_QUIET_NAN__", "1");
        config.implicitMacros.put("__DYNAMIC__", "1");
//		config.implicitMacros.put("__GNUC__", "4");
//		config.implicitMacros.put("__MMX__", "1");
        config.implicitMacros.put("__DBL_MAX__", "1.7976931348623157e+308");
        config.implicitMacros.put("__DBL_HAS_INFINITY__", "1");
        config.implicitMacros.put("OBJC_NEW_PROPERTIES", "1");
        config.implicitMacros.put("__weak", "");
        config.implicitMacros.put("__DBL_MAX_EXP__", "1024");
//		config.implicitMacros.put("__SSE2_MATH__", "1");
        config.implicitMacros.put("__LONG_LONG_MAX__", "9223372036854775807LL");
        config.implicitMacros.put("__GXX_ABI_VERSION", "1002");
        config.implicitMacros.put("__FLT_MIN_EXP__", "(-125)");
        config.implicitMacros.put("__DBL_MIN__", "2.2250738585072014e-308");
        config.implicitMacros.put("__DBL_HAS_QUIET_NAN__", "1");
        config.implicitMacros.put("__REGISTER_PREFIX__", "");
        config.implicitMacros.put("__NO_INLINE__", "1");
//		config.implicitMacros.put("__i386", "1");
        config.implicitMacros.put("__FLT_MANT_DIG__", "24");
        config.implicitMacros.put("__VERSION__", "\"4.0.1 (Apple Inc. build 5484)\"");
//		config.implicitMacros.put("i386", "1");
//		config.implicitMacros.put("__i386__", "1");
        config.implicitMacros.put("__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__", "1055");
        config.implicitMacros.put("__SIZE_TYPE__", "long unsigned int");
        config.implicitMacros.put("__FLT_RADIX__", "2");
        config.implicitMacros.put("__LDBL_EPSILON__", "1.08420217248550443401e-19L");
//		config.implicitMacros.put("__SSE_MATH__", "1");
        config.implicitMacros.put("__FLT_HAS_QUIET_NAN__", "1");
        config.implicitMacros.put("__FLT_MAX_10_EXP__", "38");
        config.implicitMacros.put("__LONG_MAX__", "2147483647L");
        config.implicitMacros.put("__FLT_HAS_INFINITY__", "1");

        config.implicitMacros.put("__LDBL_MANT_DIG__", "64");
        config.implicitMacros.put("__CONSTANT_CFSTRINGS__", "1");
        config.implicitMacros.put("__WCHAR_TYPE__", "int");
        config.implicitMacros.put("__FLT_DIG__", "6");
        config.implicitMacros.put("__INT_MAX__", "2147483647");
        config.implicitMacros.put("__FLT_MAX_EXP__", "128");
        config.implicitMacros.put("__DBL_MANT_DIG__", "53");
        config.implicitMacros.put("__WINT_TYPE__", "int");
//		config.implicitMacros.put("__SSE__", "1");
        config.implicitMacros.put("__LDBL_MIN_EXP__", "(-16381)");
        config.implicitMacros.put("__LDBL_MAX_EXP__", "16384");
        config.implicitMacros.put("__LDBL_MAX_10_EXP__", "4932");
        config.implicitMacros.put("__DBL_EPSILON__", "2.2204460492503131e-16");
        config.implicitMacros.put("__LDBL_HAS_INFINITY__", "1");
        config.implicitMacros.put("__INTMAX_MAX__", "9223372036854775807LL");
        config.implicitMacros.put("__FLT_DENORM_MIN__", "1.40129846e-45F");
        config.implicitMacros.put("__PIC__", "1");
        config.implicitMacros.put("__FLT_MAX__", "3.40282347e+38F");
//		config.implicitMacros.put("__SSE2__", "1");
        config.implicitMacros.put("__FLT_MIN_10_EXP__", "(-37)");
        config.implicitMacros.put("__INTMAX_TYPE__", "long long int");
        config.implicitMacros.put("__GNUC_MINOR__", "0");
        config.implicitMacros.put("__DBL_MAX_10_EXP__", "308");
        config.implicitMacros.put("__LDBL_DENORM_MIN__", "3.64519953188247460253e-4951L");
//		config.implicitMacros.put("__PTRDIFF_TYPE__", "int");
        config.implicitMacros.put("__LDBL_MIN_10_EXP__", "(-4931)");
        config.implicitMacros.put("__LDBL_DIG__", "18");
//		config.implicitMacros.put("__GNUC_GNU_INLINE__", "1");
    }

    static void defaultMacro(JNAeratorConfig config, String name, String value) {
        if (!config.preprocessorConfig.implicitMacros.containsKey(name)) {
            config.preprocessorConfig.implicitMacros.put(name, value);
        }
    }

    /**
     * TODO move this to a .h resource file
     */
    public static void autoConfigure(final JNAeratorConfig config) {
        if (config.runtime == JNAeratorConfig.Runtime.BridJ) {
            config.genCPlusPlus = true;
        }

        if (!config.noCPlusPlus) {
            addCPlusPlus(config.preprocessorConfig);
        }

        if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) {
            defaultMacro(config, "__BIG_ENDIAN__", "1");
            defaultMacro(config, "G_BYTE_ORDER", "4321"); //glibc: #define G_BIG_ENDIAN    4321
        } else {
            defaultMacro(config, "__LITTLE_ENDIAN__", "1");
            defaultMacro(config, "G_BYTE_ORDER", "1234"); //glibc: #define G_LITTLE_ENDIAN 1234
        }
        //prevent a jcpp bug to happen when expanding assert(...) :
        config.preprocessorConfig.implicitMacros.put("NDEBUG", null);

        config.preprocessorConfig.implicitMacros.put("__STDC__", null);
        config.preprocessorConfig.implicitMacros.put("HAVE_PROTOTYPES", null);
        config.preprocessorConfig.implicitMacros.put("STDC_HEADERS", null);

        config.preprocessorConfig.implicitIncludes.addAll(getDefaultIncludePath(config.verbose));//JNAeratorConfigUtils.DEFAULT_INCLUDE_PATH);
        config.preprocessorConfig.frameworksPath.addAll(getDefaultFrameworkPath(config.verbose));//JNAeratorConfigUtils.DEFAULT_FRAMEWORKS_PATH);
        if (SystemUtils.isWindows()) {
            //http://msdn.microsoft.com/en-us/library/b0084kay(VS.80).aspx

            //config.preprocessorConfig.includeStrings.add("#define __declspec(x)\n");

            //http://support.microsoft.com/kb/65472
            config.preprocessorConfig.implicitMacros.put("_CHAR_UNSIGNED", null);

            config.preprocessorConfig.implicitMacros.put("WIN32_LEAN_AND_MEAN", null);

            // http://msdn.microsoft.com/en-us/library/dh8che7s(VS.80).aspx
            config.preprocessorConfig.implicitMacros.put("_WCHAR_T_DEFINED", null);
            config.preprocessorConfig.implicitMacros.put("_NATIVE_WCHAR_T_DEFINED", null);

            defaultMacro(config, "_MSC_VER", "1100");
            defaultMacro(config, "WINAPI", "__stdcall");

            config.functionsAccepter = new Adapter<Function, Boolean>() {
                public Boolean adapt(Function value) {
                    return true;
                    /*
                     Set<Modifier> mods = value.getModifiers();
	
                     return mods.contains(ModifierType.DllExport) ||
                     mods.contains(ModifierType.DllImport) ||
                     mods.contains(ModifierType.Extern);*/
                }
            };

            //config.preprocessorConfig.implicitMacros.put("")
            //_CPPRTTI
            //_DLL
            //_M_IX86
            //_MT
            //_CPPUNWIND

        } else {
            config.preprocessorConfig.implicitMacros.put("__GNUC__", null);

            if (SystemUtils.isMacOSX()) {
                config.preprocessorConfig.implicitMacros.put("TARGET_API_MAC_OSX", null);
                config.preprocessorConfig.implicitMacros.put("__APPLE_CPP__", null);
                config.preprocessorConfig.implicitMacros.put("__APPLE_CC__", null);
//				config.preprocessorConfig.implicitMacros.put("FUNCTION_PASCAL", "0");
//				config.preprocessorConfig.implicitMacros.put("FUNCTION_DECLSPEC", "1");
//				config.preprocessorConfig.implicitMacros.put("FUNCTION_WIN32CC", "1");

            }

            config.functionsAccepter = new Adapter<Function, Boolean>() {
                public Boolean adapt(Function value) {
                    return config.convertBodies || !value.hasModifier(ModifierType.Inline);
                }
            };
        }
        config.preprocessorConfig.includeStrings.add(0, "#define __attribute__(x)\n");

        JNAeratorConfigUtils.autoConfigureArchitecture(config);
    }

    private static Collection<? extends String> getDefaultFrameworkPath(boolean verbose) {
        return Arrays.asList(getProp("JNAERATOR_FRAMEWORKS_PATH", StringUtils.implode(DEFAULT_FRAMEWORKS_PATH, File.pathSeparator), verbose).split(File.pathSeparator));
    }

    private static Collection<? extends String> getDefaultIncludePath(boolean verbose) {
        return Arrays.asList(getProp("JNAERATOR_INCLUDE_PATH", StringUtils.implode(DEFAULT_INCLUDE_PATH, File.pathSeparator), verbose).split(File.pathSeparator));

    }

    /**
     * TODO move this to a .h resource file <ul> <li> endianness <li>
     * TARGET_CPU_* : see
     * /System/Library/Frameworks/CoreServices.framework/Versions/Current/Frameworks/CarbonCore.framework/Headers/fp.h
     * </ul>
     *
     * @param config
     */
    static void autoConfigureArchitecture(JNAeratorConfig config) {
        String arch = System.getProperty("os.arch").toLowerCase();
        if (config.verbose) {
            System.out.println("os.arch = " + arch);
        }

        //protect us from inline assembly in VC++:
        //config.preprocessorConfig.implicitMacros.put("_M_CEE_PURE", null);

        if (arch.equals("x86_64") || arch.equals("amd64")) {
            config.preprocessorConfig.implicitMacros.put("TARGET_CPU_X86_64", null);
            config.preprocessorConfig.implicitMacros.put("__i386__", null);
            config.preprocessorConfig.implicitMacros.put("__x86_64__", null);
            config.preprocessorConfig.implicitMacros.put("__amd64__", null);
            config.preprocessorConfig.implicitMacros.put("__LITTLE_ENDIAN__", null);
            config.preprocessorConfig.implicitMacros.put("M_I86", "1");
            config.preprocessorConfig.implicitMacros.put("_M_I86", "1");
            config.preprocessorConfig.implicitMacros.put("_WIN32", "1");
//			config.preprocessorConfig.implicitMacros.put("M_X64", "1");
//			config.preprocessorConfig.implicitMacros.put("_M_X64", "1");
//			config.preprocessorConfig.implicitMacros.put("_WIN64", "1");
        } else if (arch.equals("i386") || arch.equals("x86")) {
            config.preprocessorConfig.implicitMacros.put("TARGET_CPU_X86", null);
            config.preprocessorConfig.implicitMacros.put("__i386__", null);
            config.preprocessorConfig.implicitMacros.put("__LITTLE_ENDIAN__", null);
            config.preprocessorConfig.implicitMacros.put("M_I86", "1");
            config.preprocessorConfig.implicitMacros.put("_M_I86", "1");
            config.preprocessorConfig.implicitMacros.put("_WIN32", "1");
        } else if (arch.equals("ppc")) {
            config.preprocessorConfig.implicitMacros.put("TARGET_CPU_PPC", null);
            config.preprocessorConfig.implicitMacros.put("__PPC__", null);
            config.preprocessorConfig.implicitMacros.put("__powerpc__", null);
            config.preprocessorConfig.implicitMacros.put("__BIG_ENDIAN__", null);
        } else if (arch.equals("ppc64")) {
            config.preprocessorConfig.implicitMacros.put("TARGET_CPU_PPC64", null);
            config.preprocessorConfig.implicitMacros.put("__PPC_64__", null);
//			config.preprocessorConfig.implicitMacros.put("__powerpc64__", null);
            config.preprocessorConfig.implicitMacros.put("__BIG_ENDIAN__", null);
        }

    }

    public static void readProjectConfig(File projectFile, String configName, final JNAeratorConfig config) throws Exception {
        String projectFileName = projectFile.getName();
        config.rootDirectoriesPrefixesForSourceComments.add(projectFile.getCanonicalFile().getParent() + File.separator);

        if (projectFileName.endsWith(".sln")) {
            if (configName == null) {
                configName = "Release|Win32";
            }

            for (String include : VisualStudioUtils.getMicrosoftIncludes()) {
                include = new File(include).getCanonicalPath();
                config.preprocessorConfig.implicitIncludes.add(include);
                if (!include.endsWith(File.separator)) {
                    include = include + File.separator;
                }
                config.rootDirectoriesPrefixesForSourceComments.add(include);
            }

            Solution solution = new Solution(projectFile);
            solution.parseProjects(config.fileFilter);

            //final Map<String, FileConfiguration> configsByFile = new LinkedHashMap<String, FileConfiguration>();
            //final Map<File, String> libraryDLLByFile = new LinkedHashMap<File, String>();

            for (Project project : solution.getProjects()) {
                String projectConfigName = project.activeConfigurationNameBySolutionConfigurationName.get(configName);
                if (projectConfigName == null) {
                    projectConfigName = configName;
                }

                //	throw new IOException("Solution configuration with name '" + configName + "' does correspond to any configuration in project '" + project.name + "' (available configs : " + 
                //			StringUtils.implode(project.activeConfigurationNameBySolutionConfigurationName.keySet(), ", ") + ")");

                Configuration configuration = project.configurations.get(projectConfigName);
                String libraryFile = configuration.outputFile == null ? project.name : RegexUtils.findFirst(configuration.outputFile, Pattern.compile("^(.*?)(\\.[^.]*)?$"), 1);
                config.libraryProjectSources.put(libraryFile, project.projectFile.getCanonicalFile());

                System.out.println("project " + project.name + ": library = " + libraryFile);
                if (configuration != null) {
                    System.out.println("preprocessorDefinitions : " + configuration.preprocessorDefinitions);
                    for (String def : configuration.preprocessorDefinitions) {
                        config.preprocessorConfig.implicitMacros.put(def, "");
                    }
                }
                for (File file : project.files) {
                    try {
                        file = file.getCanonicalFile();
                        System.out.println(file + "\n\t-> " + libraryFile);
                        config.addSourceFile(file, libraryFile, false, true, true);

                        //config.preprocessorConfig
                        //config.libraryByFile.put(file, libraryFile)
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }

            if (config.preprocessorConfig.implicitMacros.containsKey("WIN32") || config.preprocessorConfig.implicitMacros.containsKey("_WIN32")) {
                config.preprocessorConfig.implicitMacros.put("_M_IX86", "");
            } else if (config.preprocessorConfig.implicitMacros.containsKey("WIN64") || config.preprocessorConfig.implicitMacros.containsKey("_WIN64")) {
                config.preprocessorConfig.implicitMacros.put("_M_AMD64", "");
            }

        }
    }

    public static File getFrameworkHeaderDirectory(String framework, List<String> frameworksPath) throws IOException {
        return new File(getFrameworkDirectory(framework, frameworksPath), "Headers");
    }

    public static File getFrameworkDirectory(String framework, List<String> frameworksPath) throws IOException {
        File file = new File(framework);
        if (!file.getName().matches("\\.framework$") || !file.exists()) {
            for (String pathEl : frameworksPath) {
                File f = new File(pathEl, framework + ".framework");
                if (f.exists() && f.isDirectory()) {
                    file = f;
                    break;
                }
            }
        }
        if (!file.exists()) {
            throw new IOException("Could not find framework '" + framework + "' in path " + frameworksPath);
        }
        return file;
    }
}
