/*
 * Written by Dawid Kurzyniec and released to the public domain, as explained
 * at http://creativecommons.org/licenses/publicdomain
 */

package edu.emory.mathcs.util.natives;

import java.io.*;
import java.security.*;

/**
 * Allows to detect local system architecture as defined by the
 * <a href="http://www.csm.ornl.gov/pvm/pvmArch.html">PVM project</a>.
 *
 * @author Dawid Kurzyniec
 * @version 1.0
 */
public class PvmArch {

    private PvmArch() {}

    private static String arch;

    private static String[][] stdOsArchVerMappings = new String[][] {
        // based on http://www.tolstoy.com/samizdat/sysprops.html
        { "solaris,sparc,[25]\\..*", "SUN4SOL2" },

        // observed on compute.mathcs.emory.edu
        { "sunos,sparc,5\\..*", "SUN4SOL2" },
    };

    private static String[][] stdOsArchMappings = new String[][] {
        // based on http://www.tolstoy.com/samizdat/sysprops.html
        { "linux,x86",             "LINUX" },
        { "linux,i[3456]86",       "LINUX" },
        { "mac os.*,powerpc.*",    "MACOS" },
        { "macos.*,powerpc.*",     "MACOS", },
        { "os/2,x86",              "OS2" },
        { "hp.*,pa.risc",          "HP3" },
        { "aix.*",                 "RS6K" },
        { "freebsd,x86",           "FREEBSD" },
        { "irix,.*",               "SGI" },
        { "irix64,.*",             "SGI64" },
        { "digital unix,alpha",    "DGAV" },

        // based on pvmgetarch
        { "sunos,sun3.*",          "SUN3" },
        { "sunos,sun4.*",          "SUN4" },
        { "sunos,i86pc",           "X86SOL2" },
        { "ultrix,risc",           "PMAX" },
        { "ultrix,vax",            "UVAX" },
        { ".*hp.*,9000/[2345].*",  "HP300" },
        { ".*hp.*,9000/[78].*",    "HPPA" },
        { ".*osf.*,alpha",         "ALPHA" },
        { "crsos,smp",             "CRAYSMP" },
        { ".*,paragon",            "PGON" },
        { "dgux,aviion",           "DGAV" },
        { ".*,88k",                "MIPS" },
        { ".*,cray-2",             "CRAY2" },
        { "linux,i[3456]86",       "LINUX" },
        { "linux,alpha",           "LINUXALPHA" },
        { "linux,arm",             "LINUXARM" },
        { "linux,sparc*",          "LINUXSPARC" },
        { "linux,hp_pa",           "LINUXHPPA" },
        { "linux,ppc",             "LINUXPPC" },
        { "linux,ia64",            "LINUXIA64" },
        { "linux,x86_64",          "LINUXAMD64" },
        { "bsd/os,i[3456]86",      "BSD386" },
        { "freebsd,i386",          "FREEBSD" },
        { "super-ux,sx-3",         "SX3" },
        { "uts,.*",                "UTS2" },
        { "realix,m88",            "M88K" },
        { "domainos,dn"  ,         "APOLLO" },
        { "os/2,i[3456]86",        "OS2" },
        { "cygwin.*,i[3456]86",    "CYGWIN" },

        // default Windows mappings
        { "windows.*,x86",         "WIN32" },
        { "windows.*,pentium",     "WIN32" },
    };

    private static String getPvmArchFromStdMappings(String os, String ht, String ver) {
        String key;
        if (ver != null) {
            key = os + "," + ht + "," + ver;
            for (int i=0; i<stdOsArchVerMappings.length; i++) {
                String pattern = stdOsArchVerMappings[i][0];
                if (key.matches(pattern)) {
                    return stdOsArchVerMappings[i][1];
                }
            }
        }
        key = os + "," + ht;
        for (int i=0; i<stdOsArchMappings.length; i++) {
            String pattern = stdOsArchMappings[i][0];
            if (key.matches(pattern)) {
                return stdOsArchMappings[i][1];
            }
        }
        return "UNKNOWN";
    }

    private static final String[] unameExecs = {
        "/bin/uname", "/usr/bin/uname", "/bin/uname.exe", "/usr/bin/uname.exe"
    };

    private synchronized static String getUnameCmd() {
        return FilesystemUtils.findFile(unameExecs).getPath();
    }

    private static boolean isF(String path) {
        return new File(path).isFile();
    }

    private static boolean isD(String path) {
        return new File(path).isDirectory();
    }

