/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 */

package glass.mat;

import com.sun.glass.events.KeyEvent;
import com.sun.glass.events.MouseEvent;
import com.sun.glass.events.ViewEvent;
import com.sun.glass.events.WindowEvent;
import com.sun.glass.ui.*;

import java.util.Map;

public class Main implements Launchable {
    // verbosity tweakers...
    static boolean reportRunLoop = System.getProperty("main.runLoop", "false").equals("true");
    static boolean reportPaints = System.getProperty("main.paints", "false").equals("true");
    static boolean reportLocks = System.getProperty("main.locks", "false").equals("true");
    static boolean reportViewEvents = System.getProperty("main.locks", "false").equals("true");
    static boolean reportWindowEvents = System.getProperty("main.locks", "false").equals("true");
    static boolean reportHeartBeat = System.getProperty("main.heartbeat", "false").equals("true");
    static boolean reportWaitBeat = System.getProperty("main.waitbeat", "false").equals("true");
    static boolean reportTimer = System.getProperty("main.timer", "false").equals("true");
    static int heartBeatPS = Integer.parseInt(System.getProperty("main.heartbeatps", "5"));
    static int timerPS = Integer.parseInt(System.getProperty("main.timerps", "5"));

    static final String TITLE = "MAT-Main:";

    private Window window;
    private View view;
    private Timer pulseTimer;
    private static Thread eventThread = null;
    
