/*
 * Copyright 2014-2015 See AUTHORS file.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.kotcrab.vis.editor;

import com.artemis.annotations.SkipWire;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.lwjgl.LwjglGraphics;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
import com.kotcrab.vis.editor.event.ProjectStatusEvent;
import com.kotcrab.vis.editor.event.ProjectStatusEvent.Status;
import com.kotcrab.vis.editor.module.VisContainers;
import com.kotcrab.vis.editor.module.editor.*;
import com.kotcrab.vis.editor.module.project.Project;
import com.kotcrab.vis.editor.module.project.ProjectModuleContainer;
import com.kotcrab.vis.editor.plugin.api.ContainerExtension.ExtensionScope;
import com.kotcrab.vis.editor.ui.EditorFrame;
import com.kotcrab.vis.editor.ui.NoProjectFilesOpenView;
import com.kotcrab.vis.editor.ui.WindowListener;
import com.kotcrab.vis.editor.ui.dialog.AsyncTaskProgressDialog;
import com.kotcrab.vis.editor.ui.dialog.NewProjectDialog;
import com.kotcrab.vis.editor.ui.dialog.SettingsDialog;
import com.kotcrab.vis.editor.ui.dialog.UnsavedResourcesDialog;
import com.kotcrab.vis.editor.ui.tabbedpane.MainContentTab;
import com.kotcrab.vis.editor.ui.tabbedpane.TabViewMode;
import com.kotcrab.vis.editor.util.ThreadUtils;
import com.kotcrab.vis.editor.util.async.AsyncTask;
import com.kotcrab.vis.editor.util.scene2d.VisGroup;
import com.kotcrab.vis.editor.util.vis.LaunchConfiguration;
import com.kotcrab.vis.ui.VisUI;
import com.kotcrab.vis.ui.util.dialog.DialogUtils;
import com.kotcrab.vis.ui.util.dialog.DialogUtils.OptionDialog;
import com.kotcrab.vis.ui.util.dialog.DialogUtils.OptionDialogType;
import com.kotcrab.vis.ui.util.dialog.OptionDialogAdapter;
import com.kotcrab.vis.ui.widget.VisSplitPane;
import com.kotcrab.vis.ui.widget.VisTable;
import com.kotcrab.vis.ui.widget.file.FileChooser;
import com.kotcrab.vis.ui.widget.file.FileChooserAdapter;
import com.kotcrab.vis.ui.widget.tabbedpane.Tab;
import com.kotcrab.vis.ui.widget.tabbedpane.TabbedPaneAdapter;
import com.kotcrab.vis.ui.widget.tabbedpane.TabbedPaneListener;

import java.lang.reflect.Field;

/**
 * VisEditor main ApplicationAdapter class. The main() method is located in {@link EditorFrame}
 * @author Kotcrab
 */
public class Editor extends ApplicationAdapter {
	public static Editor instance;

	private EditorFrame frame;
	private LaunchConfiguration launchConfig;

	@SkipWire private Stage stage;
	private VisGroup stageRoot;
	private Table uiRoot;

	private EditorModuleContainer editorMC;
	private ProjectModuleContainer projectMC;

	private TabsModule tabsModule;
	private StatusBarModule statusBar;
	private ProjectIOModule projectIO;
	private FileChooserModule fileChooser;
	private ExtensionStorageModule extensionStorage;

	private GeneralSettingsModule settings;
	private ColorSettingsModule colorSettings;
	private ExperimentalSettingsModule experimentalSettings;

	// TODO move to module
	private Table mainContentTable;
	private Table tabContentTable;
	private VisTable quickAccessContentTable;
	private VisSplitPane splitPane;

	private NoProjectFilesOpenView noProjectFilesOpenView;

	private SettingsDialog settingsDialog;

	private boolean projectLoaded = false;

	private MainContentTab tab;

	private boolean exitInProgress;
	private Tab quickAccessTab;
	private ScreenViewport stageViewport;

	public Editor (EditorFrame frame, LaunchConfiguration launchConfig) {
		this.frame = frame;
		this.launchConfig = launchConfig;
	}

	@Override
	public void create () {
		instance = this;

		Log.debug("Starting loading");

		Assets.load();

		VisUI.load();
		VisUI.setDefaultTitleAlign(Align.center);
		FileChooser.setFavoritesPrefsName("com.kotcrab.vis.editor");
		Log.debug("VisUI " + VisUI.VERSION + " loaded");

		if (Gdx.graphics instanceof LwjglGraphics && ((LwjglGraphics) Gdx.graphics).isSoftwareMode()) {
			Log.info("Running in software mode");
		}

		stage = createStage();
		Gdx.input.setInputProcessor(stage);

		uiRoot = new Table();
		uiRoot.setFillParent(true);

		stage.addActor(uiRoot);

		createUI();
		createModuleContainers();
		createModulesUI();

		Log.debug("Loading completed");

		if (experimentalSettings.isUIScale() || launchConfig.scaleUIEnabled) {
			stageViewport.setUnitsPerPixel(0.5f);
		}
	}

