// Written in the D programming language.

/**
This module contains implementation of SDL2 based backend for dlang library.


Synopsis:

----
import dlangui.platforms.sdl.sdlapp;

----

Copyright: Vadim Lopatin, 2014
License:   Boost License 1.0
Authors:   Vadim Lopatin, coolreader.org@gmail.com
*/
module dlangui.platforms.sdl.sdlapp;

version(USE_SDL):
import core.runtime;
import std.conv;
import std.string;
import std.utf;
import std.stdio;
import std.algorithm;
import std.file;

import dlangui.core.logger;
import dlangui.core.events;
import dlangui.core.files;
import dlangui.graphics.drawbuf;
import dlangui.graphics.fonts;
import dlangui.graphics.ftfonts;
import dlangui.graphics.resources;
import dlangui.widgets.styles;
import dlangui.widgets.widget;
import dlangui.platforms.common.platform;

import derelict.sdl2.sdl;
import derelict.opengl3.gl3;

version (USE_OPENGL) {
    import dlangui.graphics.gldrawbuf;
	import dlangui.graphics.glsupport;
}
	

//	pragma(lib, "xcb");
//	pragma(lib, "xcb-shm");
//	pragma(lib, "xcb-image");
//	pragma(lib, "X11-xcb");
//	pragma(lib, "X11");
//	pragma(lib, "dl");	
		
class SDLWindow : Window {
	SDLPlatform _platform;
	SDL_Window * _win;
	SDL_Renderer* _renderer;
	this(SDLPlatform platform, dstring caption, Window parent, uint flags) {
		_platform = platform;
		_caption = caption;
		debug Log.d("Creating SDL window");
		create(flags);
	}

	~this() {
		debug Log.d("Destroying SDL window");
		if (_renderer)
			SDL_DestroyRenderer(_renderer);
        version(USE_OPENGL) {
            if (_context)
                SDL_GL_DeleteContext(_context);
        }
		if (_win)
			SDL_DestroyWindow(_win);
		if (_drawbuf)
			destroy(_drawbuf);
	}

    version(USE_OPENGL) {
        static private bool _gl3Reloaded = false;
        private SDL_GLContext _context;
    }

	protected uint _flags;
	bool create(uint flags) {
		_flags = flags;
		uint windowFlags = SDL_WINDOW_HIDDEN;
		if (flags & WindowFlag.Resizable)
			windowFlags |= SDL_WINDOW_RESIZABLE;
		if (flags & WindowFlag.Fullscreen)
			windowFlags |= SDL_WINDOW_FULLSCREEN;
		// TODO: implement modal behavior
		//if (flags & WindowFlag.Modal)
		//	windowFlags |= SDL_WINDOW_INPUT_GRABBED;
		version(USE_OPENGL) {
            if (_enableOpengl)
                windowFlags |= SDL_WINDOW_OPENGL;
        }
		_win = SDL_CreateWindow(toUTF8(_caption).toStringz, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 
                                700, 500, 
                                windowFlags);
        version(USE_OPENGL) {
            if (!_win) {
                if (_enableOpengl) {
                    Log.e("SDL_CreateWindow failed - cannot create OpenGL window: ", fromStringz(SDL_GetError()));
                    _enableOpengl = false;
                    // recreate w/o OpenGL
                    windowFlags &= ~SDL_WINDOW_OPENGL;
					_win = SDL_CreateWindow(toUTF8(_caption).toStringz, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 
                                            700, 500, 
                                            windowFlags);
                }
            }
        }
		if (!_win) {
			Log.e("SDL2: Failed to create window");
			return false;
		}
        version(USE_OPENGL) {
            if (_enableOpengl) {
                _context = SDL_GL_CreateContext(_win); // Create the actual context and make it current
                if (!_context) {
                    Log.e("SDL_GL_CreateContext failed: ", fromStringz(SDL_GetError()));
                    _enableOpengl = false;
                } else if (!_gl3Reloaded) {
                    DerelictGL3.reload();
                    _gl3Reloaded = true;
                    if (!initShaders())
                        _enableOpengl = false;
                }
            }
        }
        if (!_enableOpengl) {
			_renderer = SDL_CreateRenderer(_win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
			if (!_renderer) {
				Log.e("SDL2: Failed to create renderer");
				return false;
			}
        }
		windowCaption = _caption;
		return true;
	}
		
	@property uint windowId() {
        if (_win)
			return SDL_GetWindowID(_win);
        return 0;
	}

		
	override void show() {
		Log.d("SDLWindow.show()");
		if (_mainWidget && !(_flags & WindowFlag.Resizable)) {
			_mainWidget.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED);
			SDL_SetWindowSize(_win, _mainWidget.measuredWidth, _mainWidget.measuredHeight);
		}
		SDL_ShowWindow(_win);
	}

