/**
 * Defines:
 * commands / command registry / code editor commands
 */

// Keyboard shortcut origins:
// c9: cloud9 IDE
// ca: CodeAnywhere
// atom: github atom
// sublime: sublime text
// code: VScode
//---------------------------------------------
//
// This file also sets up Monaco keybindings

import * as Mousetrap from "mousetrap";
require("mousetrap/plugins/global-bind/mousetrap-global-bind");
import * as events from "../../common/events";
import * as utils from "../../common/utils";
import * as types from "../../common/types";

export enum CommandContext {
    Global,
    Editor,
    TreeView,
}

interface UICommandConfig {
    keyboardShortcut?: string;
    description: string;
    context: CommandContext;

    // only valid for editor commands
    // we use this to trigger the command on the editor
    editorCommandName?: string;

    /** allow the browser default to still happen */
    allowDefault?: boolean;
}

/**
 * The command registry composed of commands that are keyboard only
 */
export let commandRegistry: UICommand[] = [];

/**
 * A command is just an event emitter with some useful properties relevant to the front end command registry
 * such commands cannot have a payload
 */
export class UICommand extends events.TypedEvent<{}>{
    constructor(public config: UICommandConfig) {
        super();
        commandRegistry.push(this);
    }
}

/**
 * BAS
 */
// export const bas = new UICommand({
//     description: "BAS: I map this to whatever command I am currently testing",
//     context: CommandContext.Global,
// });


/**
 * General purpose UI escape
 */
export const esc = new UICommand({
    keyboardShortcut: 'esc', // atom
    description: "Close any open dialogs and focus back to any open tab",
    context: CommandContext.Global,
});

/**
 * Active list
 */
export const gotoNext = new UICommand({
    keyboardShortcut: 'mod+f8', // atom
    description: "Main Panel : Goto next error in project",
    context: CommandContext.Global,
});

export const gotoPrevious = new UICommand({
    keyboardShortcut: 'mod+shift+f8', // atom
    description: "Main Panel : Goto previous error in project",
    context: CommandContext.Global,
});

/**
 * Tabs
 */
export const nextTab = new UICommand({
    keyboardShortcut: 'alt+k',
    description: "Tabs: Focus on the Next Tab",
    context: CommandContext.Global,
});
export const prevTab = new UICommand({
    keyboardShortcut: 'alt+j',
    description: "Tabs: Focus on the Previous Tab",
    context: CommandContext.Global,
});
export const closeTab = new UICommand({
    keyboardShortcut: 'alt+w', // c9
    description: "Tabs: Close current tab",
    context: CommandContext.Global,
});
export const undoCloseTab = new UICommand({
    keyboardShortcut: 'shift+alt+w', // Couldn't find IDEs that do this. c9/ca have this bound to close all tabs
    description: "Tabs: Undo close tab",
    context: CommandContext.Global,
});
export const saveTab = new UICommand({
    keyboardShortcut: 'mod+s', // c9
    description: "Tabs: Save current tab",
    context: CommandContext.Global,
});
export const closeOtherTabs = new UICommand({
    description: "Tabs: Close other tabs",
    context: CommandContext.Global,
});
export const closeAllTabs = new UICommand({
    description: "Tabs: Close all tabs",
    context: CommandContext.Global,
});
export const jumpToTab = new UICommand({
    keyboardShortcut: 'mod+shift+enter',
    description: "Tabs: Jump to tab",
    context: CommandContext.Global,
});
export const duplicateTab = new UICommand({
    description: "Tabs: Duplicate",
    context: CommandContext.Global,
});
export const duplicateWindow = new UICommand({
    description: "Window: Duplicate in a new browser window",
    context: CommandContext.Global,
});

/**
 * Build / output js
 */
export const sync = new UICommand({
    keyboardShortcut: 'shift+f6',
    description: "TypeScript: Sync",
    context: CommandContext.Global,
});
export const build = new UICommand({
    keyboardShortcut: 'f6',
    description: "TypeScript: Build",
    context: CommandContext.Global,
});
export const toggleOutputJS = new UICommand({
    keyboardShortcut: 'mod+shift+m', // atom
    description: "TypeScript: Toggle output js file",
    context: CommandContext.Global,
});
export const enableLiveDemo = new UICommand({
    description: "TypeScript: Demo file",
    context: CommandContext.Global,
});
export const disableLiveDemo = new UICommand({
    description: "TypeScript: Demo stop",
    context: CommandContext.Global,
});
export const enableLiveDemoReact = new UICommand({
    description: "TypeScript: Demo react file",
    context: CommandContext.Global,
});
export const disableLiveDemoReact = new UICommand({
    description: "TypeScript: Demo react stop",
    context: CommandContext.Global,
});

