package com.beloko.libsdl;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;

import android.graphics.PixelFormat;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;


/**
    SDL Activity
 */
public class SDLLib {

	private static final Object threadLock = new Object();
	public static boolean resumed = false;
	
	public static void loadSDL()
	{

		try {
			Log.i("JNI", "Trying to load SDL.so");

			System.loadLibrary("SDL");
			System.loadLibrary("SDL_mixer");
			System.loadLibrary("SDL_image");
		}
		catch (UnsatisfiedLinkError ule) {
			Log.e("JNI", "WARNING: Could not load SDL.so: " + ule.toString());
		}
		
		
	}

	// Audio
	private static Thread mAudioThread;
	private static AudioTrack mAudioTrack;

	// EGL private objects
	private static EGLContext  mEGLContext;
	private static EGLSurface  mEGLSurface;
	private static EGLDisplay  mEGLDisplay;
	private static EGLConfig   mEGLConfig;
	private static int mGLMajor, mGLMinor;


	// C functions we call
	public static native void nativeInit(boolean launch);
	public static native void nativeQuit();
	public static native void nativePause();
	public static native void nativeResume();
	public static native void onNativeResize(int x, int y, int format);
	public static native void onNativeKeyDown(int keycode);
	public static native void onNativeKeyUp(int keycode);
	public static native void onNativeTouch(int touchDevId, int pointerFingerId,
			int action, float x, 
			float y, float p);
	public static native void onNativeAccel(float x, float y, float z);
	public static native void nativeRunAudioThread();


	// Java functions called from C

	public static boolean createGLContext(int majorVersion, int minorVersion) {
		return true;
	}

	public static void flipBuffers() {

	}

	public static void setActivityTitle(String title) {

	}