	/// close window
	override void close() {
		Log.d("SDLWindow.close()");
		_platform.closeWindow(this);
	}


	protected dstring _caption;

	override @property dstring windowCaption() {
		return _caption;
	}

	override @property void windowCaption(dstring caption) {
		_caption = caption;
		if (_win)
			SDL_SetWindowTitle(_win, toUTF8(_caption).toStringz);
	}

	/// sets window icon
	@property override void windowIcon(DrawBufRef buf) {
		ColorDrawBuf icon = cast(ColorDrawBuf)buf.get;
		if (!icon) {
			Log.e("Trying to set null icon for window");
			return;
		}
        int iconw = 32;
        int iconh = 32;
        ColorDrawBuf iconDraw = new ColorDrawBuf(iconw, iconh);
        iconDraw.fill(0xE0E0E0);
        iconDraw.drawRescaled(Rect(0, 0, iconw, iconh), icon, Rect(0, 0, icon.width, icon.height));
		iconDraw.invertAlpha();
		SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(iconDraw.scanLine(0), iconDraw.width, iconDraw.height, 32, iconDraw.width * 4, 0x00ff0000,0x0000ff00,0x000000ff,0xff000000);
		if (surface) {
			// The icon is attached to the window pointer
			SDL_SetWindowIcon(_win, surface); 
			// ...and the surface containing the icon pixel data is no longer required.
			SDL_FreeSurface(surface);
		} else {
			Log.e("failed to set window icon");
		}
		destroy(iconDraw);
	}

	/// after drawing, call to schedule redraw if animation is active
	override void scheduleAnimation() {
		invalidate();
	}

