/**
 * This package provides classes to handle video in Processing. The API is compatible with the built-in video library of Processing. 
 * GSVideo uses the multimedia toolkit GStreamer (http://www.gstreamer.net/)  through the gstreamer-java bindings by Wayne Meissener:
 * http://code.google.com/p/gstreamer-java/ 
 * @author Andres Colubri
 * @version 0.5.1
 *
 * Copyright (c) 2008 Andres Colubri
 *
 * This source 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 2 of the License, or
 * (at your option) any later version.
 * 
 * This code 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.
 * 
 * A copy of the GNU General Public License is available on the World
 * Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also
 * obtain it by writing to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

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 {

    public interface DummyLibrary extends Library {
    }    
    
    private static JnaLibLoaderChain instance;

    static final Object[][] WIN32_DEPENDENCIES = {    	
        { "SDL", new String[] { }, true },    	
        { "glew32", new String[] { }, true },    	 	
        { "iconv", new String[] { }, true },    	
        { "liba52-0", new String[] { }, true },    	    	
        { "libbz2", new String[] { }, true },    	    	
        { "libcairo-2", new String[] { }, true },    		
        { "libdca-0", new String[] { }, true },    		
        { "libdl", new String[] { }, true },    	 	
        { "libdvdcss-2", new String[] { }, true },    	
        { "libdvdnav-4", new String[] { }, true },    	 	
        { "libdvdnavmini-4", new String[] { }, true },    	 	
        { "libdvdread-4", new String[] { }, true },    	 	
        { "libfaac-0", new String[] { }, true },    		
        { "libfaad-2", new String[] { }, true },    		
        { "libfontconfig-1", new String[] { }, true },    	
        { "libfreetype-6", new String[] { }, true },    	
        { "libgcrypt-11", new String[] { }, true },    	
        { "libgnutls-26", new String[] { }, true },    	
        { "libgnutls-extra-26", new String[] { }, true },    	
        { "libgnutls-openssl-26", new String[] { }, true },    	  	
        { "libgnutlsxx-26", new String[] { }, true },    	
        { "libgpg-error-0", new String[] { }, true },    	    	
        { "libjpeg", new String[] { }, true },    	
        { "libmms-0", new String[] { }, true },    	
        { "libmp3lame-0", new String[] { }, true },    	 	
        { "libmpeg2-0", new String[] { }, true },    	    	
        { "libmpeg2convert-0", new String[] { }, true },    		
        { "libneon-27", new String[] { }, true },    	
        { "libnice", new String[] { }, true },    	
        { "libogg-0", new String[] { }, true },    	
        { "liboil-0.3-0", new String[] { }, true },    	
        { "libopenjpeg-2", new String[] { }, true },    	
        { "libpango-1.0-0", new String[] { }, true },    	
        { "libpangocairo-1.0-0", new String[] { }, true },    	
        { "libpangoft2-1.0-0", new String[] { }, true },    	
        { "libpangowin32-1.0-0", new String[] { }, true },    	
        { "libpixman-1-0", new String[] { }, true },    	
        { "libpng12-0", new String[] { }, true },    	
        { "libschroedinger-1.0-0", new String[] { }, true },    	
        { "libsoup-2.4-1", new String[] { }, true },    	
        { "libspeex-1", new String[] { }, true },    	
        { "libspeexdsp-1", new String[] { }, true },    	
        { "libtheora-0", new String[] { }, true },    	
        { "libtheoradec-1", new String[] { }, true },    	
        { "libtheoraenc-1", new String[] { }, true },    	
        { "libvorbis-0", new String[] { }, true },    	
        { "libvorbisenc-2", new String[] { }, true },    	
        { "libvorbisfile-3", new String[] { }, true },    	
        { "libwavpack-1", new String[] { }, true },    	
        { "libx264-67", new String[] { }, true },    	
        { "libxml2-2", new String[] { }, true },    	
        { "pthreadGC2", new String[] { }, true },    	
        { "xvidcore", new String[] { }, true },    	
        { "zlib1", new String[] { }, true },    	

    	{ "gio-2.0", new String[] { }, true },
    	{ "glib-2.0", new String[] { }, true },
    	{ "gmodule-2.0", new String[] { }, true },
    	{ "gobject-2.0", new String[] { }, true },
    	{ "gthread-2.0", new String[] { }, true },
    	
    	{ "gstapp-0.10", new String[] { }, true },
    	{ "gstaudio-0.10", new String[] { }, true },
    	{ "gstbase-0.10", new String[] { }, true },
    	{ "gstcdda-0.10", new String[] { }, true },
    	{ "gstcontroller-0.10", new String[] { }, true },
    	{ "gstdataprotocol-0.10", new String[] { }, true },
    	{ "gstdshow-0.10", new String[] { }, true },
    	{ "gstfarsight-0.10", new String[] { }, true },
    	{ "gstfft-0.10", new String[] { }, true },
    	{ "gstgl-0.10", new String[] { }, true },
    	{ "gstinterfaces-0.10", new String[] { }, true },
    	{ "gstnet-0.10", new String[] { }, true },
    	{ "gstnetbuffer-0.10", new String[] { }, true },
    	{ "gstpbutils-0.10", new String[] { }, true },
    	{ "gstphotography-0.10", new String[] { }, true },
    	{ "gstreamer-0.10", new String[] { }, true },
    	{ "gstriff-0.10", new String[] { }, true },
    	{ "gstrtp-0.10", new String[] { }, true },
    	{ "gstrtsp-0.10", new String[] { }, true },
    	{ "gstsdp-0.10", new String[] { }, true },
    	{ "gsttag-0.10", new String[] { }, true },
    	{ "gstvideo-0.10", new String[] { }, true }
    };
    
    static final Object[][] OSX_DEPENDENCIES = {
        { "gstreamer-0.10", new String[] { }, true },
        { "gstbase-0.10", new String[] { "gstreamer-0.10"  }, true },
        { "gstinterfaces-0.10", new String[] { "gstreamer-0.10" }, true },
        { "gstcontroller-0.10", new String[] { "gstreamer-0.10"  }, true },
        { "gstaudio-0.10", new String[] { "gstbase-0.10" }, true },
        { "gstvideo-0.10", new String[] { "gstbase-0.10" }, true },    	
    };
    
    static final Object[][] DEFAULT_DEPENDENCIES = {
        { "gstreamer-0.10", new String[] { }, true },
        { "gstbase-0.10", new String[] { "gstreamer-0.10"  }, true },
        { "gstinterfaces-0.10", new String[] { "gstreamer-0.10" }, true },
        { "gstcontroller-0.10", new String[] { "gstreamer-0.10"  }, true },
        { "gstaudio-0.10", new String[] { "gstbase-0.10" }, true },
        { "gstvideo-0.10", new String[] { "gstbase-0.10" }, true },
    };
    
    
    static final Object[][] dependencies = Platform.isWindows() ? WIN32_DEPENDENCIES : 
    	                                   Platform.isMac()     ? OSX_DEPENDENCIES : 
	                                	                          DEFAULT_DEPENDENCIES; 
    
    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 : dependencies) {
        	load(a[0].toString(), DummyLibrary.class, true, 0, (Boolean)a[2]);
        }
    }

    private String[] findDeps(String name) {

        for (Object[] a : dependencies) {
            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, boolean reqLib) {
        return load(name, clazz, true, 0, reqLib);
    }

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

        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, reqLib);
                }

                library = loadLibrary(name, clazz, reqLib);

                if (library != null) loadedMap.put(name, library);
            } catch (Exception e) {
            	if (reqLib)
                    throw new RuntimeException(String.format("can not load library %s", name, e));
            	else
            		System.out.println(String.format("can not load library %s", name, e));
            }
        }

        return library;
    }

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

        //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;
            }
        }
        
    	if (reqLib)
    		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));
    	else
    	{
    		System.out.println(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));
    		return null;
    	}
    }

    public static synchronized JnaLibLoaderChain getInstance() {

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

        return instance;
    }

}