/**
 * Tab indexing
 * // c9, chrome, atom
 */
export const gotoTab1 = new UICommand({
    keyboardShortcut: 'mod+1',
    description: "Tabs: Goto Tab 1",
    context: CommandContext.Global,
});
export const gotoTab2 = new UICommand({
    keyboardShortcut: 'mod+2',
    description: "Tabs: Goto Tab 2",
    context: CommandContext.Global,
});
export const gotoTab3 = new UICommand({
    keyboardShortcut: 'mod+3',
    description: "Tabs: Goto Tab 3",
    context: CommandContext.Global,
});
export const gotoTab4 = new UICommand({
    keyboardShortcut: 'mod+4',
    description: "Tabs: Goto Tab 4",
    context: CommandContext.Global,
});
export const gotoTab5 = new UICommand({
    keyboardShortcut: 'mod+5',
    description: "Tabs: Goto Tab 5",
    context: CommandContext.Global,
});
export const gotoTab6 = new UICommand({
    keyboardShortcut: 'mod+6',
    description: "Tabs: Goto Tab 6",
    context: CommandContext.Global,
});
export const gotoTab7 = new UICommand({
    keyboardShortcut: 'mod+7',
    description: "Tabs: Goto Tab 7",
    context: CommandContext.Global,
});
export const gotoTab8 = new UICommand({
    keyboardShortcut: 'mod+8',
    description: "Tabs: Goto Tab 8",
    context: CommandContext.Global,
});
export const gotoTab9 = new UICommand({
    keyboardShortcut: 'mod+9',
    description: "Tabs: Goto Tab 9",
    context: CommandContext.Global,
});

/**
 * OmniSearch
 */
export const omniFindFile = new UICommand({
    keyboardShortcut: 'mod+o',  // atom,sublime
    description: "Find a file in the working directory",
    context: CommandContext.Global,
});
export const omniFindCommand = new UICommand({
    keyboardShortcut: 'mod+shift+p', // atom,sublime
    description: "Find a command",
    context: CommandContext.Global,
});
export const omniSelectProject = new UICommand({
    keyboardShortcut: 'alt+shift+p', // atom:projectmanager package
    description: "Find and set active project",
    context: CommandContext.Global,
});
export const omniProjectSymbols = new UICommand({
    keyboardShortcut: 'mod+shift+h',
    description: "Find Symbols (Hieroglyphs) in active project",
    context: CommandContext.Global,
});
export const omniProjectSourcefile = new UICommand({
    keyboardShortcut: 'mod+p', //
    description: "Find Source File in active project",
    context: CommandContext.Global,
});


/**
 * FAR find and replace
 */
export const findAndReplace = new UICommand({
    keyboardShortcut: 'mod+f', // atom,sublime,c9
    description: "Show find and replace dialog",
    context: CommandContext.Global,
});
export const findAndReplaceMulti = new UICommand({
    keyboardShortcut: 'mod+shift+f', // atom,sublime,c9
    description: "Show find and replace in files",
    context: CommandContext.Global,
});
export const findNext = new UICommand({
    keyboardShortcut: 'f3', // atom,sublime
    description: "Find the next search result",
    context: CommandContext.Global,
});
export const findPrevious = new UICommand({
    keyboardShortcut: 'shift+f3', // atom,sublime
    description: "Find the previous search result",
    context: CommandContext.Global,
});
export const replaceNext = new events.TypedEvent<{ newText: string }>();
export const replacePrevious = new events.TypedEvent<{ newText: string }>();
export const replaceAll = new events.TypedEvent<{ newText: string }>();

/**
 * Error panel
 */
export let toggleMessagePanel = new UICommand({
    keyboardShortcut: 'mod+;', //
    description: "Toggle Message Panel",
    context: CommandContext.Global,
});

export let cycleMessagesPanel = new UICommand({
    keyboardShortcut: 'mod+shift+;', //
    description: "Cycle Message Panel",
    context: CommandContext.Global,
});

/**
 * Documentation features
 */