	protected uint _lastCursorType = CursorType.None;
	protected SDL_Cursor * [uint] _cursorMap;
	/// sets cursor type for window
	override protected void setCursorType(uint cursorType) {
		// override to support different mouse cursors
		if (_lastCursorType != cursorType) {
			if (cursorType == CursorType.None) {
				SDL_ShowCursor(SDL_DISABLE);
				return;
			}
			if (_lastCursorType == CursorType.None)
				SDL_ShowCursor(SDL_ENABLE);
			_lastCursorType = cursorType;
			SDL_Cursor * cursor;
			// check for existing cursor in map
			if (cursorType in _cursorMap) {
				//Log.d("changing cursor to ", cursorType);
				cursor = _cursorMap[cursorType];
				if (cursor)
					SDL_SetCursor(cursor);
				return;
			}
			// create new cursor
			switch (cursorType) {
				case CursorType.Arrow:
					cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
					break;
				case CursorType.IBeam:
					cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
					break;
				case CursorType.Wait:
					cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT);
					break;
				case CursorType.WaitArrow:
					cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAITARROW);
					break;
				case CursorType.Crosshair:
					cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
					break;
				case CursorType.No:
					cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO);
					break;
				case CursorType.Hand:
					cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
					break;
				case CursorType.SizeNWSE:
					cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
					break;
				case CursorType.SizeNESW:
					cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
					break;
				case CursorType.SizeWE:
					cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
					break;
				case CursorType.SizeNS:
					cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
					break;
				case CursorType.SizeAll:
					cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
					break;
				default:
					// TODO: support custom cursors
					cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
					break;
			}
			_cursorMap[cursorType] = cursor;
			if (cursor) {
				Log.d("changing cursor to ", cursorType);
				SDL_SetCursor(cursor);
			}
		}
	}

    SDL_Texture * _texture;
    int _txw;
    int _txh;
    private void updateBufferSize() {
        if (_texture && (_txw != _dx || _txh != _dy)) {
            SDL_DestroyTexture(_texture);
            _texture = null;
        }
        if (!_texture) {
            _texture = SDL_CreateTexture(_renderer,
                                        SDL_PIXELFORMAT_ARGB8888,
                                        SDL_TEXTUREACCESS_STATIC, //SDL_TEXTUREACCESS_STREAMING,
                                        _dx,
                                        _dy);
            _txw = _dx;
            _txh = _dy;
        }
    }

	private void draw(ColorDrawBuf buf) {
        updateBufferSize();
        SDL_Rect rect;
        rect.w = buf.width;
        rect.h = buf.height;
        SDL_UpdateTexture(_texture,
                            &rect,
                            cast(const void*)buf.scanLine(0),
                            buf.width * cast(int)uint.sizeof);
        SDL_RenderCopy(_renderer, _texture, &rect, &rect);
	}

	void redraw() {
        //Log.e("Widget instance count in SDLWindow.redraw: ", Widget.instanceCount());
        // check if size has been changed
        int w, h;
        SDL_GetWindowSize(_win, &w, &h);
        onResize(w, h);

		if (_enableOpengl) {
            version(USE_OPENGL) {
                SDL_GL_MakeCurrent(_win, _context);
                glDisable(GL_DEPTH_TEST);
                glViewport(0, 0, _dx, _dy);
                float a = 1.0f;
                float r = ((_backgroundColor >> 16) & 255) / 255.0f;
                float g = ((_backgroundColor >> 8) & 255) / 255.0f;
                float b = ((_backgroundColor >> 0) & 255) / 255.0f;
                glClearColor(r, g, b, a);
                glClear(GL_COLOR_BUFFER_BIT);
                GLDrawBuf buf = new GLDrawBuf(_dx, _dy, false);
                buf.beforeDrawing();
                onDraw(buf);
                buf.afterDrawing();
                SDL_GL_SwapWindow(_win);
                destroy(buf);
            }
		} else {
            // Select the color for drawing.
            ubyte r = cast(ubyte)((_backgroundColor >> 16) & 255);
            ubyte g = cast(ubyte)((_backgroundColor >> 8) & 255);
            ubyte b = cast(ubyte)((_backgroundColor >> 0) & 255);
            SDL_SetRenderDrawColor(_renderer, r, g, b, 255);
            // Clear the entire screen to our selected color.
            SDL_RenderClear(_renderer);

			if (!_drawbuf)
				_drawbuf = new ColorDrawBuf(_dx, _dy);
			_drawbuf.resize(_dx, _dy);
            _drawbuf.resetClipping();
			_drawbuf.fill(_backgroundColor);
			onDraw(_drawbuf);
			draw(_drawbuf);

            // Up until now everything was drawn behind the scenes.
            // This will show the new, red contents of the window.
            SDL_RenderPresent(_renderer);
		}
	}
		
	ColorDrawBuf _drawbuf;

	//bool _exposeSent;
	void processExpose() {
		redraw();
		//_exposeSent = false;
	}

	protected ButtonDetails _lbutton;
	protected ButtonDetails _mbutton;
	protected ButtonDetails _rbutton;
    ushort convertMouseFlags(uint flags) {
        ushort res = 0;
        if (flags & SDL_BUTTON_LMASK)
            res |= MouseFlag.LButton;
        if (flags & SDL_BUTTON_RMASK)
            res |= MouseFlag.RButton;
        if (flags & SDL_BUTTON_MMASK)
            res |= MouseFlag.MButton;
        return res;
    }

    MouseButton convertMouseButton(uint button) {
        if (button == SDL_BUTTON_LEFT)
            return MouseButton.Left;
        if (button == SDL_BUTTON_RIGHT)
            return MouseButton.Right;
        if (button == SDL_BUTTON_MIDDLE)
            return MouseButton.Middle;
        return MouseButton.None;
    }

    ushort lastFlags;
    short lastx;
    short lasty;
	void processMouseEvent(MouseAction action, uint button, uint state, int x, int y) {
        MouseEvent event = null;
        if (action == MouseAction.Wheel) {
            // handle wheel
            short wheelDelta = cast(short)y;
            if (wheelDelta)
                event = new MouseEvent(action, MouseButton.None, lastFlags, lastx, lasty, wheelDelta);
        } else {
            lastFlags = convertMouseFlags(state);
            lastx = cast(short)x;
            lasty = cast(short)y;
            MouseButton btn = convertMouseButton(button);
            event = new MouseEvent(action, btn, lastFlags, lastx, lasty);
        }
        if (event) {
            ButtonDetails * pbuttonDetails = null;
            if (button == MouseButton.Left)
                pbuttonDetails = &_lbutton;
            else if (button == MouseButton.Right)
                pbuttonDetails = &_rbutton;
            else if (button == MouseButton.Middle)
                pbuttonDetails = &_mbutton;
            if (pbuttonDetails) {
                if (action == MouseAction.ButtonDown) {
                    pbuttonDetails.down(cast(short)x, cast(short)y, lastFlags);
                } else if (action == MouseAction.ButtonUp) {
                    pbuttonDetails.up(cast(short)x, cast(short)y, lastFlags);
                }
            }
            event.lbutton = _lbutton;
            event.rbutton = _rbutton;
            event.mbutton = _mbutton;
		    bool res = dispatchMouseEvent(event);
	        if (res) {
	            debug(mouse) Log.d("Calling update() after mouse event");
	            invalidate();
	        }
        }
	}

	uint convertKeyCode(uint keyCode) {
        switch(keyCode) {
            case SDLK_0:
                return KeyCode.KEY_0;
            case SDLK_1:
                return KeyCode.KEY_1;
            case SDLK_2:
                return KeyCode.KEY_2;
            case SDLK_3:
                return KeyCode.KEY_3;
            case SDLK_4:
                return KeyCode.KEY_4;
            case SDLK_5:
                return KeyCode.KEY_5;
            case SDLK_6:
                return KeyCode.KEY_6;
            case SDLK_7:
                return KeyCode.KEY_7;
            case SDLK_8:
                return KeyCode.KEY_8;
            case SDLK_9:
                return KeyCode.KEY_9;
            case SDLK_a:
                return KeyCode.KEY_A;
            case SDLK_b:
                return KeyCode.KEY_B;
            case SDLK_c:
                return KeyCode.KEY_C;
            case SDLK_d:
                return KeyCode.KEY_D;
            case SDLK_e:
                return KeyCode.KEY_E;
            case SDLK_f:
                return KeyCode.KEY_F;
            case SDLK_g:
                return KeyCode.KEY_G;
            case SDLK_h:
                return KeyCode.KEY_H;
            case SDLK_i:
                return KeyCode.KEY_I;
            case SDLK_j:
                return KeyCode.KEY_J;
            case SDLK_k:
                return KeyCode.KEY_K;
            case SDLK_l:
                return KeyCode.KEY_L;
            case SDLK_m:
                return KeyCode.KEY_M;
            case SDLK_n:
                return KeyCode.KEY_N;
            case SDLK_o:
                return KeyCode.KEY_O;
            case SDLK_p:
                return KeyCode.KEY_P;
            case SDLK_q:
                return KeyCode.KEY_Q;
            case SDLK_r:
                return KeyCode.KEY_R;
            case SDLK_s:
                return KeyCode.KEY_S;
            case SDLK_t:
                return KeyCode.KEY_T;
            case SDLK_u:
                return KeyCode.KEY_U;
            case SDLK_v:
                return KeyCode.KEY_V;
            case SDLK_w:
                return KeyCode.KEY_W;
            case SDLK_x:
                return KeyCode.KEY_X;
            case SDLK_y:
                return KeyCode.KEY_Y;
            case SDLK_z:
                return KeyCode.KEY_Z;
            case SDLK_F1:
                return KeyCode.F1;
            case SDLK_F2:
                return KeyCode.F2;
            case SDLK_F3:
                return KeyCode.F3;
            case SDLK_F4:
                return KeyCode.F4;
            case SDLK_F5:
                return KeyCode.F5;
            case SDLK_F6:
                return KeyCode.F6;
            case SDLK_F7:
                return KeyCode.F7;
            case SDLK_F8:
                return KeyCode.F8;
            case SDLK_F9:
                return KeyCode.F9;
            case SDLK_F10:
                return KeyCode.F10;
            case SDLK_F11:
                return KeyCode.F11;
            case SDLK_F12:
                return KeyCode.F12;
            case SDLK_F13:
                return KeyCode.F13;
            case SDLK_F14:
                return KeyCode.F14;
            case SDLK_F15:
                return KeyCode.F15;
            case SDLK_F16:
                return KeyCode.F16;
            case SDLK_F17:
                return KeyCode.F17;
            case SDLK_F18:
                return KeyCode.F18;
            case SDLK_F19:
                return KeyCode.F19;
            case SDLK_F20:
                return KeyCode.F20;
            case SDLK_F21:
                return KeyCode.F21;
            case SDLK_F22:
                return KeyCode.F22;
            case SDLK_F23:
                return KeyCode.F23;
            case SDLK_F24:
                return KeyCode.F24;
            case SDLK_BACKSPACE:
                return KeyCode.BACK;
            case SDLK_TAB:
                return KeyCode.TAB;
            case SDLK_RETURN:
                return KeyCode.RETURN;
            case SDLK_ESCAPE:
                return KeyCode.ESCAPE;
            case SDLK_DELETE:
			case 0x40000063: // dirty hack for Linux - key on keypad
				return KeyCode.DEL;
			case SDLK_INSERT:
			case 0x40000062: // dirty hack for Linux - key on keypad
				return KeyCode.INS;
            case SDLK_HOME:
			case 0x4000005f: // dirty hack for Linux - key on keypad
				return KeyCode.HOME;
            case SDLK_PAGEUP:
			case 0x40000061: // dirty hack for Linux - key on keypad
				return KeyCode.PAGEUP;
            case SDLK_END:
			case 0x40000059: // dirty hack for Linux - key on keypad
                return KeyCode.END;
            case SDLK_PAGEDOWN:
			case 0x4000005b: // dirty hack for Linux - key on keypad
				return KeyCode.PAGEDOWN;
            case SDLK_LEFT:
			case 0x4000005c: // dirty hack for Linux - key on keypad
				return KeyCode.LEFT;
            case SDLK_RIGHT:
			case 0x4000005e: // dirty hack for Linux - key on keypad
				return KeyCode.RIGHT;
            case SDLK_UP:
			case 0x40000060: // dirty hack for Linux - key on keypad
				return KeyCode.UP;
            case SDLK_DOWN:
			case 0x4000005a: // dirty hack for Linux - key on keypad
				return KeyCode.DOWN;
            case SDLK_LCTRL:
                return KeyCode.LCONTROL;
            case SDLK_LSHIFT:
                return KeyCode.LSHIFT;
            case SDLK_LALT:
                return KeyCode.LALT;
            case SDLK_RCTRL:
                return KeyCode.RCONTROL;
            case SDLK_RSHIFT:
                return KeyCode.RSHIFT;
            case SDLK_RALT:
                return KeyCode.RALT;
            default:
                return 0x10000 | keyCode;
        }
	}
		
	uint convertKeyFlags(uint flags) {
        uint res;
        if (flags & KMOD_CTRL)
            res |= KeyFlag.Control;
        if (flags & KMOD_SHIFT)
            res |= KeyFlag.Shift;
        if (flags & KMOD_ALT)
            res |= KeyFlag.Alt;
        if (flags & KMOD_RCTRL)
			res |= KeyFlag.RControl | KeyFlag.Control;
        if (flags & KMOD_RSHIFT)
			res |= KeyFlag.RShift | KeyFlag.Shift;
        if (flags & KMOD_RALT)
			res |= KeyFlag.RAlt | KeyFlag.Alt;
        if (flags & KMOD_LCTRL)
			res |= KeyFlag.LControl | KeyFlag.Control;
        if (flags & KMOD_LSHIFT)
			res |= KeyFlag.LShift | KeyFlag.Shift;
        if (flags & KMOD_LALT)
			res |= KeyFlag.LAlt | KeyFlag.Alt;
        return res;
	}
				
    bool processTextInput(const char * s) {
        string str = fromStringz(s).dup;
        dstring ds = toUTF32(str);
        uint flags = convertKeyFlags(SDL_GetModState());
        bool res = dispatchKeyEvent(new KeyEvent(KeyAction.Text, 0, flags, ds));
        if (res) {
            Log.d("Calling update() after text event");
            invalidate();
        }
        return res;
    }

	bool processKeyEvent(KeyAction action, uint keyCode, uint flags) {
		Log.d("processKeyEvent ", action, " SDL key=0x", format("%08x", keyCode), " SDL flags=0x", format("%08x", flags));
		keyCode = convertKeyCode(keyCode);
		flags = convertKeyFlags(flags);
		if (action == KeyAction.KeyDown) {
			switch(keyCode) {
				case KeyCode.ALT:
					flags |= KeyFlag.Alt;
					break;
				case KeyCode.RALT:
					flags |= KeyFlag.Alt | KeyFlag.RAlt;
					break;
				case KeyCode.LALT:
					flags |= KeyFlag.Alt | KeyFlag.LAlt;
					break;
				case KeyCode.CONTROL:
					flags |= KeyFlag.Control;
					break;
				case KeyCode.RCONTROL:
					flags |= KeyFlag.Control | KeyFlag.RControl;
					break;
				case KeyCode.LCONTROL:
					flags |= KeyFlag.Control | KeyFlag.LControl;
					break;
				case KeyCode.SHIFT:
					flags |= KeyFlag.Shift;
					break;
				case KeyCode.RSHIFT:
					flags |= KeyFlag.Shift | KeyFlag.RShift;
					break;
				case KeyCode.LSHIFT:
					flags |= KeyFlag.Shift | KeyFlag.LShift;
					break;
				default:
					break;
			}
		}

		Log.d("processKeyEvent ", action, " converted key=0x", format("%08x", keyCode), " converted flags=0x", format("%08x", flags));
		bool res = dispatchKeyEvent(new KeyEvent(action, keyCode, flags));
//			if ((keyCode & 0x10000) && (keyCode & 0xF000) != 0xF000) {
//				dchar[1] text;
//				text[0] = keyCode & 0xFFFF;
//				res = dispatchKeyEvent(new KeyEvent(KeyAction.Text, keyCode, flags, cast(dstring)text)) || res;
//			}
	    if (res) {
	        Log.d("Calling update() after key event");
	        invalidate();
	    }
		return res;
	}

	uint _lastRedrawEventCode;
	/// request window redraw
	override void invalidate() {
		_platform.sendRedrawEvent(windowId, ++_lastRedrawEventCode);
	}
		
	void processRedrawEvent(uint code) {
		if (code == _lastRedrawEventCode)
			redraw();
	}
}