    /**
     * Returns the identifier representing current system architecture,
     * as defined by the
     * <a href="http://www.csm.ornl.gov/pvm/pvmArch.html">PVM project</a>.
     *
     * @return PVM architecture
     */
    public static String getArch() {

        // make sure the client is authorized to access this information

        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkPropertyAccess("os.name");
            security.checkPropertyAccess("os.arch");
            security.checkPropertyAccess("os.version");
        }

        synchronized (PvmArch.class) {
            if (PvmArch.arch != null) return PvmArch.arch;
        }
        String pvmArch = (String)AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                try {
                    return getArchPrivileged();
                }
                catch (Exception e) {
                    return "UNKNOWN";
                }
            }
        });
        synchronized (PvmArch.class) {
            PvmArch.arch = pvmArch;
        }
        return pvmArch;
    }

    private static String getArchPrivileged() throws IOException, InterruptedException {

        // try guessing from standard system properties
        String osName = System.getProperty("os.name").toLowerCase();
        String osArch = System.getProperty("os.arch").toLowerCase();
        String osVer  = System.getProperty("os.version").toLowerCase();

        String pvmArch = getPvmArchFromStdMappings(osName, osArch, osVer);

        String os = null;
        String ht = null;
        String ov = null;


        if ("UNKNOWN".equals(pvmArch)) {
            String unameCmd = getUnameCmd();
            if (unameCmd != null) {
                os = ExecUtils.execCommand(unameCmd + " -s").getOut().trim().toLowerCase();
                ht = ExecUtils.execCommand(unameCmd + " -m").getOut().trim().toLowerCase();
                ov = ExecUtils.execCommand(unameCmd + " -v").getOut().trim().toLowerCase();

                pvmArch = getPvmArchFromStdMappings(os, ht, ov);
            }
        }

        if ("DGAV".equals(pvmArch)) {
            // correction (from pvmgetarch) for DG/Intel
            String unamea = ExecUtils.execCommand(getUnameCmd() + " -a").getOut();
            if (unamea.matches("PentiumPro$")) {
                pvmArch = "DGIX";
            }
        }

        if ("UNKNOWN".equals(pvmArch)) {
            if (isF("/bin/arch")) {
                String binarch = ExecUtils.execCommand("/bin/arch").getOut().trim();
                if      ("ksr1".equals(binarch)) pvmArch = "KSR1";
                else if ("sun2".equals(binarch)) pvmArch = "SUN2";
                else if ("sun3".equals(binarch)) pvmArch = "SUN3";
                else if ("sun4".equals(binarch)) pvmArch = "SUN4";
            }
        }

        if ("UNKNOWN".equals(pvmArch)) {
            if (isF("/usr/etc/RELDEF")) { pvmArch = "ATT"; }
            else if (isF("/ultrixboot")) {
                if (isF("/pcs750.bin")) {
                    pvmArch = "UVAX";
                }
                else {
                    pvmArch = "PMAX";
                }
            }
            else if (isF("/pcs750.bin"))       { pvmArch = "VAX"; }
            else if (isD("/usr/bin/alliant"))  { pvmArch = "AFX8"; }
            else if (isF("/usr/bin/cluster"))  { pvmArch = "BFLY"; }
            else if (isD("/usr/convex"))       { pvmArch = "CNVX"; }
            else if (isF("/unicos"))           { pvmArch = "CRAY"; }
            else if (isF("/hp-ux"))            { pvmArch = "HP300"; }
            else if (isF("/usr/bin/getcube"))  { pvmArch = "I860"; }
            else if (isF("/usr/bin/asm56000")) { pvmArch = "NEXT"; }
            else if (isF("/etc/vg"))           { pvmArch = "RS6K"; }
            else if (isD("/usr/include/caif")) { pvmArch = "RT"; }
            else if (isF("/bin/4d"))           { pvmArch = "SGI"; }
            else if (isF("/dynix"))            { pvmArch = "SYMM"; }
            else if (isF("/bin/titan"))        { pvmArch = "TITN"; }
            else if (isF("/netbsd")) {
                String unamep = ExecUtils.execCommand("/usr/bin/uname -p").getOut().trim();
                if      ("alpha"  .equals(unamep)) { pvmArch = "NETBSDALPHA"; }
                else if ("arm32"  .equals(unamep)) { pvmArch = "NETBSDARM32"; }
                else if ("i386"   .equals(unamep)) { pvmArch = "NETBSDI386"; }
                else if ("m68k"   .equals(unamep)) { pvmArch = "NETBSDM68K"; }
                else if ("mipseb" .equals(unamep)) { pvmArch = "NETBSDMIPSEB"; }
                else if ("mipsel" .equals(unamep)) { pvmArch = "NETBSDMIPSEL"; }
                else if ("ns32k"  .equals(unamep)) { pvmArch = "NETBSDNS32K"; }
                else if ("powerpc".equals(unamep)) { pvmArch = "NETBSDPOWERPC"; }
                else if ("sh3"    .equals(unamep)) { pvmArch = "NETBSDSH3"; }
                else if ("sparc"  .equals(unamep)) { pvmArch = "NETBSDSPARC"; }
                else if ("sparc64".equals(unamep)) { pvmArch = "NETBSDSPARC64"; }
                else if ("vax"    .equals(unamep)) { pvmArch = "NETBSDVAX"; }
            }
            else if (isF("/usr/bin/machine")) {
                String machine = ExecUtils.execCommand("/usr/bin/machine").getOut().trim();
                if ("i386".equals(machine))   { pvmArch = "BSD386"; }
            }
            else if (isF("/usr/bin/uxpm")) {
                if (ExecUtils.execCommand("/usr/bin/uxpm").getExitValue() == 0) {
                    pvmArch = "UXPM";
                }
            }
            else if (isF("/usr/bin/uxpv")) {
                if (ExecUtils.execCommand("/usr/bin/uxpv").getExitValue() == 0) {
                    pvmArch = "UXPV";
                }
            }
        }

        if ("UNKNOWN".equals(pvmArch)) {
            String unameCmd = getUnameCmd();
            if (unameCmd != null) {
                String rv = ExecUtils.execCommand(unameCmd + " -r").getOut().trim();
                if ((os + "," + ht).matches(".*,i[3456]86")) {
                    if (rv.matches("4\\..*")) {
                        pvmArch = "UWARE";
                    }
                    else {
                        pvmArch = "SCO";
                    }
                }
            }
        }

        // update the machine type to derive subclasses
        int nproc = Runtime.getRuntime().availableProcessors();
        boolean pvmShmem =
            Boolean.valueOf(System.getProperty("pvm.shmem", "false")).booleanValue();

        if ("SUN4".equals(pvmArch)) {
            String rel = ExecUtils.execCommand(getUnameCmd() + " -r").getOut().trim();
            if (rel.matches("5\\..*")) pvmArch = "SUN4SOL2";
        }

        if ("SGI".equals(pvmArch)) {
            String rel = ExecUtils.execCommand("/bin/uname -r").getOut().trim();
            if      (rel.matches("5\\..*")) { pvmArch = "SGI5"; }
            else if (rel.matches("6\\..*")) { pvmArch = "SGI6"; }
        }

        if      ("SUN4".equals(pvmArch) && isF("/dev/cm"))     { pvmArch = "CM2"; }
        else if ("SUN4".equals(pvmArch) && isF("/dev/cmni"))   { pvmArch = "CM5"; }
        else if ("CNVX".equals(pvmArch)) {
            if (ExecUtils.execCommand("/usr/convex/getsysinfo -f native_default").
                    getExitValue() == 0) {
                pvmArch = "CNVXN";
            }
        }
        else if ("PMAX".equals(pvmArch) && isD("/usr/maspar")) { pvmArch = "MASPAR"; }
        else if ("RS6K".equals(pvmArch) && (os + "," + ov).matches("AIX.*,4")) {
            pvmArch = "AIX46K";
        }
        else if ("HPPA".equals(pvmArch) && isF("/bin/sysinfo")) { pvmArch = "CSPP"; }

        if (nproc >= 2 && pvmShmem) {
            if ("SUN4SOL2".equals(pvmArch))     { pvmArch = "SUNMP"; }
            else if ("ALPHA".equals(pvmArch))   { pvmArch = "ALPHAMP"; }
            else if ("SGI64".equals(pvmArch))   { pvmArch = "SGIMP64"; }
            else if ("SGI5".equals(pvmArch))    { pvmArch = "SGIMP5"; }
            else if ("SGI6".equals(pvmArch))    { pvmArch = "SGIMP6"; }
            else if ("AIX46K".equals(pvmArch))  { pvmArch = "AIX4MP"; }
            else if ("HPPA".equals(pvmArch))    { pvmArch = "HPPAMP"; }
        }

        return pvmArch;
    }
}