	//    // EGL functions
	//    public static boolean initEGL(int majorVersion, int minorVersion) {
	//        if (SDLLib.mEGLDisplay == null) {
	//            //Log.v("SDL", "Starting up OpenGL ES " + majorVersion + "." + minorVersion);
	//
	//            try {
	//                EGL10 egl = (EGL10)EGLContext.getEGL();
	//
	//                EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
	//
	//                int[] version = new int[2];
	//                egl.eglInitialize(dpy, version);
	//
	//                int EGL_OPENGL_ES_BIT = 1;
	//                int EGL_OPENGL_ES2_BIT = 4;
	//                int renderableType = 0;
	//                if (majorVersion == 2) {
	//                    renderableType = EGL_OPENGL_ES2_BIT;
	//                } else if (majorVersion == 1) {
	//                    renderableType = EGL_OPENGL_ES_BIT;
	//                }
	//                int[] configSpec = {
	//                    //EGL10.EGL_DEPTH_SIZE,   16,
	//                    EGL10.EGL_RENDERABLE_TYPE, renderableType,
	//                    EGL10.EGL_NONE
	//                };
	//                EGLConfig[] configs = new EGLConfig[1];
	//                int[] num_config = new int[1];
	//                if (!egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config) || num_config[0] == 0) {
	//                    Log.e("SDL", "No EGL config available");
	//                    return false;
	//                }
	//                EGLConfig config = configs[0];
	//
	//                /*int EGL_CONTEXT_CLIENT_VERSION=0x3098;
	//                int contextAttrs[] = new int[] { EGL_CONTEXT_CLIENT_VERSION, majorVersion, EGL10.EGL_NONE };
	//                EGLContext ctx = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, contextAttrs);
	//
	//                if (ctx == EGL10.EGL_NO_CONTEXT) {
	//                    Log.e("SDL", "Couldn't create context");
	//                    return false;
	//                }
	//                SDLActivity.mEGLContext = ctx;*/
	//                SDLLib.mEGLDisplay = dpy;
	//                SDLLib.mEGLConfig = config;
	//                SDLLib.mGLMajor = majorVersion;
	//                SDLLib.mGLMinor = minorVersion;
	//
	//                SDLLib.createEGLSurface();
	//            } catch(Exception e) {
	//                Log.v("SDL", e + "");
	//                for (StackTraceElement s : e.getStackTrace()) {
	//                    Log.v("SDL", s.toString());
	//                }
	//            }
	//        }
	//        else SDLLib.createEGLSurface();
	//
	//        return true;
	//    }
	//
	//    public static boolean createEGLContext() {
	//        EGL10 egl = (EGL10)EGLContext.getEGL();
	//        int EGL_CONTEXT_CLIENT_VERSION=0x3098;
	//        int contextAttrs[] = new int[] { EGL_CONTEXT_CLIENT_VERSION, SDLLib.mGLMajor, EGL10.EGL_NONE };
	//        SDLLib.mEGLContext = egl.eglCreateContext(SDLLib.mEGLDisplay, SDLLib.mEGLConfig, EGL10.EGL_NO_CONTEXT, contextAttrs);
	//        if (SDLLib.mEGLContext == EGL10.EGL_NO_CONTEXT) {
	//            Log.e("SDL", "Couldn't create context");
	//            return false;
	//        }
	//        return true;
	//    }
	//
	//    public static boolean createEGLSurface() {
	//        if (SDLLib.mEGLDisplay != null && SDLLib.mEGLConfig != null) {
	//            EGL10 egl = (EGL10)EGLContext.getEGL();
	//            if (SDLLib.mEGLContext == null) createEGLContext();
	//
	//            Log.v("SDL", "Creating new EGL Surface");
	//            EGLSurface surface = egl.eglCreateWindowSurface(SDLLib.mEGLDisplay, SDLLib.mEGLConfig, SDLLib.mSurface, null);
	//            if (surface == EGL10.EGL_NO_SURFACE) {
	//                Log.e("SDL", "Couldn't create surface");
	//                return false;
	//            }
	//
	//            if (!egl.eglMakeCurrent(SDLLib.mEGLDisplay, surface, surface, SDLLib.mEGLContext)) {
	//                Log.e("SDL", "Old EGL Context doesnt work, trying with a new one");
	//                createEGLContext();
	//                if (!egl.eglMakeCurrent(SDLLib.mEGLDisplay, surface, surface, SDLLib.mEGLContext)) {
	//                    Log.e("SDL", "Failed making EGL Context current");
	//                    return false;
	//                }
	//            }
	//            SDLLib.mEGLSurface = surface;
	//            return true;
	//        }
	//        return false;
	//    }
	//
	//    // EGL buffer flip
	//    public static void flipEGL() {
	//        try {
	//            EGL10 egl = (EGL10)EGLContext.getEGL();
	//
	//            egl.eglWaitNative(EGL10.EGL_CORE_NATIVE_ENGINE, null);
	//
	//            // drawing here
	//
	//            egl.eglWaitGL();
	//
	//            egl.eglSwapBuffers(SDLLib.mEGLDisplay, SDLLib.mEGLSurface);
	//
	//
	//        } catch(Exception e) {
	//            Log.v("SDL", "flipEGL(): " + e);
	//            for (StackTraceElement s : e.getStackTrace()) {
	//                Log.v("SDL", s.toString());
	//            }
	//        }
	//    }