private __gshared bool _enableOpengl;

class SDLPlatform : Platform {
	this() {
			
	}
	~this() {
		foreach(ref SDLWindow wnd; _windowMap) {
			destroy(wnd);
			wnd = null;
		}
		destroy(_windowMap);
		disconnect();
	}
	void disconnect() {
		/* Cleanup */
	}
	bool connect() {
			

		return true;
	}

	SDLWindow getWindow(uint id) {
		if (id in _windowMap)
			return _windowMap[id];
		return null;
	}

	SDLWindow _windowToClose;

	/// close window
	override void closeWindow(Window w) {
		SDLWindow window = cast(SDLWindow)w;
		_windowToClose = window;
	}

	/// calls request layout for all windows
	override void requestLayout() {
		foreach(w; _windowMap) {
			w.requestLayout();
			w.invalidate();
		}
	}


	private uint _redrawEventId;

	void sendRedrawEvent(uint windowId, uint code) {
		if (!_redrawEventId)
			_redrawEventId = SDL_RegisterEvents(1);
		SDL_Event event;
		event.type = _redrawEventId;
		event.user.windowID = windowId;
		event.user.code = code;
		SDL_PushEvent(&event);

	}

	override Window createWindow(dstring windowCaption, Window parent, uint flags = WindowFlag.Resizable) {
		SDLWindow res = new SDLWindow(this, windowCaption, parent, flags);
		_windowMap[res.windowId] = res;
		return res;
	}

