//Copyright (c) 2015, David Larochelle-Pratte
//All rights reserved.
//
//        Redistribution and use in source and binary forms, with or without
//        modification, are permitted provided that the following conditions are met:
//
//        1. Redistributions of source code must retain the above copyright notice, this
//        list of conditions and the following disclaimer.
//        2. Redistributions in binary form must reproduce the above copyright notice,
//        this list of conditions and the following disclaimer in the documentation
//        and/or other materials provided with the distribution.
//
//        THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//        ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//        WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//        DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
//        ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
//        (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//        LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
//        ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
//        (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//        SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.hancinworld.fw.proxy;

import com.hancinworld.fw.handler.ConfigurationHandler;
import com.hancinworld.fw.handler.DrawScreenEventHandler;
import com.hancinworld.fw.handler.KeyInputEventHandler;
import com.hancinworld.fw.reference.Reference;
import com.hancinworld.fw.utility.LogHelper;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.client.SplashProgress;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.settings.KeyBinding;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;

import java.awt.*;
import java.io.File;

public class ClientProxy extends CommonProxy {

    private final Minecraft client = Minecraft.getMinecraft();
    private Rectangle _savedWindowedBounds;
    public static boolean fullscreen;
    public static KeyBinding fullscreenKeyBinding;
    public DrawScreenEventHandler dsHandler;

    /** This keybind replaces the default MC fullscreen keybind in their logic handler. Without it, the game crashes.
     *  If this is set to any valid key, problems may occur. */
    public static KeyBinding ignoreKeyBinding = new KeyBinding("key.fullscreenwindowed.unused", Keyboard.KEY_NONE, "key.categories.misc");


    public ClientProxy()
    {
    }

    @Override
    public void registerKeyBindings()
    {
        /* FIXME: Overrides the minecraft hotkey for fullscreen, as there are no hooks */
        if(fullscreenKeyBinding == null && ConfigurationHandler.instance().isFullscreenWindowedEnabled())
        {
            fullscreenKeyBinding = client.gameSettings.keyBindFullscreen;
            client.gameSettings.keyBindFullscreen = ignoreKeyBinding;

        }
        else if(fullscreenKeyBinding != null && !ConfigurationHandler.instance().isFullscreenWindowedEnabled())
        {
            Minecraft mc = Minecraft.getMinecraft();
            mc.gameSettings.keyBindFullscreen = fullscreenKeyBinding;
            fullscreenKeyBinding = null;

            if(fullscreen){
                mc.fullscreen = false;
                mc.toggleFullscreen();
            }
        }
    }

    @Override
    public void subscribeEvents(File configurationFile) {

        ConfigurationHandler.instance().init(configurationFile);
        FMLCommonHandler.instance().bus().register(ConfigurationHandler.instance());
        FMLCommonHandler.instance().bus().register(new KeyInputEventHandler());
        dsHandler = new DrawScreenEventHandler();
        MinecraftForge.EVENT_BUS.register(dsHandler);
    }

    private Rectangle findCurrentScreenDimensionsAndPosition(int x, int y)
    {
        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] screens = env.getScreenDevices();

        for(GraphicsDevice dev : screens)
        {
            GraphicsConfiguration displayMode = dev.getDefaultConfiguration();
            Rectangle bounds = displayMode.getBounds();

            if(bounds.contains(x, y))
                return bounds;
        }

