/*==============================================================================

 Copyright (c) 2011, 2012 Christopher Baker <http://christopherbaker.net>

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.

 modified by Philip Whitfield (http://www.undef.ch)

 ==============================================================================*/

#pragma once

#include "ofBaseApp.h"

#include "ofAppBaseWindow.h"
#include "ofThread.h"
#include "ofImage.h"

// include includes for both native and X11 possibilities
#include <libudev.h>
#include <stdbool.h>
#include <stdio.h> // sprintf
#include <stdlib.h>  // malloc
#include <math.h>
#include <fcntl.h>  // open fcntl
#include <unistd.h> // read close 
#include <linux/joystick.h>

#include "linux/kd.h"	// keyboard stuff...
#include "termios.h"
#include "sys/ioctl.h"

#include <dirent.h>  // scandir
#include <string.h> // strlen

// x11
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include <queue>
#include <map>

// TODO: this shold be passed in with the other window settings, like window alpha, etc.
enum ofAppEGLWindowType {
	OF_APP_WINDOW_AUTO,
	OF_APP_WINDOW_NATIVE,
	OF_APP_WINDOW_X11
};

typedef map<EGLint,EGLint> ofEGLAttributeList;
typedef map<EGLint,EGLint>::iterator ofEGLAttributeListIterator;

class ofAppEGLWindow : public ofAppBaseWindow, public ofThread {
public:

	struct Settings;

	ofAppEGLWindow();
	ofAppEGLWindow(Settings settings);
	virtual ~ofAppEGLWindow();

	void exit(ofEventArgs &e);

    void setGLESVersion(int glesVersion);
	virtual void setupOpenGL(int w, int h, int screenMode);

	virtual void initializeWindow();
	virtual void runAppViaInfiniteLoop(ofBaseApp * appPtr);

	virtual void hideCursor();
	virtual void showCursor();

	virtual void	setWindowPosition(int x, int y);
	virtual void	setWindowShape(int w, int h);

	virtual ofPoint	getWindowPosition();
	virtual ofPoint	getWindowSize();
	virtual ofPoint	getScreenSize();

	virtual void			setOrientation(ofOrientation orientation);
	virtual ofOrientation	getOrientation();
	virtual bool			doesHWOrientation();

	//this is used by ofGetWidth and now determines the window width based on orientation
	virtual int		getWidth();
	virtual int		getHeight();

	virtual void	setWindowTitle(string title); // TODO const correct

	virtual int		getWindowMode(); // TODO use enum

	virtual void	setFullscreen(bool fullscreen);
	virtual void	toggleFullscreen();

	virtual void	enableSetupScreen();
	virtual void	disableSetupScreen();

	virtual void	setVerticalSync(bool enabled);
	
	struct Settings {
		ofAppEGLWindowType eglWindowPreference;  // what window type is preferred?
		EGLint eglWindowOpacity; // 0-255 window alpha value

		ofEGLAttributeList frameBufferAttributes;
		// surface creation
		ofEGLAttributeList windowSurfaceAttributes;

		ofColor initialClearColor;

		int screenNum;
		int layer;

		Settings();
	};

	EGLDisplay getEglDisplay() const;
	EGLSurface getEglSurface() const;
	EGLContext getEglContext() const;

#ifndef TARGET_RASPBERRY_PI
	Display* 	getX11Display();
	Window  	getX11Window();
#endif

	EGLConfig  getEglConfig() const;

	EGLint getEglVersionMajor () const;
	EGLint getEglVersionMinor() const;


protected:
	void init(Settings settings = Settings());

	void idle();
	void display();

	void setWindowRect(const ofRectangle& requestedWindowRect);


//	bool create

	virtual void setupPeripherals();

	virtual ofRectangle getScreenRect();

	int getWindowWidth();
	int getWindowHeight();

	bool     terminate;

	int      windowMode;
	bool     bNewScreenMode;
	int      buttonInUse;
	bool     bEnableSetupScreen;
	bool	 bShowCursor;

	string   eglDisplayString;
	int      nFramesSinceWindowResized;
	ofOrientation orientation;
	ofBaseApp *  ofAppPtr;


	void threadedFunction();
	queue<ofMouseEventArgs> mouseEvents;
	queue<ofKeyEventArgs>   keyEvents;
	void checkEvents();
	ofImage mouseCursor;

	// TODO: getters and setters?  OR automatically set based on 
	// OS or screen size?  Should be changed when screen is resized?
	float mouseScaleX;
	float mouseScaleY;


	// float getMouseScaleX() const;
	// void setMouseScaleX(float x);
	// float getMouseScaleY() const;
	// void setMouseScaleY(float y);

//------------------------------------------------------------
// EGL
//------------------------------------------------------------

	bool createSurface();
	bool destroySurface();

	// bool resizeSurface();

	EGLDisplay eglDisplay;  // EGL display connection
	EGLSurface eglSurface;
	EGLContext eglContext;

    EGLConfig eglConfig;

	EGLint eglVersionMajor;
    EGLint eglVersionMinor;

//------------------------------------------------------------
// PLATFORM SPECIFIC WINDOWING
//------------------------------------------------------------
	
//------------------------------------------------------------
// WINDOWING
//------------------------------------------------------------
	// EGL window
	ofRectangle nonFullscreenWindowRect; // the rectangle describing the non-fullscreen window
	ofRectangle currentWindowRect; // the rectangle describing the current device

	bool createWindow(const ofRectangle& requestedWindowRect);
	bool destroyWindow();

	bool isUsingX11;

	bool isWindowInited;
	bool isSurfaceInited;

	void initNative();
	void exitNative();

	EGLNativeWindowType getNativeWindow();
	EGLNativeDisplayType getNativeDisplay();

#ifdef TARGET_RASPBERRY_PI
	void initRPiNative();
	void exitRPiNative();

	EGL_DISPMANX_WINDOW_T dispman_native_window; // rpi

	DISPMANX_UPDATE_HANDLE_T dispman_update;
    DISPMANX_ELEMENT_HANDLE_T dispman_element;
    DISPMANX_DISPLAY_HANDLE_T dispman_display;

	DISPMANX_CLAMP_T 		  dispman_clamp;
	DISPMANX_TRANSFORM_T 	  dispman_transform;
    VC_DISPMANX_ALPHA_T       dispman_alpha;
	
	bool createRPiNativeWindow(const ofRectangle& requestedWindowRect);

#else
	// if you are not raspberry pi, you will not be able to
	// create a window without using x11.
#endif

	Display*	x11Display;
	Screen*		x11Screen;
	Window		x11Window;
	long 		x11ScreenNum;
	bool createX11NativeWindow(const ofRectangle& requestedWindowRect);
	 
//------------------------------------------------------------
// EVENTS
//------------------------------------------------------------
	void setupNativeEvents();
	void destroyNativeEvents();

	void setupNativeUDev();
	void destroyNativeUDev();

	void setupNativeMouse();
	void setupNativeKeyboard();

	void destroyNativeMouse();
	void destroyNativeKeyboard();

	void readNativeMouseEvents();
	void readNativeKeyboardEvents();
	void readNativeUDevEvents();

	void handleX11Event(const XEvent& event);

private:
	Settings 			settings;
	int glesVersion;

};