    //void redrawWindows() {
    //    foreach(w; _windowMap)
    //        w.redraw();
    //}

	override int enterMessageLoop() {
		Log.i("entering message loop");
		SDL_Event event;
		bool quit = false;
        while(!quit) {
			//redrawWindows();

			//if (SDL_PollEvent(&event)) {
			if (SDL_WaitEvent(&event)) {

				//Log.d("Event.type = ", event.type);

				if (event.type == SDL_QUIT) {
					Log.i("event.type == SDL_QUIT");
					quit = true;
					break;
				} 
				if (_redrawEventId && event.type == _redrawEventId) {
					// user defined redraw event
					uint windowID = event.user.windowID;
					SDLWindow w = getWindow(windowID);
					if (w) {
						w.processRedrawEvent(event.user.code);
					}
					continue;
				}
                switch (event.type) {
                    case SDL_WINDOWEVENT:
                    {
                        // WINDOW EVENTS
                        uint windowID = event.window.windowID;
                        SDLWindow w = getWindow(windowID);
                        if (!w) {
                            Log.w("SDL_WINDOWEVENT ", event.window.event, " received with unknown id ", windowID);
                            break;
                        }
                        // found window
                        switch (event.window.event) {
                            case SDL_WINDOWEVENT_RESIZED:
                                Log.d("SDL_WINDOWEVENT_RESIZED win=", event.window.windowID, " pos=", event.window.data1,
                                        ",", event.window.data2);
                                w.onResize(event.window.data1, event.window.data2);
                                w.redraw();
                                break;
                            case SDL_WINDOWEVENT_SIZE_CHANGED:
                                Log.d("SDL_WINDOWEVENT_SIZE_CHANGED win=", event.window.windowID, " pos=", event.window.data1,
                                        ",", event.window.data2);
                                w.onResize(event.window.data1, event.window.data2);
                                w.redraw();
                                break;
                            case SDL_WINDOWEVENT_CLOSE:
                                Log.d("SDL_WINDOWEVENT_CLOSE win=", event.window.windowID);
                                _windowMap.remove(windowID);
                                destroy(w);
                                break;
                            case SDL_WINDOWEVENT_SHOWN:
                                Log.d("SDL_WINDOWEVENT_SHOWN");
                                break;
                            case SDL_WINDOWEVENT_HIDDEN:
                                Log.d("SDL_WINDOWEVENT_HIDDEN");
                                break;
                            case SDL_WINDOWEVENT_EXPOSED:
                                Log.d("SDL_WINDOWEVENT_EXPOSED");
                                w.redraw();
                                break;
                            case SDL_WINDOWEVENT_MOVED:
                                Log.d("SDL_WINDOWEVENT_MOVED");
                                break;
                            case SDL_WINDOWEVENT_MINIMIZED:
                                Log.d("SDL_WINDOWEVENT_MINIMIZED");
                                break;
                            case SDL_WINDOWEVENT_MAXIMIZED:
                                Log.d("SDL_WINDOWEVENT_MAXIMIZED");
                                break;
                            case SDL_WINDOWEVENT_RESTORED:
								Log.d("SDL_WINDOWEVENT_RESTORED");
                                break;
                            case SDL_WINDOWEVENT_ENTER:
								Log.d("SDL_WINDOWEVENT_ENTER");
                                break;
                            case SDL_WINDOWEVENT_LEAVE:
								Log.d("SDL_WINDOWEVENT_LEAVE");
                                break;
                            case SDL_WINDOWEVENT_FOCUS_GAINED:
								Log.d("SDL_WINDOWEVENT_FOCUS_GAINED");
                                break;
                            case SDL_WINDOWEVENT_FOCUS_LOST:
								Log.d("SDL_WINDOWEVENT_FOCUS_LOST");
                                break;
                            default:
                                break;
                        }
                        break;
                    }
                    case SDL_KEYDOWN:
                        SDLWindow w = getWindow(event.key.windowID);
                        if (w) {
                            w.processKeyEvent(KeyAction.KeyDown, event.key.keysym.sym, event.key.keysym.mod);
                            SDL_StartTextInput();
                        }
                        break;
                    case SDL_KEYUP:
                        SDLWindow w = getWindow(event.key.windowID);
                        if (w) {
                            w.processKeyEvent(KeyAction.KeyUp, event.key.keysym.sym, event.key.keysym.mod);
                        }
                        break;
                    case SDL_TEXTEDITING:
                        Log.d("SDL_TEXTEDITING");
                        break;
                    case SDL_TEXTINPUT:
                        Log.d("SDL_TEXTINPUT");
                        SDLWindow w = getWindow(event.text.windowID);
                        if (w) {
                            w.processTextInput(event.text.text.ptr);
                        }
                        break;
                    case SDL_MOUSEMOTION:
                        SDLWindow w = getWindow(event.motion.windowID);
                        if (w) {
                            w.processMouseEvent(MouseAction.Move, 0, event.motion.state, event.motion.x, event.motion.y);
                        }
                        break;
                    case SDL_MOUSEBUTTONDOWN:
                        SDLWindow w = getWindow(event.button.windowID);
                        if (w) {
                            w.processMouseEvent(MouseAction.ButtonDown, event.button.button, event.button.state, event.button.x, event.button.y);
                        }
                        break;
                    case SDL_MOUSEBUTTONUP:
                        SDLWindow w = getWindow(event.button.windowID);
                        if (w) {
                            w.processMouseEvent(MouseAction.ButtonUp, event.button.button, event.button.state, event.button.x, event.button.y);
                        }
                        break;
                    case SDL_MOUSEWHEEL:
                        SDLWindow w = getWindow(event.wheel.windowID);
                        if (w) {
                            Log.d("SDL_MOUSEWHEEL x=", event.wheel.x, " y=", event.wheel.y);
                            w.processMouseEvent(MouseAction.Wheel, 0, 0, event.wheel.x, event.wheel.y);
                        }
                        break;
                    default:
                        // not supported event
                        break;
                }
				if (_windowToClose) {
					if (_windowToClose.windowId in _windowMap) {
						Log.i("Platform.closeWindow()");
						_windowMap.remove(_windowToClose.windowId);
						SDL_DestroyWindow(_windowToClose._win);
						Log.i("windowMap.length=", _windowMap.length);
						destroy(_windowToClose);
					}
					_windowToClose = null;
				}
				//
				if (_windowMap.length == 0) {
					//quit = true;
					SDL_Quit();
					quit = true;
				}
			}
		}
		Log.i("exiting message loop");
		return 0;
	}
		