	// Called when the surface is resized
	public static void surfaceChanged(int format, int width, int height) {
		Log.v("SDL", "surfaceChanged()");

		int sdlFormat = 0x85151002; // SDL_PIXELFORMAT_RGB565 by default
		switch (format) {
		case PixelFormat.A_8:
			Log.v("SDL", "pixel format A_8");
			break;
		case PixelFormat.LA_88:
			Log.v("SDL", "pixel format LA_88");
			break;
		case PixelFormat.L_8:
			Log.v("SDL", "pixel format L_8");
			break;
		case PixelFormat.RGBA_4444:
			Log.v("SDL", "pixel format RGBA_4444");
			sdlFormat = 0x85421002; // SDL_PIXELFORMAT_RGBA4444
			break;
		case PixelFormat.RGBA_5551:
			Log.v("SDL", "pixel format RGBA_5551");
			sdlFormat = 0x85441002; // SDL_PIXELFORMAT_RGBA5551
			break;
		case PixelFormat.RGBA_8888:
			Log.v("SDL", "pixel format RGBA_8888");
			sdlFormat = 0x86462004; // SDL_PIXELFORMAT_RGBA8888
			break;
		case PixelFormat.RGBX_8888:
			Log.v("SDL", "pixel format RGBX_8888");
			sdlFormat = 0x86262004; // SDL_PIXELFORMAT_RGBX8888
			break;
		case PixelFormat.RGB_332:
			Log.v("SDL", "pixel format RGB_332");
			sdlFormat = 0x84110801; // SDL_PIXELFORMAT_RGB332
			break;
		case PixelFormat.RGB_565:
			Log.v("SDL", "pixel format RGB_565");
			sdlFormat = 0x85151002; // SDL_PIXELFORMAT_RGB565
			break;
		case PixelFormat.RGB_888:
			Log.v("SDL", "pixel format RGB_888");
			// Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
			sdlFormat = 0x86161804; // SDL_PIXELFORMAT_RGB888
			break;
		default:
			Log.v("SDL", "pixel format unknown " + format);
			break;
		}
		SDLLib.onNativeResize(width, height, sdlFormat);
		Log.v("SDL", "Window size:" + width + "x"+height);
	}



	// Audio
	public static void onPause()
	{
		resumed = false;
	}
	
	public static void onResume()
	{	
		resumed = true;
		synchronized (threadLock){
			threadLock.notifyAll();
		}	
	}
	
	private static Object buf;

	public static Object audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
		int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
		int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
		int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);

		Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + ((float)sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");

		// Let the user pick a larger buffer if they really want -- but ye
		// gods they probably shouldn't, the minimums are horrifyingly high
		// latency already
		desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);

		mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
				channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);

		audioStartThread();

		Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + ((float)mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");

		if (is16Bit) {
			buf = new short[desiredFrames * (isStereo ? 2 : 1)];
		} else {
			buf = new byte[desiredFrames * (isStereo ? 2 : 1)]; 
		}
		return buf;
	}

	public static void audioStartThread() {
		mAudioThread = new Thread(new Runnable() {
			public void run() {
				mAudioTrack.play();
				nativeRunAudioThread();
			}
		});

		// I'd take REALTIME if I could get it!
		mAudioThread.setPriority(Thread.MAX_PRIORITY);
		mAudioThread.start();
	}

	public static void audioWriteShortBuffer(short[] buffer) {
		if (!resumed)
			synchronized (threadLock) {
				try {
					if (mAudioTrack != null)
						mAudioTrack.pause();
					threadLock.wait();
					if (mAudioTrack != null)
						mAudioTrack.play();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

		
		for (int i = 0; i < buffer.length; ) {
			int result = mAudioTrack.write(buffer, i, buffer.length - i);
			if (result > 0) {
				i += result;
			} else if (result == 0) {
				try {
					Thread.sleep(1);
				} catch(InterruptedException e) {
					// Nom nom
				}
			} else {
				Log.w("SDL", "SDL audio: error return from write(short)");
				return;
			}
		}
	}

	public static void audioWriteByteBuffer(byte[] buffer) {
		if (!resumed)
			synchronized (threadLock) {
				try {
					if (mAudioTrack != null)
						mAudioTrack.pause();
					threadLock.wait();
					if (mAudioTrack != null)
						mAudioTrack.play();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

		for (int i = 0; i < buffer.length; ) {
			int result = mAudioTrack.write(buffer, i, buffer.length - i);
			if (result > 0) {
				i += result;
			} else if (result == 0) {
				try {
					Thread.sleep(1);
				} catch(InterruptedException e) {
					// Nom nom
				}
			} else {
				Log.w("SDL", "SDL audio: error return from write(short)");
				return;
			}
		}
	}

	public static void audioQuit() {
		if (mAudioThread != null) {
			try {
				mAudioThread.join();
			} catch(Exception e) {
				Log.v("SDL", "Problem stopping audio thread: " + e);
			}
			mAudioThread = null;

			//Log.v("SDL", "Finished waiting for audio thread");
		}

		if (mAudioTrack != null) {
			mAudioTrack.stop();
			mAudioTrack = null;
		}
	}
}




