/*******************************************************************************
 * MIT License
 * 
 * Copyright (c) 2018 Raymond Buckley
 * 
 * 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.
 ******************************************************************************/
package com.ray3k.skincomposer;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Net;
import com.badlogic.gdx.Net.HttpMethods;
import com.badlogic.gdx.Net.HttpRequest;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.net.HttpRequestBuilder;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.scenes.scene2d.ui.Button.ButtonStyle;
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox.CheckBoxStyle;
import com.badlogic.gdx.scenes.scene2d.ui.ImageButton.ImageButtonStyle;
import com.badlogic.gdx.scenes.scene2d.ui.ImageTextButton.ImageTextButtonStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle;
import com.badlogic.gdx.scenes.scene2d.ui.List.ListStyle;
import com.badlogic.gdx.scenes.scene2d.ui.ProgressBar.ProgressBarStyle;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane.ScrollPaneStyle;
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox.SelectBoxStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Slider.SliderStyle;
import com.badlogic.gdx.scenes.scene2d.ui.SplitPane.SplitPaneStyle;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle;
import com.badlogic.gdx.scenes.scene2d.ui.TextField.TextFieldStyle;
import com.badlogic.gdx.scenes.scene2d.ui.TextTooltip.TextTooltipStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Touchpad.TouchpadStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Tree.TreeStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Window.WindowStyle;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
import com.ray3k.skincomposer.data.AtlasData;
import com.ray3k.skincomposer.data.JsonData;
import com.ray3k.skincomposer.data.ProjectData;
import com.ray3k.skincomposer.dialog.DialogFactory;
import com.ray3k.skincomposer.dialog.DialogListener;
import com.ray3k.skincomposer.utils.Utils;
import com.ray3k.stripe.FreeTypeSkin;
import com.ray3k.tenpatch.TenPatchDrawable;
import space.earlygrey.shapedrawer.GraphDrawer;
import space.earlygrey.shapedrawer.ShapeDrawer;

public class Main extends ApplicationAdapter {
    public final static String VERSION = "37";
    public static String newVersion;
    public static final Class[] BASIC_CLASSES = {Button.class, CheckBox.class,
        ImageButton.class, ImageTextButton.class, Label.class, List.class,
        ProgressBar.class, ScrollPane.class, SelectBox.class, Slider.class,
        SplitPane.class, TextButton.class, TextField.class, TextTooltip.class,
        Touchpad.class, Tree.class, Window.class};
    public static final Class[] STYLE_CLASSES = {ButtonStyle.class,
        CheckBoxStyle.class, ImageButtonStyle.class, ImageTextButtonStyle.class,
        LabelStyle.class, ListStyle.class, ProgressBarStyle.class,
        ScrollPaneStyle.class, SelectBoxStyle.class, SliderStyle.class,
        SplitPaneStyle.class, TextButtonStyle.class, TextFieldStyle.class,
        TextTooltipStyle.class, TouchpadStyle.class, TreeStyle.class,
        WindowStyle.class};
    private Stage stage;
    private static Skin skin;
    private ShapeDrawer shapeDrawer;
    private GraphDrawer graphDrawer;
    private DialogFactory dialogFactory;
    private DesktopWorker desktopWorker;
    private TenPatchDrawable loadingAnimation;
    private UndoableManager undoableManager;
    private ProjectData projectData;
    private RootTable rootTable;
    private IbeamListener ibeamListener;
    private MainListener mainListener;
    private HandListener handListener;
    private ScrollFocusListener scrollFocusListener;
    private ResizeArrowListener verticalResizeArrowListener;
    private ResizeArrowListener horizontalResizeArrowListener;
    private TooltipManager tooltipManager;
    public static FileHandle appFolder;
    private String[] args;
    public static Main main;
    
    public Main (String[] args) {
        this.args = args;
        main = this;
    }
    
    @Override
    public void create() {
        appFolder = Gdx.files.external(".skincomposer/");
        
        skin = new FreeTypeSkin(Gdx.files.internal("skin-composer-ui/skin-composer-ui.json"));
        stage = new Stage(new ScreenViewport());
        Gdx.input.setInputProcessor(stage);
        
        shapeDrawer = new ShapeDrawer(stage.getBatch(), skin.getRegion("white"));
        graphDrawer = new GraphDrawer(shapeDrawer);
        
        initDefaults();
        
        populate();
    }
    