    /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
	override dstring getClipboardText(bool mouseBuffer = false) {
        if (!SDL_HasClipboardText())
			return ""d;
        char * txt = SDL_GetClipboardText();
        if (!txt)
            return ""d;
        string s = fromStringz(txt).dup;
        SDL_free(txt);
        return toUTF32(s);
	}
		
    /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
	override void setClipboardText(dstring text, bool mouseBuffer = false) {
        string s = toUTF8(text);
        SDL_SetClipboardText(s.toStringz);
	}
		
	protected SDLWindow[uint] _windowMap;
}

// entry point
extern(C) int UIAppMain(string[] args);

version (Windows) {
    import win32.windows;
    import dlangui.platforms.windows.win32fonts;
    pragma(lib, "gdi32.lib");
    pragma(lib, "user32.lib");
    extern(Windows)
        int DLANGUIWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                LPSTR lpCmdLine, int nCmdShow) 
        {
            int result;

            try
            {
                Runtime.initialize();
                result = myWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
                Log.i("calling Runtime.terminate()");
                Runtime.terminate();
            }
            catch (Throwable e) // catch any uncaught exceptions
            {
                MessageBoxW(null, toUTF16z(e.toString ~ "\nStack trace:\n" ~ defaultTraceHandler.toString), "Error",
                            MB_OK | MB_ICONEXCLAMATION);
                result = 0;     // failed
            }

            return result;
        }

    string[] splitCmdLine(string line) {
        string[] res;
        int start = 0;
        bool insideQuotes = false;
        for (int i = 0; i <= line.length; i++) {
            char ch = i < line.length ? line[i] : 0;
            if (ch == '\"') {
                if (insideQuotes) {
                    if (i > start)
                        res ~= line[start .. i];
                    start = i + 1;
                    insideQuotes = false;
                } else {
                    insideQuotes = true;
                    start = i + 1;
                }
            } else if (!insideQuotes && (ch == ' ' || ch == '\t' || ch == 0)) {
                if (i > start) {
                    res ~= line[start .. i];
                }
                start = i + 1;
            }
        }
        return res;
    }

    int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
    {
		Log.setFileLogger(std.stdio.File("ui.log", "w"));
        Log.setLogLevel(LogLevel.Trace);
        Log.d("myWinMain()");
        string basePath = exePath();
        Log.i("Current executable: ", exePath());
        string cmdline = fromStringz(lpCmdLine).dup;
        Log.i("Command line: ", cmdline);
        string[] args = splitCmdLine(cmdline);
        Log.i("Command line params: ", args);


        //_cmdShow = iCmdShow;
        //_hInstance = hInstance;

        FontManager.instance = new Win32FontManager();

        return sdlmain(args);
    }
} else {

	int main(string[] args)
	{
		
		Log.setStderrLogger();
		Log.setLogLevel(LogLevel.Warn);


		FreeTypeFontManager ft = new FreeTypeFontManager();
		// TODO: use FontConfig
		ft.registerFont("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", FontFamily.SansSerif, "DejaVu", false, FontWeight.Normal);
		FontManager.instance = ft;

        return sdlmain(args);
	}
}

