package codeanticode.gsvideo;

import java.util.HashMap;
import java.util.Map;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;

// Library loader class by Tal Shalif
public class JnaLibLoaderChain {

    interface DummyLibrary extends Library {
    };

    private static JnaLibLoaderChain instance;

    static final Object[][] WIN32_DEPENDANCIES = {
        { "glib-2.0", new String[] { "iconv", "intl" } },
        { "gstreamer-0.10", new String[] { "gobject-2.0", "gthread-2.0", "gmodule-2.0", "glib-2.0" } },
        { "gstbase-0.10", new String[] { "gstreamer-0.10" } },
        { "gstinterfaces-0.10", new String[] { "gstreamer-0.10" } },
        { "gstcontroller-0.10", new String[] { "gstreamer-0.10" } },
        { "gobject-2.0", new String[] { "glib-2.0" } },
        { "gmodule-2.0", new String[] { "glib-2.0" } },
        //{ "gstdshow-0.10", new String[] { } },
        { "gstpbutils-0.10", new String[] {} },
        { "avutil-49", new String[] {} },
        { "avcodec-51", new String[] { "avutil-49" } },        
        { "avformat-52", new String[] { "avutil-49", "avcodec-51" } },        
//        { "avdevice-52", new String[] { "avutil-49", "avformat-52" } },
        { "gstapp-0.10", new String[] {} },
        { "gstrtp-0.10", new String[] {} },
        { "gstriff-0.10", new String[] { "glib-2.0", "gobject-2.0", "gsttag-0.10", "gstaudio-0.10", "gstreamer-0.10" } },
        { "gstdataprotocol-0.10", new String[] { } },
        { "gsttag-0.10", new String[] { } },
        { "gstnetbuffer-0.10", new String[] { } },
        { "gstrtsp-0.10", new String[] {} },
        { "gstsdp-0.10", new String[] {} },
        { "oil-0.3", new String[] {} },
        { "jpeg", new String[] { } },
        { "zlib1", new String[] { } },        
        { "mms", new String[] { "glib-2.0" } },        
        { "gstaudio-0.10", new String[] { "gstbase-0.10" } },
        { "gstvideo-0.10", new String[] { "gstbase-0.10" } },
        { "gthread-2.0", new String[] { "glib-2.0" } }
        
        //{ "SoundTouch-1", new String[] {} },
        //{ "gstfarsight-0.10", new String[] { } },
        //{ "jpeg", new String[] {} },        
        
    };

    
    static final Object[][] OSX_DEPENDANCIES = {
        { "oil-0.3", new String[] { "intl" } },
        { "ogg", new String[] { "intl" } },
        { "vorbis", new String[] { "ogg" } },
        { "vorbisenc", new String[] { "vorbis" } },
        { "vorbisfile", new String[] { "vorbis" } },

    	
    	{ "glib-2.0", new String[] { "intl" } },
        { "gmodule-2.0", new String[] { "glib-2.0" } },
        { "gobject-2.0", new String[] { "glib-2.0" } },
        { "gthread-2.0", new String[] { "glib-2.0" } },

        { "gstreamer-0.10", new String[] { "gobject-2.0", "gthread-2.0", "gmodule-2.0" } },
        { "gstbase-0.10", new String[] { "gstreamer-0.10" } },

        { "gstcontroller-0.10", new String[] { "gstreamer-0.10" } },
        { "gstdataprotocol-0.10", new String[] { "gstreamer-0.10" } },
        { "gstinterfaces-0.10", new String[] { "gstreamer-0.10" } },
        { "gstnet-0.10", new String[] { "gstreamer-0.10" } },
        { "gstnetbuffer-0.10", new String[] { "gstreamer-0.10" } },
        { "gstpbutils-0.10", new String[] { "gstreamer-0.10" } },
        
        { "gstaudio-0.10", new String[] { "gstbase-0.10", "gstinterfaces-0.10" } },
        { "gstcdda-0.10", new String[] { "gsttag-0.10", "gstbase-0.10" } },
        { "gstfft-0.10", new String[] { "gstreamer-0.10" } },        
        { "gstriff-0.10", new String[] { "gstaudio-0.10", "gstinterfaces-0.10", "gsttag-0.10",  "gstbase-0.10" } },
        { "gstrtp-0.10", new String[] { "gstbase-0.10" } },
        { "gstrtsp-0.10", new String[] { "gstreamer-0.10" } },
        { "gstsdp-0.10", new String[] { "gstreamer-0.10" } },
        { "gsttag-0.10", new String[] { "gstbase-0.10" } },
        { "gstvideo-0.10", new String[] { "gstbase-0.10" } }
    };
    