	private Stage createStage () {
		stageViewport = new ScreenViewport();
		Stage stage = new Stage(stageViewport);

		//the stage root is final field, by default group does not support actor changed events and we need that
		//here we just set our custom group to get those events
		try {
			stageRoot = new VisGroup(stage);
			Field field = stage.getClass().getDeclaredField("root");
			field.setAccessible(true);
			field.set(stage, stageRoot);
		} catch (ReflectiveOperationException e) {
			Log.exception(e);
		}

		return stage;
	}

	private void createUI () {
		mainContentTable = new Table();
		tabContentTable = new Table();
		quickAccessContentTable = new VisTable();
		splitPane = new VisSplitPane(null, null, true);
		splitPane.setSplitAmount(0.77f);

		settingsDialog = new SettingsDialog();
	}

	private void createModuleContainers () {
		editorMC = new EditorModuleContainer();
		projectMC = new ProjectModuleContainer(editorMC);
		noProjectFilesOpenView = new NoProjectFilesOpenView(projectMC);

		VisContainers.createEditorModules(editorMC, createTabsModuleListener(), createQuickAccessModuleListener());

		editorMC.init();
		editorMC.injectModules(this);

		Array<EditorModule> modules = extensionStorage.getContainersExtensions(EditorModule.class, ExtensionScope.EDITOR);
		editorMC.addAll(modules);

		settingsDialog.addAll(editorMC.getModules());
	}

	public EditorModuleContainer getEditorModuleContainer () {
		return editorMC;
	}

	private TabbedPaneListener createTabsModuleListener () {
		return new TabbedPaneAdapter() {
			@Override
			public void switchedTab (Tab tab) {
				mainContentTabChanged((MainContentTab) tab);
			}

			@Override
			public void removedAllTabs () {
				mainContentTabChanged(null);
			}
		};
	}

	private TabbedPaneListener createQuickAccessModuleListener () {
		return new TabbedPaneAdapter() {
			@Override
			public void switchedTab (Tab tab) {
				quickAccessViewChanged(tab);
			}

			@Override
			public void removedAllTabs () {
				quickAccessViewChanged(null);
			}
		};
	}

	private void createModulesUI () {
		uiRoot.add(editorMC.get(MenuBarModule.class).getTable()).fillX().expandX().row();
		uiRoot.add(editorMC.get(ToolbarModule.class).getTable()).fillX().expandX().row();
		uiRoot.add(editorMC.get(TabsModule.class).getTable()).fillX().expandX().row();
		uiRoot.add(mainContentTable).expand().fill().row();
		uiRoot.add(editorMC.get(QuickAccessModule.class).getTable()).fillX().expandX().row();
		uiRoot.add(editorMC.get(StatusBarModule.class).getTable()).fillX().expandX().row();
	}

	@Override
	public void resize (int width, int height) {
		stage.getViewport().update(width, height, true);
		editorMC.resize();
		projectMC.resize();
	}

	@Override
	public void render () {
		Color bgColor = colorSettings.getBackgroundColor();
		Gdx.gl.glClearColor(bgColor.r, bgColor.g, bgColor.b, 1f);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
		stage.act(Gdx.graphics.getDeltaTime());
		if (tab != null) tab.render(stage.getBatch());
		stage.draw();
	}

	@Override
	public void dispose () {
		frame.dispose();

		editorMC.dispose();
		if (projectLoaded) projectMC.dispose();

		stage.dispose();
		Assets.dispose();
		VisUI.dispose();

		Log.dispose();

		//make sure that application will exit eventually
		Thread exitThread = new Thread(() -> {
			ThreadUtils.sleep(5000);
			//System.exit(-2);
			//sometimes awt shutdown hook may deadlock on System.exit so I'm using runtime halt
			Runtime.getRuntime().halt(-2);
		}, "Force Exit");

		exitThread.setDaemon(true);
		exitThread.start();
	}

	public void showRestartDialog () {
		OptionDialog optionDialog = DialogUtils.showOptionDialog(stage, "Restart?",
				"Editor restart is required to apply changes", OptionDialogType.YES_NO, new OptionDialogAdapter() {
					@Override
					public void yes () {
						Editor.instance.requestExit(true);
					}
				});

		optionDialog.setNoButtonText("Later");
		optionDialog.setYesButtonText("Restart");
	}

	public void requestExit () {
		requestExit(false);
	}

	/** @see #showRestartDialog() */
	private void requestExit (boolean restartAfterExit) {
		if (exitInProgress) return;
		exitInProgress = true;

		if (projectLoaded == false) {
			showExitDialogIfNeeded(restartAfterExit);
			return;
		}

		if (tabsModule.getDirtyTabCount() > 0) {
			stage.addActor(new UnsavedResourcesDialog(tabsModule, new WindowListener() {
				@Override
				public void finished () {
					showExitDialogIfNeeded(restartAfterExit);
				}

				@Override
				public void canceled () {
					exitInProgress = false;
				}
			}).fadeIn());
		} else
			showExitDialogIfNeeded(restartAfterExit);
	}