int sdlmain(string[] args) {
    currentTheme = createDefaultTheme();

    try {
        // Load the SDL 2 library.
        DerelictSDL2.load();
    } catch (Exception e) {
        Log.e("Cannot load SDL2 library", e);
        return 1;
    }

    version(USE_OPENGL) {
        try {
            DerelictGL3.load();
            _enableOpengl = true;
        } catch (Exception e) {
            Log.e("Cannot load opengl library", e);
        }
    }

    SDL_DisplayMode displayMode;
    if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_EVENTS) != 0) {
        Log.e("Cannot init SDL2");	
        return 2;
    }
    scope(exit)SDL_Quit();
    int request = SDL_GetDesktopDisplayMode(0,&displayMode);

    version(USE_OPENGL) {
        // we want OpenGL 3.3
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,3);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,2);
        // Set OpenGL attributes
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
        SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
        // Share textures between contexts
        SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
    }

    SDLPlatform sdl = new SDLPlatform();
    if (!sdl.connect()) {
        return 1;
    }
    Platform.setInstance(sdl);

    int res = 0;

    res = UIAppMain(args);
    //Log.e("Widget instance count after UIAppMain: ", Widget.instanceCount());

    Log.d("Destroying SDL platform");
    Platform.setInstance(null);

    //
	debug(resalloc) {
        Widget.shuttingDown();
    }

    currentTheme = null;
    drawableCache = null;
    imageCache = null;
    FontManager.instance = null;
    debug(resalloc) {
		if (DrawBuf.instanceCount > 0) {
			Log.e("Non-zero DrawBuf instance count when exiting: ", DrawBuf.instanceCount);
		}
		if (Style.instanceCount > 0) {
			Log.e("Non-zero Style instance count when exiting: ", Style.instanceCount);
		}
		if (Widget.instanceCount() > 0) {
            Log.e("Non-zero Widget instance count when exiting: ", Widget.instanceCount);
        }
		if (ImageDrawable.instanceCount > 0) {
			Log.e("Non-zero ImageDrawable instance count when exiting: ", ImageDrawable.instanceCount);
		}
    }
    Log.d("Exiting main");

    return res;
}
