package %%%PACKAGE%%%;

import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JFrame;
import javax.swing.JPanel;

class GameWindow {

    public static GameWindow INSTANCE = null;
    public static double FPS = 60;

    private JFrame frame;
    private BufferStrategy strategy = null;
    private int gameWidth;
    private int gameHeight;

    private int screenWidth;
    private int screenHeight;
    private BufferedImage virtualScreen;
    private JPanel canvasHost;
    private KeyAdapter keyAdapter;
    private GameWindowMouseListener mouseListener;

    private GameWindow(double fps, int gameWidth, int gameHeight, int pixelWidth, int pixelHeight) {

        INSTANCE = this;

        this.gameWidth = gameWidth;
        this.gameHeight = gameHeight;
        this.screenWidth = -1;
        this.screenHeight = -1;

        virtualScreen = new BufferedImage(gameWidth, gameHeight, BufferedImage.TYPE_INT_ARGB);

        frame = new JFrame("Crayon Window");
        frame.setSize(pixelWidth, pixelHeight);

        canvasHost = (JPanel) frame.getContentPane();
        canvasHost.setPreferredSize(new Dimension(pixelWidth, pixelHeight));
        canvasHost.setLayout(null);

        frame.pack();
        frame.setVisible(true);

        frame.addWindowListener(new WindowAdapter() { 
            @Override 
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });

        keyAdapter = new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                handleKeyPress(e.getKeyCode(), true);
            }