export let toggleDoctor = new UICommand({
    keyboardShortcut: "mod+'", //
    description: "Editor: Toggle Doctor",
    context: CommandContext.Global,
});
export const toggleDocumentationBrowser = new UICommand({
    keyboardShortcut: 'mod+shift+\'', // Same as doctor with Shift
    description: "Documentation Browser: Open",
    context: CommandContext.Global,
});
export const doOpenUmlDiagram = new UICommand({
    description: "UML Class diagram",
    context: CommandContext.Global,
});
export const toggleSemanticView = new UICommand({
    description: "Toggle Semantic View",
    context: CommandContext.Global,
});
export const launchTsFlow = new UICommand({
    description: "Launch TypeScript flow based programming",
    context: CommandContext.Global,
});
export const doOpenTestResultsView = new UICommand({
    description: "Test Results View",
    context: CommandContext.Global,
});

/**
 * Cursor history
 */
export let previousCursorLocation = new UICommand({
    keyboardShortcut: "mod+u", // CM undo cursor
    description: "Cursor: Previous Cursor Location",
    context: CommandContext.Global,
});
export let nextCursorLocation = new UICommand({
    keyboardShortcut: "mod+shift+u", // CM redo cursor
    description: "Cursor: Next Cursor Location",
    context: CommandContext.Global,
});

/**
 * Clipboard Ring
 */
export const copy = new UICommand({
    keyboardShortcut: 'mod+c', // atom
    description: "Copy",
    context: CommandContext.Global,
    allowDefault: true
});
export const cut = new UICommand({
    keyboardShortcut: 'mod+x', // atom
    description: "Cut",
    context: CommandContext.Global,
    allowDefault: true
});
export const pasteFromRing = new UICommand({
    keyboardShortcut: 'mod+shift+v', // VS
    description: "PasteFromRing",
    context: CommandContext.Global,
    allowDefault: false
});

/**
 * Tree view
 */
export let treeViewToggle = new UICommand({
    keyboardShortcut: 'mod+\\',    // atom
    description: "Tree View: Toggle",
    context: CommandContext.Global,
});
export let treeViewRevealActiveFile = new UICommand({
    keyboardShortcut: 'mod+shift+\\',    // atom
    description: "Tree View: Reveal Active File",
    context: CommandContext.Global,
});
export let treeViewFocus = new UICommand({
    keyboardShortcut: 'mod+0',    // atom, code
    description: "Tree View: Focus",
    context: CommandContext.Global,
});
export let treeAddFile = new UICommand({
    keyboardShortcut: 'a',    // atom
    description: "Tree View: Add File",
    context: CommandContext.TreeView,
});
export let treeAddFolder = new UICommand({
    keyboardShortcut: 'shift+a',    // atom
    description: "Tree View: Add Folder",
    context: CommandContext.TreeView,
});

export let treeDuplicateFile = new UICommand({
    keyboardShortcut: 'd',    // atom
    description: "Tree View: Duplicate File|Folder",
    context: CommandContext.TreeView,
});
export let treeMoveFile = new UICommand({
    keyboardShortcut: 'm',    // atom
    description: "Tree View: Move File|Folder",
    context: CommandContext.TreeView,
});
/** Rename is same as `move` but people want to search for it */
export let treeRenameFile = new UICommand({
    keyboardShortcut: 'r',
    description: "Tree View: Rename File|Folder",
    context: CommandContext.TreeView,
});
export let treeDeleteFile = new UICommand({
    keyboardShortcut: 'del',    // atom
    description: "Tree View: Delete File|Folder",
    context: CommandContext.TreeView,
});
export let treeOpenInExplorerFinder = new UICommand({
    keyboardShortcut: 'o',    // natural
    description: "Tree View: Open folder in explorer / finder",
    context: CommandContext.TreeView,
});
export let treeOpenInCmdTerminal = new UICommand({
    keyboardShortcut: 'shift+o',    // natural
    description: "Tree View: Open folder in cmd / terminal",
    context: CommandContext.TreeView,
});

/**
 * General purpose file opening
 * These are handled in appTabsContainer at the moment
 */