    public static void main(String args[]) {
        try {
            //eventThread = Thread.currentThread();
            Application.Run(args, TITLE, new Main());
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    private static void checkThread(boolean on, String where) {
        boolean isOnEventThread = Application.checkEventThread(false,"");
        if (on && !isOnEventThread) {
            System.out.println("Warning: "+where+" not on EventThread!");
        } if (!on && isOnEventThread) {
            System.out.println("Warning: "+where+" should not on EventThread!");
        }
    }
    
    public void finishLaunching(String args[]) {
        System.out.println("In Application finishLaunching");
        System.out.println("number of args: " + args.length);
        for (int i = 0; i < args.length; i++) {
                System.out.println("args[" + i + "]: " + args[i]);
        }

        System.out.println("com.sun.glass.ui.Platform java.class.path is set to "+System.getProperty("java.class.path"));
        System.out.println("com.sun.glass.ui.Platform java.library.path is set to "+System.getProperty("java.library.path"));
        System.out.println("com.sun.glass.ui.Platform user.dir is set to "+System.getProperty("user.dir"));

        if (reportHeartBeat) {
            // used to test Application post event.
            System.out.println("Starting Heart Beat with "+heartBeatPS+" per sec");
            new Thread(new HeartBeat()).start();
        }

        if (reportWaitBeat) {
            // used to test Application post and wait event.
            System.out.println("Starting Wait Beat");
            new Thread(new WaitTest()).start();
        }

        if (reportTimer) {
            pulseTimer = Application.GetApplication().createTimer(new TimerBeat());
            pulseTimer.start(((int)1000/timerPS));
        }

        try {
            // called on native UI thread
            Application app = Application.GetApplication();

            app.invokeAndWait(new Runnable() {
                public void run() {
                    System.out.println("Yes, invoke and wait should not block the event thread!");
                    checkThread(true, "initial InvokeAndWait");
                }
            });
            
            view = app.createView(new MainViewPen());
            if (view == null)
                throw new RuntimeException("view should not be null");
            view.setEventHandler(new MainViewEventHandler());
            
            Screen screen = Screen.getMainScreen();
            if (screen == null)
                throw new RuntimeException("screen should not be null");
            System.out.println("Screen is "+screen);
            int x = (screen.getWidth() - view.getWidth()) / 2;
            int y = (screen.getHeight() - view.getHeight()) / 2;
            
            int windowMask = Window.TITLED|Window.CLOSABLE|Window.MINIMIZABLE;
            window = app.createWindow(screen, windowMask);
            if(window == null)
		throw new RuntimeException("window should not be null");
            window.setResizable(true);
            window.setEventHandler(new MainWindowEventHandler());
            window.setBackground(1.0f, 1.0f, 0.0f); // rgb
            window.setView(view);
            window.setPosition(x, y);
            window.setContentSize(512, 512);
            window.setTitle(app.getName());
            addMenus(app);
            window.setVisible(true);

            Timer fpsPulse = Application.GetApplication().createTimer(new Runnable() {
                @Override public void run() {
                    MainViewEventHandler eh = (MainViewEventHandler)view.getEventHandler();
                    eh.handleRunLoop(view, 0);
                }
            });
            fpsPulse.start((int)1000/30);

        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
    
    class MainViewPen extends Pen {
        @Override
        public Map getCapabilities() {
            return addCapability(null, View.Capability.k3dKey, Boolean.FALSE);
        }
        
        @Override
        public void paint(long time, int width, int height) {
            // pen is ready to draw here
            // normally the system and application painting happens on UI thread
            if (reportPaints)
                System.out.println(TITLE+"MainViewPen.paint()");
            checkThread(false, "pen.paint");
        }
    }
    
    private MenuItem action1Menu;
    private MenuItem action2Menu;
    void action1() {
        if (this.action2Menu.isChecked()) {
            this.action2Menu.setChecked(false);
            this.action1Menu.setTitle("set bottom menu checked");
        } else {
            this.action2Menu.setChecked(true);
            this.action1Menu.setTitle("set bottom menu unchecked");
        }
    }
    void action2() {
        if (this.action1Menu.isEnabled()) {
            this.action1Menu.setEnabled(false);
            this.action2Menu.setTitle("set top menu enabled");
        } else {
            this.action1Menu.setEnabled(true);
            this.action2Menu.setTitle("set top menu disabled");
        }
    }

    public void addMenus(Application app) {
        final Menu myMenu = app.createMenu(window.getTitle());

        action1Menu = app.createMenuItem("set bottom menu checked", new MenuItem.Callback() {
            @Override
            public void action() {
                action1();
            }
        });
        myMenu.add(action1Menu);

        action2Menu = app.createMenuItem("set top menu disabled", new MenuItem.Callback() {
            @Override
            public void action() {
                action2();
            }
        });
        myMenu.add(action2Menu);

        MenuBar myMenubar = app.createMenuBar(); // possibly should be created and installed by the App itself

        app.installDefaultMenus(myMenubar);

        myMenubar.add(myMenu);

        window.setMenuBar(myMenubar);
    }

    public class MainWindowEventHandler extends Window.EventHandler {
        public void handleWindowEvent(Window window, long time, int type) {
            if (!reportWindowEvents)
                return;
            switch(type) {
                case WindowEvent.CLOSE:
                    System.out.println(TITLE+"handleWindowEvent(CLOSE) ");
                    System.out.println("Calling Window.close()");
                    window.close();
                    break;
                case WindowEvent.DESTROY:
                    System.out.println(TITLE+"handleWindowEvent(DESTROY) ");
                    break;
                case WindowEvent.FOCUS_DISABLED:
                    System.out.println(TITLE+"handleWindowEvent(FOCUS_DISABLED)");
                    break;
                case WindowEvent.FOCUS_GAINED:
                    System.out.println(TITLE+"handleWindowEvent(FOCUS_GAINED)");
                    break;
                case WindowEvent.FOCUS_LOST:
                    System.out.println(TITLE+"handleWindowEvent(FOCUS_LOST)");
                    break;
                case WindowEvent.MAXIMIZE:
                    System.out.println(TITLE+"handleWindowEvent(MAXIMIZE)");
                    break;
                case WindowEvent.MINIMIZE:
                    System.out.println(TITLE+"handleWindowEvent(MINIMIZE)");
                    break;
                case WindowEvent.MOVE:
                    System.out.println(TITLE+"handleWindowEvent(MOVE) "+window.getX()+","+window.getY());
                    break;
                case WindowEvent.RESIZE:
                    System.out.println(TITLE+"handleWindowEvent(RESIZE) "+window.getWidth()+"X"+window.getHeight());
                    break;
                case WindowEvent.RESTORE:
                    System.out.println(TITLE+"handleWindowEvent(RESTORE)");
                    break;
            }
        }
    }

    public class MainViewEventHandler extends View.EventHandler {

        public void handleRunLoop(View view, long time) {
            // application paint
            if (reportRunLoop)
                System.out.println(TITLE+"handleRunLoop()");
            Pen pen = view.getPen();
            pen.begin();
            {
                pen.paint(time, view.getWidth(), view.getHeight());
            }
            pen.end();
        }

        @Override
        public void handleViewEvent(View view, long time, int type) {
            checkThread(true, "view.handleViewEvent");
            if (!reportViewEvents)
                return;
            switch (type) {
                case ViewEvent.RESIZE:
                    System.out.println(TITLE+"handleViewEvent(RESIZE)   " + view.getWidth() + "x" + view.getHeight());
                    break;
                case ViewEvent.ADD:
                    System.out.println(TITLE+"handleViewEvent(ADD) ");
                    break;
                case ViewEvent.REMOVE:
                    System.out.println(TITLE+"handleViewEvent(REMOVE) ");
                    break;
                case ViewEvent.REPAINT:
                    System.out.println(TITLE+"handleViewEvent(REPAINT) ");
                    break;
                case ViewEvent.LOCKED:
                    if (reportLocks) {
                        System.out.println(TITLE+"handleViewEvent(LOCKED) ");
                    }
                    break;
                case ViewEvent.UNLOCKED:
                    if (reportLocks) {
                        System.out.println(TITLE+"handleViewEvent(UNLOCKED) ");
                    }
                    break;
                default:
                    System.out.println(TITLE+"handleViewEvent(" + type + ") **UNKNOWN** ");
            }
        }

        @Override
        public void handleKeyEvent(View view, long time, int action,
            int keyCode, char[] keyChars, int modifiers) {
            if (!reportViewEvents)
                return;
            System.out.print(TITLE+"handleKeyEvent("
                + KeyEvent.getTypeString(action) + ","
                + keyCode(keyCode) + ",");
            if (keyChars != null) {
                System.out.print("\"");
                int c;
                for (c = 0; c < keyChars.length; c++) {
                    System.out.print(keyChars[c]);

                }
                System.out.print("\",");

            } else {
                System.out.println("ERROR-NULL,");
            }
            System.out.println(modifiers + ")");
            if(action == KeyEvent.RELEASE && keyChars.length > 0) {
                switch(keyChars[0]) {
                    case 'l':
                        System.out.println("Location on screen window="+
                                window.getX()+","+window.getY()+
                                " view="+view.getX()+","+view.getY());
                        break;
                    case 's':
                        System.out.println("Setting Location on screen to "+window.getX()+","+window.getY());
                        window.setPosition(window.getX(), window.getY());
                        break;
                    case 'c':
                        int newX = (Screen.getMainScreen().getWidth() - view.getWidth())/2;
                        int newY = (Screen.getMainScreen().getHeight() - view.getHeight())/2;
                        System.out.println("Moving Location on screen to "+newX+","+newY);
                        window.setPosition(newX,newY);
                        break;
                }
            }
        }

        @Override
        public void handleMouseEvent(View view, long time, int type,
            int button, int x, int y, int xAbs, int yAbs,
            int clickCount, int modifiers, boolean isPopupTrigger,
            boolean isSythesized)
        {
            if (!reportViewEvents)
                return;
            System.out.println(TITLE+"handleMouseEvent("
                + mouseType(type) + ","
                + mouseID(button) + ","
                + x + "," + y + ","
                + xAbs + "," + yAbs
                + "," + clickCount
                + "," + modifiers
                + "," + isPopupTrigger
                + "," + isSythesized
                + ")");
        }
/*
        @Override
        public void handleDragStart(View view, int button, int x, int y, int xAbs, int yAbs,
            ClipboardAssistance dropSourceAssistant) {
        }

        @Override
        public void handleDragEnd(View view, int performedAction) {
        }

        @Override
        public int handleDragEnter(View view, int x, int y, int xAbs, int yAbs,
            int recommendedDropAction, ClipboardAssistance dropTargetAssistant) {
            return recommendedDropAction;
        }

        @Override
        public int handleDragOver(View view, int x, int y, int xAbs, int yAbs,
            int recommendedDropAction, ClipboardAssistance dropTargetAssistant) {
            return recommendedDropAction;
        }

        @Override
        public void handleDragLeave(View view, ClipboardAssistance dropTargetAssistant) {
        }

        @Override
        public int handleDragDrop(View view, int x, int y, int xAbs, int yAbs,
            int recommendedDropAction, ClipboardAssistance dropTargetAssistant) {
            return Clipboard.ACTION_NONE;
        }
 */

        String keyCode(int keycode) {
            switch (keycode) {
                case KeyEvent.VK_0:
                    return "VK_0";
                case KeyEvent.VK_1:
                    return "VK_1";
                case KeyEvent.VK_2:
                    return "VK_2";
                case KeyEvent.VK_3:
                    return "VK_3";
                case KeyEvent.VK_4:
                    return "VK_4";
                case KeyEvent.VK_5:
                    return "VK_5";
                case KeyEvent.VK_6:
                    return "VK_6";
                case KeyEvent.VK_7:
                    return "VK_7";
                case KeyEvent.VK_8:
                    return "VK_8";
                case KeyEvent.VK_9:
                    return "VK_9";
                case KeyEvent.VK_A:
                    return "VK_A";
                case KeyEvent.VK_ADD:
                    return "VK_ADD";
                case KeyEvent.VK_AMPERSAND:
                    return "VK_AMPERSAND";
                case KeyEvent.VK_ASTERISK:
                    return "VK_ASTERISK";
                case KeyEvent.VK_AT:
                    return "VK_AT";
                case KeyEvent.VK_B:
                    return "VK_B";
                case KeyEvent.VK_BACKSPACE:
                    return "VK_BACKSPACE";
                case KeyEvent.VK_BACK_QUOTE:
                    return "VK_BACK_QUOTE";
                case KeyEvent.VK_BACK_SLASH:
                    return "VK_BACK_SLASH";
                case KeyEvent.VK_BRACELEFT:
                    return "VK_BRACELEFT";
                case KeyEvent.VK_BRACERIGHT:
                    return "VK_BRACERIGHT";
                case KeyEvent.VK_C:
                    return "VK_C";
                case KeyEvent.VK_CAPS_LOCK:
                    return "VK_CAPS_LOCK";
                case KeyEvent.VK_CIRCUMFLEX:
                    return "VK_CIRCUMFLEX";
                case KeyEvent.VK_CLEAR:
                    return "VK_CLEAR";
                case KeyEvent.VK_CLOSE_BRACKET:
                    return "VK_CLOSE_BRACKET";
                case KeyEvent.VK_COLON:
                    return "VK_COLON";
                case KeyEvent.VK_COMMA:
                    return "VK_COMMA";
                case KeyEvent.VK_D:
                    return "VK_D";
                case KeyEvent.VK_DECIMAL:
                    return "VK_DECIMAL";
                case KeyEvent.VK_DELETE:
                    return "VK_DELETE";
                case KeyEvent.VK_DIVIDE:
                    return "VK_DIVIDE";
                case KeyEvent.VK_DOLLAR:
                    return "VK_DOLLAR";
                case KeyEvent.VK_DOUBLE_QUOTE:
                    return "VK_DOUBLE_QUOTE";
                case KeyEvent.VK_DOWN:
                    return "VK_DOWN";
                case KeyEvent.VK_E:
                    return "VK_E";
                case KeyEvent.VK_END:
                    return "VK_END";
                case KeyEvent.VK_ENTER:
                    return "VK_ENTER";
                case KeyEvent.VK_EQUALS:
                    return "VK_EQUALS";
                case KeyEvent.VK_ESCAPE:
                    return "VK_ESCAPE";
                case KeyEvent.VK_EURO_SIGN:
                    return "VK_EURO_SIGN";
                case KeyEvent.VK_EXCLAMATION:
                    return "VK_EXCLAMATION";
                case KeyEvent.VK_F:
                    return "VK_F";
                case KeyEvent.VK_F1:
                    return "VK_F1";
                case KeyEvent.VK_F10:
                    return "VK_F10";
                case KeyEvent.VK_F11:
                    return "VK_F11";
                case KeyEvent.VK_F12:
                    return "VK_F12";
                case KeyEvent.VK_F2:
                    return "VK_F2";
                case KeyEvent.VK_F3:
                    return "VK_F3";
                case KeyEvent.VK_F4:
                    return "VK_F4";
                case KeyEvent.VK_F5:
                    return "VK_F5";
                case KeyEvent.VK_F6:
                    return "VK_F6";
                case KeyEvent.VK_F7:
                    return "VK_F7";
                case KeyEvent.VK_F8:
                    return "VK_F8";
                case KeyEvent.VK_F9:
                    return "VK_F9";
                case KeyEvent.VK_G:
                    return "VK_G";
                case KeyEvent.VK_GREATER:
                    return "VK_GREATER";
                case KeyEvent.VK_H:
                    return "VK_H";
                case KeyEvent.VK_HELP:
                    return "VK_HELP";
                case KeyEvent.VK_HOME:
                    return "VK_HOME";
                case KeyEvent.VK_I:
                    return "VK_I";
                case KeyEvent.VK_INSERT:
                    return "VK_INSERT";
                case KeyEvent.VK_INV_EXCLAMATION:
                    return "VK_INV_EXCLAMATION";
                case KeyEvent.VK_J:
                    return "VK_J";
                case KeyEvent.VK_K:
                    return "VK_K";
                case KeyEvent.VK_L:
                    return "VK_L";
                case KeyEvent.VK_LEFT:
                    return "VK_LEFT";
                case KeyEvent.VK_LEFT_PARENTHESIS:
                    return "VK_LEFT_PARENTHESIS";
                case KeyEvent.VK_LESS:
                    return "VK_LESS";
                case KeyEvent.VK_M:
                    return "VK_M";
                case KeyEvent.VK_MINUS:
                    return "VK_MINUS";
                case KeyEvent.VK_MULTIPLY:
                    return "VK_MULTIPLY";
                case KeyEvent.VK_N:
                    return "VK_N";
                case KeyEvent.VK_NUMBER_SIGN:
                    return "VK_NUMBER_SIGN";
                case KeyEvent.VK_NUMPAD0:
                    return "VK_NUMPAD0";
                case KeyEvent.VK_NUMPAD1:
                    return "VK_NUMPAD1";
                case KeyEvent.VK_NUMPAD2:
                    return "VK_NUMPAD2";
                case KeyEvent.VK_NUMPAD3:
                    return "VK_NUMPAD3";
                case KeyEvent.VK_NUMPAD4:
                    return "VK_NUMPAD4";
                case KeyEvent.VK_NUMPAD5:
                    return "VK_NUMPAD5";
                case KeyEvent.VK_NUMPAD6:
                    return "VK_NUMPAD6";
                case KeyEvent.VK_NUMPAD7:
                    return "VK_NUMPAD7";
                case KeyEvent.VK_NUMPAD8:
                    return "VK_NUMPAD8";
                case KeyEvent.VK_NUMPAD9:
                    return "VK_NUMPAD9";
                case KeyEvent.VK_NUM_LOCK:
                    return "VK_NUM_LOCK";
                case KeyEvent.VK_O:
                    return "VK_O";
                case KeyEvent.VK_OPEN_BRACKET:
                    return "VK_OPEN_BRACKET";
                case KeyEvent.VK_P:
                    return "VK_P";
                case KeyEvent.VK_PAGE_DOWN:
                    return "VK_PAGE_DOWN";
                case KeyEvent.VK_PAGE_UP:
                    return "VK_PAGE_UP";
                case KeyEvent.VK_PAUSE:
                    return "VK_PAUSE";
                case KeyEvent.VK_PERIOD:
                    return "VK_PERIOD";
                case KeyEvent.VK_PLUS:
                    return "VK_PLUS";
                case KeyEvent.VK_PRINTSCREEN:
                    return "VK_PRINTSCREEN";
                case KeyEvent.VK_Q:
                    return "VK_Q";
                case KeyEvent.VK_QUOTE:
                    return "VK_QUOTE";
                case KeyEvent.VK_R:
                    return "VK_R";
                case KeyEvent.VK_RIGHT:
                    return "VK_RIGHT";
                case KeyEvent.VK_RIGHT_PARENTHESIS:
                    return "VK_RIGHT_PARENTHESIS";
                case KeyEvent.VK_S:
                    return "VK_S";
                case KeyEvent.VK_SCROLL_LOCK:
                    return "VK_SCROLL_LOCK";
                case KeyEvent.VK_SEMICOLON:
                    return "VK_SEMICOLON";
                case KeyEvent.VK_SEPARATOR:
                    return "VK_SEPARATOR";
                case KeyEvent.VK_SLASH:
                    return "VK_SLASH";
                case KeyEvent.VK_SPACE:
                    return "VK_SPACE";
                case KeyEvent.VK_SUBTRACT:
                    return "VK_SUBTRACT";
                case KeyEvent.VK_T:
                    return "VK_T";
                case KeyEvent.VK_TAB:
                    return "VK_TAB";
                case KeyEvent.VK_U:
                    return "VK_U";
                case KeyEvent.VK_UNDEFINED:
                    return "VK_UNDEFINED";
                case KeyEvent.VK_UNDERSCORE:
                    return "VK_UNDERSCORE";
                case KeyEvent.VK_UP:
                    return "VK_UP";
                case KeyEvent.VK_V:
                    return "VK_V";
                case KeyEvent.VK_W:
                    return "VK_W";
                case KeyEvent.VK_X:
                    return "VK_X";
                case KeyEvent.VK_Y:
                    return "VK_Y";
                case KeyEvent.VK_Z:
                    return "VK_Z";
            }
            return "(" + keycode + ")";
        }

        String mouseType(int type) {
            switch (type) {
                case MouseEvent.DOWN:
                    return "DOWN";
                case MouseEvent.UP:
                    return "UP";
                case MouseEvent.DRAG:
                    return "DRAG";
                case MouseEvent.ENTER:
                    return "ENTER";
                case MouseEvent.EXIT:
                    return "EXIT";
                case MouseEvent.CLICK:
                    return "CLICK";
                case MouseEvent.MOVE:
                    return "MOVE";
            }
            return "unknown! (" + type + ")";
        }

        String mouseID(int button) {
            switch (button) {
                case MouseEvent.BUTTON_NONE:
                    return "NONE";
                case MouseEvent.BUTTON_LEFT:
                    return "LEFT";
                case MouseEvent.BUTTON_RIGHT:
                    return "RIGHT";
                case MouseEvent.BUTTON_OTHER:
                    return "OTHER";
            }
            return "unknown! (" + button + ")";
        }
    }

    static public class WaitTest implements Runnable {
        int counter = 1;   // next runnable we should post
        int nextIteration = 1; // next runnable we should see

        private class TestRunnable implements Runnable {
            int iteration;

            TestRunnable(int iteration) {
                this. iteration = iteration;
            }

            public void run() {
                System.out.println("Invoke and Wait BEAT "+iteration);
                checkThread(true, "InvokeAndWait");
                if (iteration != nextIteration) {
                    throw new RuntimeException("Dropped runnable "+iteration+"!="+nextIteration);
                }
                nextIteration++;
            }

        }

        public void run() {
            int hbps =  1000;
            try {
                while(true) {
                    Application.invokeAndWait(new TestRunnable(counter));
                    counter++;
                    Thread.sleep(hbps);
                }
            }catch(InterruptedException e) {
            }
        }

    }

    static public class HeartBeat implements Runnable {

        int counter = 1;   // next runnable we should post
        int nextIteration = 1; // next runnable we should see

        private class TestRunnable implements Runnable {
            int iteration;

            TestRunnable(int iteration) {
                this. iteration = iteration;
            }

            public void run() {
                System.out.println("BEAT "+iteration);
                checkThread(true, "postOnEventQueue");
                if (iteration != nextIteration) {
                    throw new RuntimeException("Dropped runnable "+iteration+"!="+nextIteration);
                }
                nextIteration++;
            }

        }

        public void run() {
            int hbps =  1000 / heartBeatPS;
            try {
                while(true) {
                    Application.postOnEventQueue(new TestRunnable(counter));
                    counter++;
                    Thread.sleep(hbps);
                }
            }catch(InterruptedException e) {
            }
        }

    }

    static public class TimerBeat implements Runnable {
        int iteration = 0;
        public void run() {
            System.out.println("TIMER "+iteration);
            checkThread(false, "pen.paint");
            Application.postOnEventQueue(new Runnable() {
                public void run() {
                    System.out.println("  PULSE "+iteration);
                }
            });
            iteration++;
        }
    }
}