	private void showExitDialogIfNeeded (boolean restartAfterExit) {
		//the "Do you want to restart" dialog was already displayed and user accepted so no need to display exit dialog even if it is enabled
		if (restartAfterExit) {
			exit(true);
			return;
		}

		if (settings.isConfirmExit()) {
			OptionDialog dialog = DialogUtils.showOptionDialog(stage, "Confirm Exit", "Are you sure you want to exit VisEditor?", OptionDialogType.YES_CANCEL, new OptionDialogAdapter() {
				@Override
				public void yes () {
					exit(false);
				}

				@Override
				public void cancel () {
					exitInProgress = false;
				}
			});

			dialog.setYesButtonText("Exit");
		} else
			exit(false);
	}

	private void exit (boolean restartAfterExit) {
		if (restartAfterExit) App.startNewInstance();
		Gdx.app.exit();
	}

	public LaunchConfiguration getLaunchConfig () {
		return launchConfig;
	}

	public Stage getStage () {
		return stage;
	}

	public void requestProjectUnload () {
		if (tabsModule.getDirtyTabCount() > 0)
			stage.addActor(new UnsavedResourcesDialog(tabsModule, () -> doProjectUnloading()).fadeIn());
		else
			doProjectUnloading();
	}

	private void doProjectUnloading () {
		projectLoaded = false;
		settingsDialog.removeAll(projectMC.getModules());
		projectMC.dispose();

		statusBar.setText("Project unloaded");
		App.eventBus.post(new ProjectStatusEvent(Status.Unloaded, projectMC.getProject()));
	}

	public void loadProjectDialog () {
		fileChooser.pickFileOrDirectory(new FileChooserAdapter() {
			@Override
			public void selected (FileHandle file) {
				editorMC.get(ProjectIOModule.class).loadHandleError(stage, file);
			}
		});
	}

	public void newProjectDialog () {
		stage.addActor(new NewProjectDialog(fileChooser, projectIO).fadeIn());
	}

	public void projectLoaded (final Project project) {
		if (projectLoaded) {
			DialogUtils.showOptionDialog(stage, "Warning", "Other project is already loaded, unload it and continue?", OptionDialogType.YES_CANCEL, new OptionDialogAdapter() {
				@Override
				public void yes () {
					switchProject(project);
				}
			});

			return;
		}

		ProjectLoadingDialogController controller = new ProjectLoadingDialogController();

		AsyncTaskProgressDialog dialog = new AsyncTaskProgressDialog("Loading Project", new AsyncTask("ProjectLoaderThread") {
			@Override
			public void execute () {
				setProgressPercent(10);
				setMessage("Loading project data...");

				executeOnOpenGL(() -> {
					projectLoaded = true;
					projectMC.setProject(project);
					VisContainers.createProjectModules(projectMC, extensionStorage);
				});

				setMessage("Initializing...");
				setProgressPercent(50);
				ThreadUtils.sleep(10);

				executeOnOpenGL(() -> {
					projectMC.init();

					settingsDialog.addAll(projectMC.getModules());

					statusBar.setText("Project loaded");
					App.eventBus.post(new ProjectStatusEvent(Status.Loaded, project));
					controller.loading = false;
				});

				while (controller.loading) {
					ThreadUtils.sleep(10);
				}
			}
		});
		dialog.setVisible(true);
		stage.addActor(dialog);
	}

	private void switchProject (final Project project) {
		requestProjectUnload();

		Gdx.app.postRunnable(() -> projectLoaded(project));
	}

	public void showSettingsWindow () {
		stage.addActor(settingsDialog.fadeIn());
	}

	private void mainContentTabChanged (MainContentTab tab) {
		this.tab = tab;

		if (tab == null)
			frame.setTitle("VisEditor");
		else
			frame.setTitle("VisEditor - " + tab.getTabTitle());

		tabContentTable.clear();

		if (tab != null)
			tabContentTable.add(tab.getContentTable()).expand().fill();
		else if (projectLoaded)
			tabContentTable.add(noProjectFilesOpenView).center();

		updateRootView();
	}

	private void quickAccessViewChanged (Tab tab) {
		quickAccessTab = tab;
		quickAccessContentTable.clear();

		if (tab != null)
			quickAccessContentTable.add(tab.getContentTable()).expand().fill();

		updateRootView();
	}

	private void updateRootView () {
		mainContentTable.clear();
		splitPane.setWidgets(null, null);

		if (tab != null && tab.getViewMode() == TabViewMode.TAB_ONLY || quickAccessTab == null)
			mainContentTable.add(tabContentTable).expand().fill();
		else {
			splitPane.setWidgets(tabContentTable, quickAccessContentTable);
			mainContentTable.add(splitPane).expand().fill();
		}
	}

	private class ProjectLoadingDialogController {
		public boolean loading = true;
	}
}