    private void initDefaults() {
        if (Utils.isMac()) System.setProperty("java.awt.headless", "true");
        
        skin.getFont("font").getData().markupEnabled = true;
        
        //copy defaults.json to temp folder if it doesn't exist
        var fileHandle = appFolder.child("texturepacker/atlas-export-settings.json");
        if (!fileHandle.exists()) {
            Gdx.files.internal("atlas-export-settings.json").copyTo(fileHandle);
        }
        
        //copy atlas settings for preview to temp folder if it doesn't exist
        fileHandle = appFolder.child("texturepacker/atlas-internal-settings.json");
        if (!fileHandle.exists()) {
            Gdx.files.internal("atlas-internal-settings.json").copyTo(fileHandle);
        }
        
        //copy preview fonts to preview fonts folder if they do not exist
        fileHandle = appFolder.child("preview fonts/IBMPlexSerif-Medium.ttf");
        if (!fileHandle.exists()) {
            Gdx.files.internal("preview fonts/IBMPlexSerif-Medium.ttf").copyTo(fileHandle);
        }
        
        fileHandle = appFolder.child("preview fonts/Pacifico-Regular.ttf");
        if (!fileHandle.exists()) {
            Gdx.files.internal("preview fonts/Pacifico-Regular.ttf").copyTo(fileHandle);
        }
        
        fileHandle = appFolder.child("preview fonts/PressStart2P-Regular.ttf");
        if (!fileHandle.exists()) {
            Gdx.files.internal("preview fonts/PressStart2P-Regular.ttf").copyTo(fileHandle);
        }
        
        fileHandle = appFolder.child("preview fonts/SourceSansPro-Regular.ttf");
        if (!fileHandle.exists()) {
            Gdx.files.internal("preview fonts/SourceSansPro-Regular.ttf").copyTo(fileHandle);
        }
        
        ibeamListener = new IbeamListener();
        
        projectData = new ProjectData();
        projectData.setMain(this);
        projectData.randomizeId();
        projectData.setMaxUndos(30);
        
        newVersion = VERSION;
        if (projectData.isCheckingForUpdates()) {
            checkForUpdates(this);
        }
        
        dialogFactory = new DialogFactory(this);
        undoableManager = new UndoableManager(this);
        
        desktopWorker.attachLogListener();
        desktopWorker.sizeWindowToFit(800, 800, 50, Gdx.graphics);
        desktopWorker.centerWindow(Gdx.graphics);
        desktopWorker.setCloseListener(() -> {
            dialogFactory.showCloseDialog(new DialogListener() {
                @Override
                public void opened() {
                    desktopWorker.removeFilesDroppedListener(rootTable.getFilesDroppedListener());
                }

                @Override
                public void closed() {
                    desktopWorker.addFilesDroppedListener(rootTable.getFilesDroppedListener());
                }
            });
            return false;
        });
        
        loadingAnimation = skin.get("loading-animation", TenPatchDrawable.class);
        
        projectData.getAtlasData().clearTempData();
        handListener = new HandListener();
        
        scrollFocusListener = new ScrollFocusListener(stage);
        
        verticalResizeArrowListener = new ResizeArrowListener(true);
        horizontalResizeArrowListener = new ResizeArrowListener(false);
        
        tooltipManager = new TooltipManager();
        tooltipManager.animations = false;
        tooltipManager.initialTime = .4f;
        tooltipManager.resetTime = 0.0f;
        tooltipManager.subsequentTime = 0.0f;
        tooltipManager.hideAll();
        tooltipManager.instant();
    }

    private void populate() {
        stage.clear();
        
        rootTable = new RootTable(this);
        rootTable.setFillParent(true);
        mainListener = new MainListener(this);
        rootTable.addListener(mainListener);
        rootTable.populate();
        stage.addActor(rootTable);
        rootTable.setRecentFilesDisabled(projectData.getRecentFiles().size == 0);
        
        //pass arguments
        if (!mainListener.argumentsPassed(args)) {
            //show welcome screen if there are no valid arguments
            mainListener.createWelcomeListener();
        }
    }
    