export const doOpenFile = new events.TypedEvent<{ filePath: string, position?: EditorPosition }>();
export const doOpenOrFocusFile = new events.TypedEvent<{ filePath: string, position?: EditorPosition }>();
export const openFileFromDisk = new UICommand({
    keyboardShortcut: 'mod+shift+o',
    description: 'Open a file present on server disk',
    context: CommandContext.Global,
});
/** needed by cursor history */
export const doOpenOrFocusTab = new events.TypedEvent<{ tabId: string, tabUrl: string, position: EditorPosition }>();
/** needed by file tree */
export const closeFilesDirs = new events.TypedEvent<{ files: string[], dirs: string[] }>();
/** Needed by file tree, activates the tab but doesn't change focus away from tree view */
export const doOpenOrActivateFileTab = new events.TypedEvent<{ filePath: string }>();
/** Needed to toggle output js file. We toggle and also do not steal focus */
export const doToggleFileTab = new events.TypedEvent<{ filePath: string }>();
/** Needed to ensure that a demo view is open */
export const ensureLiveDemoTab = new events.TypedEvent<{ filePath: string }>();
export const closeDemoTab = new events.TypedEvent<{}>();
export const ensureLiveDemoReactTab = new events.TypedEvent<{ filePath: string }>();
export const closeDemoReactTab = new events.TypedEvent<{}>();

/**
 * Other tab types
 */
export const doOpenDependencyView = new UICommand({
    description: 'Open Dependency View',
    context: CommandContext.Global,
});
export const doOpenASTView = new UICommand({
    description: 'Open AST View',
    context: CommandContext.Global,
});
export const doOpenASTFullView = new UICommand({
    description: 'Open AST-Full View',
    context: CommandContext.Global,
});

/**
 * Common configuration file creations
 */
export const createEditorconfig = new UICommand({
    description: 'Create a .editorconfig',
    context: CommandContext.Global,
});

/**
 * Settings stuff
 */
export const openSettingsFile = new UICommand({
    description: 'Open settings file',
    context: CommandContext.Global
})

/**
 * Git
 */
export const gitAddAllCommitAndPush = new UICommand({
    description: 'Git: Add all, Commit and Push',
    context: CommandContext.Global
})
export const gitFetchLatestAndRebase = new UICommand({
    description: 'Git: Fetch + Pull latest, and rebase any local commits',
    context: CommandContext.Global
})
/** Whenever status might be invalid */
export const gitStatusNeedsRefresh = new events.TypedEvent<{}>();

/**
 * Registration
 */
export function register() {

    commandRegistry.forEach(c => {
        if (c.config.context == CommandContext.Global
            && c.config.keyboardShortcut) {
            Mousetrap.bindGlobal(c.config.keyboardShortcut, function() {
                c.emit({});
                return !!c.config.allowDefault;
            });
        }
    });
}

/**
 *
 * CODE MIRROR
 *
 */

/**
* Straight out of codemirror.js
*/
export const ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
export const mac = ios || /Mac/.test(navigator.platform);
export const windows = /win/i.test(navigator.platform);
/** Nice display name for the mod by user platform */
export const modName = mac ? '⌘' : 'Ctrl';
const mod = mac ? 'Cmd' : 'Ctrl';

/** Load editor actions + keymaps */
import { getActions } from "./monacoActionLoader";
const actions = getActions();
// console.log(actions); // DEBUG

if (mac) {
    // TODO: mon
    // Prevent the browser from handling the CMD + SHIFT + [ (or ]) which monaco uses for fold / unfold
}

function addEditorMapToCommands(command: types.MonacoActionInformation) {
    new UICommand({
        keyboardShortcut: command.kbd,
        description: `Editor: ${command.label}`,
        context: CommandContext.Editor,
        editorCommandName: command.id,
    });
}
actions.forEach(addEditorMapToCommands)

/**
 * This is a consolidation of the `file edited` and `file changed on disk`
 */
export const fileContentsChanged = new events.TypedEvent<{ filePath: string }>();

/**
 * Setup toasts to hide on esc
 * Note: this is done here instead of `ui` as some commands depend on `ui` and having depend on commands causes a circular dependency
 */
import * as toastr from "toastr";
esc.on(() => {
    toastr.clear();
});

/* DEBUG
console.table(
    commandRegistry
        .filter(c=>c.config.context == CommandContext.Editor)
        .map(c=>({cmd:c.config.description, shortcut:c.config.keyboardShortcut}))
);
/* DEBUG */

/**
 * Mac (the chrome browser in mac) doesn't have *cmd + y* (common redo).
 * Instead it opens the browser history by mistake.
 * So we redirect it to redo for any open editor :)
 */
Mousetrap.bindGlobal('mod+y', function(event: any) {
    // If the focus is on editor than monaco already handles it
    // If we made it till here .... then ....
    // Prevent default
    return false;
});
/**
 * Mac: Cmd + H at the wrong place hides the window.
 */
Mousetrap.bindGlobal('mod+h', function(event: any) {
    // If the focus is on editor than monaco already handles it
    // If we made it till here .... then ....
    // Prevent default
    return false;
});