    static final Object[][] DEFAULT_DEPENDANCIES = {
        { "gstreamer-0.10", new String[] { } },
        { "gstbase-0.10", new String[] { "gstreamer-0.10"  } },
        { "gstinterfaces-0.10", new String[] { "gstreamer-0.10" } },
        { "gstcontroller-0.10", new String[] { "gstreamer-0.10"  } },
        { "gstaudio-0.10", new String[] { "gstbase-0.10" } },
        { "gstvideo-0.10", new String[] { "gstbase-0.10" } },
    };

    static final Object[][] dependancies = Platform.isWindows() ? WIN32_DEPENDANCIES : OSX_DEPENDANCIES;
    
    
    private static final Map<String, Object> loadedMap = new
HashMap<String, Object>();

    private static final int RECURSIVE_LOAD_MAX_DEPTH = 5;

    private JnaLibLoaderChain() {
    }

    private void preLoadLibs() {
        for (Object[] a : dependancies) {
            load(a[0].toString(), DummyLibrary.class, false, 0);
        }
    }

    private String[] findDeps(String name) {

        for (Object[] a : dependancies) {
            if (name.equals(a[0])) {

                return (String[]) a[1];
            }
        }

        return new String[] {}; // library dependancy load chain unspecified - probably client call
    }

    public Object load(String name, Class<?> clazz) {
        return load(name, clazz, true, 0);
    }

    private Object load(String name, Class<?> clazz, boolean
forceReload, int depth) {

        assert depth < RECURSIVE_LOAD_MAX_DEPTH: String.format("recursive max load depth %s has been exceeded", depth);

        Object library = loadedMap.get(name);

        if (null == library || forceReload) {

            //Logger.getAnonymousLogger().info(String.format("%" + ((depth + 1) * 2) + "sloading %s", "->", name));

            try {
                String[] deps = findDeps(name);

                for (String lib : deps) {
                    load(lib, DummyLibrary.class, false, depth + 1);
                }

                library = loadLibrary(name, clazz);

                loadedMap.put(name, library);
            } catch (Exception e) {
                throw new RuntimeException(String.format("can not load library %s", name, e));
            }
        }

        return library;
    }

    private static Object loadLibrary(String name, Class<?> clazz) {

        //Logger.getAnonymousLogger().info(String.format("loading %s", name));

        String[] nameFormats;
        nameFormats = Platform.isWindows() ? new String[]{ "%s", "lib%s", "lib%s-0" } : new String[]{ "%s-0", "%s" };
        
        
        UnsatisfiedLinkError linkError = null;

        for (String fmt : nameFormats) {
            try {
            	String s = String.format(fmt, name);
            	System.out.println("Trying to load library file " + s);
            	Object obj = Native.loadLibrary(s, clazz);
            	System.out.println("Loaded library " + s + " succesfully!");
                return obj;
            } catch (UnsatisfiedLinkError ex) {
                linkError = ex;
            }
        }

        throw new UnsatisfiedLinkError(String.format("can't load library %s (%1$s|lib%1$s|lib%1$s-0) with -Djna.library.path=%s. Last error:%s" , name, System.getProperty("jna.library.path"), linkError));
    }

    public static synchronized JnaLibLoaderChain getInstance() {

        if (null == instance) {
            instance = new JnaLibLoaderChain();
            instance.preLoadLibs();
        }

        return instance;
    }

}