            @Override
            public void keyReleased(KeyEvent e) { 
                handleKeyPress(e.getKeyCode(), false);
            }
        };

        mouseListener = new GameWindowMouseListener(this);

        canvasHost.addKeyListener(keyAdapter);
        canvasHost.requestFocus();
    }

    public void maybeUpdateCanvas() {
        int actualWidth = canvasHost.getWidth();;
        int actualHeight = canvasHost.getHeight();
        if (actualWidth == screenWidth && actualHeight == screenHeight) {
            return;
        }

        canvasHost.removeAll();
        Canvas canvas = new Canvas();
        canvas.setBounds(0, 0, actualWidth, actualHeight);
        canvas.setSize(actualWidth, actualHeight);
        canvasHost.add(canvas);
        canvas.setIgnoreRepaint(true);
        canvas.addKeyListener(keyAdapter);
        canvas.addMouseListener(mouseListener);
        canvas.addMouseMotionListener(mouseListener);

        canvas.createBufferStrategy(2);
        strategy = canvas.getBufferStrategy();

        screenWidth = actualWidth;
        screenHeight = actualHeight;
    }

    public static void initializeScreen(int gameWidth, int gameHeight) {
        initializeScreen(gameWidth, gameHeight, gameWidth, gameHeight);
    }

    public static void initializeScreen(int gameWidth, int gameHeight, int screenWidth, int screenHeight) {
        GameWindow window = new GameWindow(FPS, gameWidth, gameHeight, screenWidth, screenHeight);
        window.show();
    }

    public void setTitle(String title) {
        frame.setTitle(title);
    }

    private static HashMap<Integer, String> keyCodeLookup = null;
    private HashMap<Integer, String> getKeyCodeLookup() {
        if (keyCodeLookup == null) {
            keyCodeLookup = new HashMap<Integer, String>();
            keyCodeLookup.put(KeyEvent.VK_LEFT, "left");
            keyCodeLookup.put(KeyEvent.VK_RIGHT, "right");
            keyCodeLookup.put(KeyEvent.VK_DOWN, "down");
            keyCodeLookup.put(KeyEvent.VK_UP, "up");

            keyCodeLookup.put(KeyEvent.VK_SPACE, "space");
            keyCodeLookup.put(KeyEvent.VK_ENTER, "enter");
            keyCodeLookup.put(KeyEvent.VK_TAB, "tab");
            keyCodeLookup.put(KeyEvent.VK_ESCAPE, "escape");

            keyCodeLookup.put(KeyEvent.VK_CONTROL, "ctrl");
            keyCodeLookup.put(KeyEvent.VK_SHIFT, "shift");
            keyCodeLookup.put(KeyEvent.VK_ALT, "alt");

            for (int i = 0; i < 26; ++i) {
                keyCodeLookup.put(KeyEvent.VK_A + i, "" + (char)('a' + i));
            }

            for (int i = 0; i < 10; ++i) {
                keyCodeLookup.put(KeyEvent.VK_0 + i, "" + (char)('0' + i));
            }

            for (int i = 0; i < 12; ++i) {
                keyCodeLookup.put(KeyEvent.VK_F1 + i, "F" + (i + 1));
            }
        }
        return keyCodeLookup;
    }

    private void timerTick() {
        RenderEngine.reset();
        CrayonWrapper.v_runTick();

        maybeUpdateCanvas();
        Graphics2D virtualG = (Graphics2D) virtualScreen.createGraphics();
        RenderEngine.render(virtualG, gameWidth, gameHeight);

        Graphics2D realG = (Graphics2D) strategy.getDrawGraphics();
        realG.setRenderingHint(
            RenderingHints.KEY_INTERPOLATION, 
            RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
        realG.drawImage(
            virtualScreen, 0, 0, screenWidth, screenHeight,
            0, 0, virtualScreen.getWidth(), virtualScreen.getHeight(),
            null);

        realG.dispose();
        strategy.show();
    }

    public void show() {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                timerTick();
            }
        }, 0, (int)(1000 / FPS));

        frame.setVisible(true);
    }

    private ArrayList<Value> eventQueue = new ArrayList<Value>();

    public ArrayList<Value> pumpEventQueue() {
        ArrayList<Value> output = eventQueue;
        eventQueue = new ArrayList<Value>();
        return output;
    }

    private static Value getBoolean(boolean b) {
        return b ? CrayonWrapper.v_VALUE_TRUE : CrayonWrapper.v_VALUE_FALSE;
    }

    private static Value getInteger(int x) {
        return CrayonWrapper.v_buildInteger(x);
    }

    private static final HashMap<String, Value> STRING_CACHE  = new HashMap<String, Value>();
    private Value getCommonString(String str) {
        Value output = STRING_CACHE.get(str);
        if (output == null) {
            output = new Value(%%%TYPE_STRING%%%, str);
            STRING_CACHE.put(str, output);
        }
        return output;
    }

    private void handleKeyPress(int keyCode, boolean down) {
        String key = getKeyCodeLookup().get(keyCode);
        if (key != null) {
            eventQueue.add(generateEventItem(
                getCommonString("key"),
                getBoolean(down),
                getCommonString(key)));
        }
    }

    private void handleMouseEvent(boolean isClick, boolean down, boolean isLeft, int x, int y) {
        int gameX = x * gameWidth / screenWidth;
        int gameY = y * gameHeight / screenHeight;
        
        String eventName;
        if (isClick) {
            if (down) {
                eventName = isLeft ? "mouseleftdown" : "mouserightdown";
            } else {
                eventName = isLeft ? "mouseleftup" : "mouserightup";
            }
        } else {
            eventName = "mousemove";
        }

        eventQueue.add(generateEventItem(
            getCommonString(eventName),
            getInteger(gameX),
            getInteger(gameY)));
    }

    private Value generateEventItem(Value ... values) {
        ArrayList<Value> output = new ArrayList<Value>(values.length);
        for (int i = 0; i < values.length; ++i) {
            output.add(values[i]);
        }
        return new Value(%%%TYPE_LIST%%%, output);
    }

    private static class GameWindowMouseListener implements MouseListener, MouseMotionListener {
        private GameWindow gw;
        GameWindowMouseListener(GameWindow gw) {
            this.gw = gw;
        }

        @Override
        public void mouseClicked(MouseEvent e) {}
        @Override
        public void mouseEntered(MouseEvent e) {}
        @Override
        public void mouseExited(MouseEvent e) {}
        @Override
        public void mouseDragged(MouseEvent e) {}

        @Override
        public void mousePressed(MouseEvent e) {
            int button = e.getButton();
            if (button == MouseEvent.BUTTON1 || button == MouseEvent.BUTTON3) {
                gw.handleMouseEvent(true, false, button == MouseEvent.BUTTON1, e.getX(), e.getY());
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            int button = e.getButton();
            if (button == MouseEvent.BUTTON1 || button == MouseEvent.BUTTON3) {
                gw.handleMouseEvent(true, true, button == MouseEvent.BUTTON1, e.getX(), e.getY());
            }
        }

        @Override
        public void mouseMoved(MouseEvent e) {
            gw.handleMouseEvent(false, false, false, e.getX(), e.getY());
        }
    }
}