        //if Java isn't able to find a matching screen then use the old LWJGL calcs.
        return new Rectangle(0, 0, Display.getDesktopDisplayMode().getWidth(), Display.getDesktopDisplayMode().getHeight());
}
    private Rectangle findScreenDimensionsByID(int monitorID)
    {
        if(monitorID < 1)
            return null;

        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] screens = env.getScreenDevices();

        if(screens == null || screens.length == 0 || screens.length < monitorID){
            return null;
        }

        return screens[monitorID - 1].getDefaultConfiguration().getBounds();
    }

    private Rectangle getAppropriateScreenBounds(Rectangle currentCoordinates, int desiredMonitor)
    {
        Rectangle screenBounds;
        Point centerCoordinates = new Point((int) (currentCoordinates.getMinX() + currentCoordinates.getWidth() / 2), (int) (currentCoordinates.getMinY() + currentCoordinates.getHeight() / 2));

        ConfigurationHandler configuration = ConfigurationHandler.instance();
        //First feature mode: Only remove decorations. No need to calculate screen positions, we're not changing size or location.
        if(configuration.areAdvancedFeaturesEnabled() && configuration.isOnlyRemoveDecorations()){
            screenBounds = currentCoordinates;
        }
        //Custom dimensions enabled: follow requested settings if we can work with them.
        else if(configuration.areAdvancedFeaturesEnabled() && configuration.isCustomFullscreenDimensions() && (configuration.getCustomFullscreenDimensionsH() > 256 && configuration.getCustomFullscreenDimensionsW() > 256))
        {
            screenBounds = new Rectangle(configuration.getCustomFullscreenDimensionsX(),configuration.getCustomFullscreenDimensionsY(), configuration.getCustomFullscreenDimensionsW(),configuration.getCustomFullscreenDimensionsH());

            //If you've selected a monitor, then X & Y are offsets - easier to do math.
            if(desiredMonitor > 0) {
                Rectangle actualScreenBounds = findScreenDimensionsByID(desiredMonitor);
                if(actualScreenBounds != null){
                    screenBounds.setLocation(actualScreenBounds.x + screenBounds.x, actualScreenBounds.y + screenBounds.y);
                }
            }
        }
        // No specified monitor for fullscreen -> find the one the window is on right now
        else if(desiredMonitor < 0 || desiredMonitor == Reference.AUTOMATIC_MONITOR_SELECTION) {
            //find which monitor we should be using based on the center of the MC window
            screenBounds = findCurrentScreenDimensionsAndPosition((int) centerCoordinates.getX(), (int) centerCoordinates.getY());
        // specified monitor for fullscreen -> get dimensions.
        }else{
            screenBounds = findScreenDimensionsByID(desiredMonitor);
            // you've specified a monitor but it doesn't look connected. Revert to automatic mode.
            if(screenBounds == null){
                screenBounds = findCurrentScreenDimensionsAndPosition((int) centerCoordinates.getX(), (int) centerCoordinates.getY());
            }
        }

        return screenBounds;
    }
    @Override
    public void toggleFullScreen(boolean goFullScreen) {
        toggleFullScreen(goFullScreen, ConfigurationHandler.instance().getFullscreenMonitor());
    }

    @Override
    public void toggleFullScreen(boolean goFullScreen, int desiredMonitor) {

        //Set value if it isn't set already.
        if(System.getProperty("org.lwjgl.opengl.Window.undecorated") == null){
            System.setProperty("org.lwjgl.opengl.Window.undecorated", "false");
        }

        //If we're in actual fullscreen right now, then we need to fix that.
        if(Display.isFullscreen()) {
            fullscreen = true;
        }

        String expectedState = goFullScreen ? "true":"false";
        // If all state is valid, there is nothing to do and we just exit.
        if(fullscreen == goFullScreen
                && !Display.isFullscreen()//Display in fullscreen mode: Change required
                && System.getProperty("org.lwjgl.opengl.Window.undecorated") == expectedState // Window not in expected state
        )
            return;

        //Save our current display parameters
        Rectangle currentCoordinates = new Rectangle(Display.getX(), Display.getY(), Display.getWidth(), Display.getHeight());
        if(goFullScreen && !Display.isFullscreen())
            _savedWindowedBounds = currentCoordinates;

        //Changing this property and causing a Display update will cause LWJGL to add/remove decorations (borderless).
        System.setProperty("org.lwjgl.opengl.Window.undecorated",expectedState);

        //Get the fullscreen dimensions for the appropriate screen.
        Rectangle screenBounds = getAppropriateScreenBounds(currentCoordinates, desiredMonitor);

        //This is the new bounds we have to apply.
        Rectangle newBounds = goFullScreen ? screenBounds : _savedWindowedBounds;
        if(newBounds == null)
            newBounds = screenBounds;

        if(goFullScreen == false && ClientProxy.fullscreen == false) {
            newBounds = currentCoordinates;
            _savedWindowedBounds = currentCoordinates;
        }

        try {
            fullscreen = goFullScreen;
            client.fullscreen = fullscreen;
            if( client.gameSettings.fullScreen != fullscreen) {
                client.gameSettings.fullScreen = fullscreen;
                client.gameSettings.saveOptions();
            }
            Display.setFullscreen(false);
            Display.setResizable(!goFullScreen);
            Display.setDisplayMode(new DisplayMode((int) newBounds.getWidth(), (int) newBounds.getHeight()));
            Display.setLocation(newBounds.x, newBounds.y);

            client.resize((int) newBounds.getWidth(), (int) newBounds.getHeight());
            Display.setVSyncEnabled(client.gameSettings.enableVsync);
            client.updateDisplay();

        } catch (LWJGLException e) {
            e.printStackTrace();
        }

    }

    @Override
    @SuppressWarnings("deprecated")
    public void performStartupChecks()
    {
        //If the mod is disabled by configuration, just put back the initial value.
        if(!ConfigurationHandler.instance().isFullscreenWindowedEnabled()) {
            return;
        }

        if(ConfigurationHandler.instance().isMaximumCompatibilityEnabled()){
            dsHandler.setInitialFullscreen(client.gameSettings.fullScreen,  ConfigurationHandler.instance().getFullscreenMonitor());
        // This is the correct way to set fullscreen at launch, but LWJGL limitations means we might crash the game if
        // another mod tries to do a similar Display changing operation. Doesn't help the API says "don't use this"
        }else{
            try {
                //FIXME: Living dangerously here... Is there a better way of doing this?
                SplashProgress.pause();
                toggleFullScreen(client.gameSettings.fullScreen, ConfigurationHandler.instance().getFullscreenMonitor());
                SplashProgress.resume();
            }catch(NoClassDefFoundError e) {
                LogHelper.warn("Error while doing startup checks, are you using an old version of Forge ? " + e);
                toggleFullScreen(client.gameSettings.fullScreen, ConfigurationHandler.instance().getFullscreenMonitor());
            }
        }
    }
}