package org.gjt.sp.jedit.gui;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import javax.annotation.Nonnull;
import javax.swing.JOptionPane;

import org.gjt.sp.jedit.ActionSet;
import org.gjt.sp.jedit.Buffer;
import org.gjt.sp.jedit.EBComponent;
import org.gjt.sp.jedit.EBMessage;
import org.gjt.sp.jedit.EditAction;
import org.gjt.sp.jedit.EditBus;
import org.gjt.sp.jedit.GUIUtilities;
import org.gjt.sp.jedit.Mode;
import org.gjt.sp.jedit.View;
import org.gjt.sp.jedit.jEdit;
import org.gjt.sp.jedit.gui.DockableWindowManager.DockingLayout;
import org.gjt.sp.jedit.msg.BufferUpdate;
import org.gjt.sp.jedit.msg.EditPaneUpdate;
import org.gjt.sp.jedit.msg.ViewUpdate;
import org.gjt.sp.jedit.options.DockingOptionPane;
import org.gjt.sp.util.StandardUtilities;

/** Saves and loads dockable layouts to disk
    @author Shlomy Reinstein
    @since jEdit 4.3pre16
*/
public class DockingLayoutManager implements EBComponent
{

	private static final String SAVE_LAYOUT_FAILED = "save-layout-failed.message";
	private static final String LOAD_LAYOUT_TITLE = "load-layout.title";
	private static final String LOAD_LAYOUT_MESSAGE = "load-layout.message";
	private static final String SAVE_LAYOUT_TITLE = "save-layout.title";
	private static final String SAVE_LAYOUT_MESSAGE = "save-layout.message";
	private static ActionSet actions;
	private static DockingLayoutManager instance;
	private final Map<View, String> currentMode;

	private DockingLayoutManager()
	{
		currentMode = new HashMap<>();
	}

	private static boolean save(View view, String layoutName)
	{
		if (jEdit.getSettingsDirectory() == null)
		{
			GUIUtilities.error(view,"no-settings",null);
			return false;
		}
		DockingLayout docking = view.getViewConfig().docking;
		if (docking != null)
		{
			boolean ret = docking.saveLayout(layoutName, DockingLayout.NO_VIEW_INDEX);
			if (! ret)
				return false;
			addAction(layoutName);
		}
		return true;
	}

	public static void saveAs(View view)
	{
		if (jEdit.getSettingsDirectory() == null)
		{
			GUIUtilities.error(view,"no-settings",null);
			return;
		}
		String layoutName = JOptionPane.showInputDialog(view,
			jEdit.getProperty(SAVE_LAYOUT_MESSAGE),
			jEdit.getProperty(SAVE_LAYOUT_TITLE),
			JOptionPane.QUESTION_MESSAGE);
		if (layoutName == null)
			return;
		if (! save(view, layoutName))
			JOptionPane.showMessageDialog(view, jEdit.getProperty(SAVE_LAYOUT_FAILED));
	}

	private static void load(View view, String layoutName)
	{
		if (jEdit.getSettingsDirectory() == null)
		{
			GUIUtilities.error(view,"no-settings",null);
			return;
		}
		DockingLayout docking = View.getDockingFrameworkProvider().createDockingLayout();
		if (docking.loadLayout(layoutName, DockingLayout.NO_VIEW_INDEX))
			view.getDockableWindowManager().setDockingLayout(docking);
	}

	public static void load(View view)
	{
		if (jEdit.getSettingsDirectory() == null)
		{
			GUIUtilities.error(view,"no-settings",null);
			return;
		}
		String layoutName = (String) JOptionPane.showInputDialog(view,
			jEdit.getProperty(LOAD_LAYOUT_MESSAGE),
			jEdit.getProperty(LOAD_LAYOUT_TITLE),
			JOptionPane.QUESTION_MESSAGE,
			null,
			getSavedLayouts(),
			null);
		if (layoutName == null)
			return;
		load(view, layoutName);
	}

	private static String[] getSavedLayouts()
	{
		DockingLayout docking = View.getDockingFrameworkProvider().createDockingLayout();
		String[] layouts = null;
		if (docking != null)
			layouts = docking.getSavedLayouts();
		if (layouts == null)
			return StandardUtilities.EMPTY_STRING_ARRAY;
		return layouts;
	}