    @Override
    public void render() {
        Gdx.gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        
        stage.act(Gdx.graphics.getDeltaTime());
        
        for (var tenPatch : skin.getAll(TenPatchDrawable.class)) {
            tenPatch.value.update(Gdx.graphics.getDeltaTime());
        }
        
        stage.draw();
    }

    @Override
    public void resize(int width, int height) {
        if (width != 0 && height != 0) {
            stage.getViewport().update(width, height, true);
            rootTable.fire(new StageResizeEvent(width, height));
        }
    }

    @Override
    public void dispose() {
        stage.dispose();
        skin.dispose();
    }

    public DesktopWorker getDesktopWorker() {
        return desktopWorker;
    }
    
    public void setDesktopWorker(DesktopWorker textureWorker) {
        this.desktopWorker = textureWorker;
    }

    public Stage getStage() {
        return stage;
    }

    public TenPatchDrawable getLoadingAnimation() {
        return loadingAnimation;
    }

    public Skin getSkin() {
        return skin;
    }
    
    public ShapeDrawer getShapeDrawer() {
        return shapeDrawer;
    }
    
    public GraphDrawer getGraphDrawer() {
        return graphDrawer;
    }
    
    public UndoableManager getUndoableManager() {
        return undoableManager;
    }

    public ProjectData getProjectData() {
        return projectData;
    }
    
    public JsonData getJsonData() {
        return projectData.getJsonData();
    }
    
    public AtlasData getAtlasData() {
        return projectData.getAtlasData();
    }

    public void setProjectData(ProjectData projectData) {
        this.projectData = projectData;
    }

    public RootTable getRootTable() {
        return rootTable;
    }

    public DialogFactory getDialogFactory() {
        return dialogFactory;
    }

    public IbeamListener getIbeamListener() {
        return ibeamListener;
    }

    public MainListener getMainListener() {
        return mainListener;
    }

    public HandListener getHandListener() {
        return handListener;
    }
    
    public ScrollFocusListener getScrollFocusListener() {
        return scrollFocusListener;
    }
    
    public ResizeArrowListener getVerticalResizeArrowListener() {
        return verticalResizeArrowListener;
    }

    public ResizeArrowListener getHorizontalResizeArrowListener() {
        return horizontalResizeArrowListener;
    }

    public TooltipManager getTooltipManager() {
        return tooltipManager;
    }

    public static Class basicToStyleClass(Class clazz) {
        int i = 0;
        for (Class basicClass : BASIC_CLASSES) {
            if (clazz.equals(basicClass)) {
                break;
            } else {
                i++;
            }
        }
        return i < STYLE_CLASSES.length ? STYLE_CLASSES[i] : null;
    }
    
    public static Class styleToBasicClass(Class clazz) {
        int i = 0;
        for (Class styleClass : STYLE_CLASSES) {
            if (clazz.equals(styleClass)) {
                break;
            } else {
                i++;
            }
        }
        return BASIC_CLASSES[i];
    }

    public static void checkForUpdates(Main main) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                HttpRequestBuilder requestBuilder = new HttpRequestBuilder();
                HttpRequest httpRequest = requestBuilder.newRequest().method(HttpMethods.GET).url("https://raw.githubusercontent.com/raeleus/skin-composer/master/version").build();
                Gdx.net.sendHttpRequest(httpRequest, new Net.HttpResponseListener() {
                    @Override
                    public void handleHttpResponse(Net.HttpResponse httpResponse) {
                        newVersion = httpResponse.getResultAsString();
                        Gdx.app.postRunnable(new Runnable() {
                            @Override
                            public void run() {
                                main.rootTable.fire(new RootTable.RootTableEvent(RootTable.RootTableEnum.CHECK_FOR_UPDATES_COMPLETE));
                            }
                        });
                    }

                    @Override
                    public void failed(Throwable t) {
                        newVersion = VERSION;
                    }

                    @Override
                    public void cancelled() {
                        newVersion = VERSION;
                    }
                });
            }
        });
        
        thread.start();
    }
}