	private static void addAction(String layoutName)
	{
		if ((actions != null) && (! actions.contains(layoutName)))
			actions.addAction(new LoadPerspectiveAction(layoutName));
	}

	public static void init()
	{
		createActions();
		instance = new DockingLayoutManager();
		EditBus.addToBus(instance);
	}

	private static void createActions()
	{
		actions = new ActionSet("Docking Layouts");
		String[] layouts = getSavedLayouts();
		for (String layout: layouts)
			addAction(layout);
		jEdit.addActionSet(actions);
		actions.initKeyBindings();
	}

	public static void removeActions()
	{
		jEdit.removeActionSet(actions);
	}

	private static class LoadPerspectiveAction extends EditAction
	{
		private static final String LOAD_PREFIX = "load-";

		LoadPerspectiveAction(String layoutName)
		{
			super(LOAD_PREFIX + layoutName, new String[] { layoutName });
			jEdit.setTemporaryProperty(LOAD_PREFIX + layoutName + ".label", LOAD_PREFIX + layoutName);
		}

		@Override
		public void invoke(View view)
		{
			DockingLayoutManager.load(view, (String) args[0]);
		}
	}

	private static boolean canChangeEditMode(EBMessage message)
	{
		if (message instanceof BufferUpdate)
		{
			BufferUpdate bu = (BufferUpdate) message;
			Object what = bu.getWhat();
			if ((what == BufferUpdate.CLOSED) ||
				(what == BufferUpdate.CREATED) ||
				(what == BufferUpdate.PROPERTIES_CHANGED))
			{
				return true;
			}
		}
		else if (message instanceof EditPaneUpdate)
		{
			EditPaneUpdate ep = (EditPaneUpdate) message;
			Object what = ep.getWhat();
			if ((what == EditPaneUpdate.BUFFER_CHANGED) ||
				(what == EditPaneUpdate.CREATED))
			{
				return true;
			}
		}
		return false;
	}

	@Override
	public void handleMessage(EBMessage message)
	{
		boolean autoLoadModeLayout = jEdit.getBooleanProperty(
			DockingOptionPane.AUTO_LOAD_MODE_LAYOUT_PROP, false);
		if (! autoLoadModeLayout)
			return;
		if (message instanceof ViewUpdate)
		{
			ViewUpdate vu = (ViewUpdate) message;
			if (vu.getWhat() == ViewUpdate.CLOSED)
			{
				View view = jEdit.getActiveView();
				String mode = currentMode.get(view);
				saveModeLayout(view, mode);
				return;
			}
		}
		// Check for a change in the edit mode
		View view = jEdit.getActiveView();
		if (view == null)
			return;
		if (! canChangeEditMode(message))
			return;
		String newMode = getCurrentEditMode(view);
		String mode = currentMode.get(view);
		boolean sameMode = Objects.equals(mode, newMode);
		if (!sameMode)
		{
			boolean autoSaveModeLayout = jEdit.getBooleanProperty(
				DockingOptionPane.AUTO_SAVE_MODE_LAYOUT_PROP, false);
			if (autoSaveModeLayout)
				saveModeLayout(view, mode);
			currentMode.put(view, newMode);
			loadModeLayout(view, newMode);
		}
	}

	private static String getCurrentEditMode(View view)
	{
		Buffer buffer = view.getBuffer();
		if (buffer == null)
			return null;
		Mode bufferMode = buffer.getMode();
		if (bufferMode == null)
			return null;
		return bufferMode.getName();
	}

	private static final String GLOBAL_MODE = "DEFAULT";

	private static void saveModeLayout(View view, String mode)
	{
		String modeLayout = getModePerspective(mode);
		save(view, modeLayout);
	}

	private static void loadModeLayout(View view, String mode)
	{
		String modeLayout = getModePerspective(mode);
		load(view, modeLayout);
	}

	public static void loadCurrentModeLayout(View view)
	{
		if (view == null)
			return;
		String mode = DockingLayoutManager.getCurrentEditMode(view);
		loadModeLayout(view, mode);
	}

	public static void saveCurrentModeLayout(View view)
	{
		if (view == null)
			return;
		String mode = DockingLayoutManager.getCurrentEditMode(view);
		saveModeLayout(view, mode);
	}

	@Nonnull
	private static String getModePerspective(String mode)
	{
		if (mode == null)
			mode = GLOBAL_MODE;
		return "mode-" + mode;
	}
}
