// *****************************************************************************
// Copyright (C) 2017 Ericsson and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify';
import { ILogger, ContributionProvider, CommandContribution, Command, CommandRegistry, MenuContribution, MenuModelRegistry, nls } from '@theia/core/lib/common';
import { QuickOpenTask, TaskTerminateQuickOpen, TaskRunningQuickOpen, TaskRestartRunningQuickOpen } from './quick-open-task';
import {
    FrontendApplication, FrontendApplicationContribution, QuickAccessContribution,
    KeybindingRegistry, KeybindingContribution, StorageService, StatusBar, StatusBarAlignment, CommonMenus
} from '@theia/core/lib/browser';
import { WidgetManager } from '@theia/core/lib/browser/widget-manager';
import { TaskContribution, TaskResolverRegistry, TaskProviderRegistry } from './task-contribution';
import { TaskService } from './task-service';
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
import { TaskSchemaUpdater } from './task-schema-updater';
import { TaskConfiguration, TaskWatcher } from '../common';
import { EditorManager } from '@theia/editor/lib/browser';
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';

export namespace TaskCommands {
    const TASK_CATEGORY = 'Task';
    const TASK_CATEGORY_KEY = nls.getDefaultKey(TASK_CATEGORY);
    export const TASK_RUN = Command.toDefaultLocalizedCommand({
        id: 'task:run',
        category: TASK_CATEGORY,
        label: 'Run Task...'
    });

    export const TASK_RUN_BUILD = Command.toDefaultLocalizedCommand({
        id: 'task:run:build',
        category: TASK_CATEGORY,
        label: 'Run Build Task'
    });

    export const TASK_RUN_TEST = Command.toDefaultLocalizedCommand({
        id: 'task:run:test',
        category: TASK_CATEGORY,
        label: 'Run Test Task'
    });

    export const WORKBENCH_RUN_TASK = Command.toLocalizedCommand({
        id: 'workbench.action.tasks.runTask',
        category: TASK_CATEGORY
    }, '', TASK_CATEGORY_KEY);

    export const TASK_RUN_LAST = Command.toDefaultLocalizedCommand({
        id: 'task:run:last',
        category: TASK_CATEGORY,
        label: 'Rerun Last Task'
    });

    export const TASK_ATTACH = Command.toLocalizedCommand({
        id: 'task:attach',
        category: TASK_CATEGORY,
        label: 'Attach Task...'
    }, 'theia/task/attachTask', TASK_CATEGORY_KEY);

    export const TASK_RUN_TEXT = Command.toDefaultLocalizedCommand({
        id: 'task:run:text',
        category: TASK_CATEGORY,
        label: 'Run Selected Text'
    });

    export const TASK_CONFIGURE = Command.toDefaultLocalizedCommand({
        id: 'task:configure',
        category: TASK_CATEGORY,
        label: 'Configure Tasks...'
    });

    export const TASK_OPEN_USER = Command.toDefaultLocalizedCommand({
        id: 'task:open_user',
        category: TASK_CATEGORY,
        label: 'Open User Tasks'
    });

    export const TASK_CLEAR_HISTORY = Command.toLocalizedCommand({
        id: 'task:clear-history',
        category: TASK_CATEGORY,
        label: 'Clear History'
    }, 'theia/task/clearHistory', TASK_CATEGORY_KEY);

    export const TASK_SHOW_RUNNING = Command.toDefaultLocalizedCommand({
        id: 'task:show-running',
        category: TASK_CATEGORY,
        label: 'Show Running Tasks'
    });

    export const TASK_TERMINATE = Command.toDefaultLocalizedCommand({
        id: 'task:terminate',
        category: TASK_CATEGORY,
        label: 'Terminate Task'
    });

    export const TASK_RESTART_RUNNING = Command.toDefaultLocalizedCommand({
        id: 'task:restart-running',
        category: TASK_CATEGORY,
        label: 'Restart Running Task...'
    });
}

const TASKS_STORAGE_KEY = 'tasks';

@injectable()
export class TaskFrontendContribution implements CommandContribution, MenuContribution, KeybindingContribution, FrontendApplicationContribution, QuickAccessContribution {
    @inject(QuickOpenTask)
    protected readonly quickOpenTask: QuickOpenTask;

    @inject(EditorManager)
    protected readonly editorManager: EditorManager;

    @inject(FrontendApplication)
    protected readonly app: FrontendApplication;

    @inject(ILogger) @named('task')
    protected readonly logger: ILogger;

    @inject(WidgetManager)
    protected readonly widgetManager: WidgetManager;

    @inject(ContributionProvider) @named(TaskContribution)
    protected readonly contributionProvider: ContributionProvider<TaskContribution>;

    @inject(TaskProviderRegistry)
    protected readonly taskProviderRegistry: TaskProviderRegistry;

    @inject(TaskResolverRegistry)
    protected readonly taskResolverRegistry: TaskResolverRegistry;

    @inject(TaskService)
    protected readonly taskService: TaskService;

    @inject(TaskSchemaUpdater)
    protected readonly schemaUpdater: TaskSchemaUpdater;

    @inject(StorageService)
    protected readonly storageService: StorageService;

    @inject(TaskRunningQuickOpen)
    protected readonly taskRunningQuickOpen: TaskRunningQuickOpen;

    @inject(TaskTerminateQuickOpen)
    protected readonly taskTerminateQuickOpen: TaskTerminateQuickOpen;

    @inject(TaskRestartRunningQuickOpen)
    protected readonly taskRestartRunningQuickOpen: TaskRestartRunningQuickOpen;

    @inject(TaskWatcher)
    protected readonly taskWatcher: TaskWatcher;

    @inject(StatusBar)
    protected readonly statusBar: StatusBar;

    @inject(WorkspaceService)
    protected readonly workspaceService: WorkspaceService;

    @postConstruct()
    protected init(): void {
        this.taskWatcher.onTaskCreated(() => this.updateRunningTasksItem());
        this.taskWatcher.onTaskExit(() => this.updateRunningTasksItem());
    }

    onStart(): void {
        this.contributionProvider.getContributions().forEach(contrib => {
            if (contrib.registerResolvers) {
                contrib.registerResolvers(this.taskResolverRegistry);
            }
            if (contrib.registerProviders) {
                contrib.registerProviders(this.taskProviderRegistry);
            }
        });
        this.schemaUpdater.update();

        this.storageService.getData<{ recent: TaskConfiguration[] }>(TASKS_STORAGE_KEY, { recent: [] })
            .then(tasks => this.taskService.recentTasks = tasks.recent);
    }

    onStop(): void {
        const recent = this.taskService.recentTasks;
        this.storageService.setData<{ recent: TaskConfiguration[] }>(TASKS_STORAGE_KEY, { recent });
    }

    /**
     * Contribute a status-bar item to trigger
     * the `Show Running Tasks` command.
     */
    protected async updateRunningTasksItem(): Promise<void> {
        const id = 'show-running-tasks';
        const items = await this.taskService.getRunningTasks();
        if (!!items.length) {
            this.statusBar.setElement(id, {
                text: `$(tools) ${items.length}`,
                tooltip: TaskCommands.TASK_SHOW_RUNNING.label,
                alignment: StatusBarAlignment.LEFT,
                priority: 2,
                command: TaskCommands.TASK_SHOW_RUNNING.id,
            });
        } else {
            this.statusBar.removeElement(id);
        }
    }

    registerCommands(registry: CommandRegistry): void {
        registry.registerCommand(
            TaskCommands.WORKBENCH_RUN_TASK,
            {
                isEnabled: () => true,
                execute: async (label: string) => {
                    const didExecute = await this.taskService.runTaskByLabel(this.taskService.startUserAction(), label);
                    if (!didExecute) {
                        this.quickOpenTask.open();
                    }
                }
            }
        );

        registry.registerCommand(
            TaskCommands.TASK_RUN,
            {
                isEnabled: () => true,
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                execute: (...args: any[]) => {
                    const [source, label, scope] = args;
                    if (source && label) {
                        return this.taskService.run(this.taskService.startUserAction(), source, label, scope);
                    }
                    return this.quickOpenTask.open();
                }
            }
        );
        registry.registerCommand(
            TaskCommands.TASK_RUN_BUILD,
            {
                isEnabled: () => this.workspaceService.opened,
                execute: () =>
                    this.quickOpenTask.runBuildOrTestTask('build')
            }
        );
        registry.registerCommand(
            TaskCommands.TASK_RUN_TEST,
            {
                isEnabled: () => this.workspaceService.opened,
                execute: () =>
                    this.quickOpenTask.runBuildOrTestTask('test')
            }
        );
        registry.registerCommand(
            TaskCommands.TASK_ATTACH,
            {
                isEnabled: () => true,
                execute: () => this.quickOpenTask.attach()
            }
        );
        registry.registerCommand(
            TaskCommands.TASK_RUN_LAST,
            {
                execute: async () => {
                    if (!await this.taskService.runLastTask(this.taskService.startUserAction())) {
                        await this.quickOpenTask.open();
                    }
                }
            }
        );
        registry.registerCommand(
            TaskCommands.TASK_RUN_TEXT,
            {
                isVisible: () => !!this.editorManager.currentEditor,
                isEnabled: () => !!this.editorManager.currentEditor,
                execute: () => this.taskService.runSelectedText()
            }
        );

        registry.registerCommand(
            TaskCommands.TASK_CONFIGURE,
            {
                execute: () => this.quickOpenTask.configure()
            }
        );

        registry.registerCommand(
            TaskCommands.TASK_OPEN_USER,
            {
                execute: () => {
                    this.taskService.openUserTasks();
                }
            }
        );

        registry.registerCommand(
            TaskCommands.TASK_CLEAR_HISTORY,
            {
                execute: () => this.taskService.clearRecentTasks()
            }
        );

        registry.registerCommand(
            TaskCommands.TASK_SHOW_RUNNING,
            {
                execute: () => this.taskRunningQuickOpen.open()
            }
        );

        registry.registerCommand(
            TaskCommands.TASK_TERMINATE,
            {
                execute: () => this.taskTerminateQuickOpen.open()
            }
        );

        registry.registerCommand(
            TaskCommands.TASK_RESTART_RUNNING,
            {
                execute: () => this.taskRestartRunningQuickOpen.open()
            }
        );
    }

    registerMenus(menus: MenuModelRegistry): void {
        menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS, {
            commandId: TaskCommands.TASK_RUN.id,
            order: '0'
        });

        menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS, {
            commandId: TaskCommands.TASK_RUN_BUILD.id,
            order: '1'
        });

        menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS, {
            commandId: TaskCommands.TASK_RUN_TEST.id,
            order: '2'
        });

        menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS, {
            commandId: TaskCommands.TASK_RUN_LAST.id,
            order: '3'
        });

        menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS, {
            commandId: TaskCommands.TASK_ATTACH.id,
            order: '4'
        });

        menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS, {
            commandId: TaskCommands.TASK_RUN_TEXT.id,
            order: '5'
        });

        menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS_INFO, {
            commandId: TaskCommands.TASK_SHOW_RUNNING.id,
            label: TaskCommands.TASK_SHOW_RUNNING.label + '...',
            order: '0'
        });

        menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS_INFO, {
            commandId: TaskCommands.TASK_RESTART_RUNNING.id,
            label: TaskCommands.TASK_RESTART_RUNNING.label,
            order: '1'
        });

        menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS_INFO, {
            commandId: TaskCommands.TASK_TERMINATE.id,
            label: TaskCommands.TASK_TERMINATE.label + '...',
            order: '2'
        });

        menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS_CONFIG, {
            commandId: TaskCommands.TASK_CONFIGURE.id,
            order: '0'
        });

        menus.registerMenuAction(CommonMenus.MANAGE_SETTINGS, {
            commandId: TaskCommands.TASK_OPEN_USER.id,
            label: nls.localizeByDefault('Tasks'),
            order: 'a40'
        });
    }

    registerQuickAccessProvider(): void {
        this.quickOpenTask.registerQuickAccessProvider();
    }

    registerKeybindings(keybindings: KeybindingRegistry): void {
        keybindings.registerKeybinding({
            command: TaskCommands.TASK_RUN_LAST.id,
            keybinding: 'ctrlcmd+shift+k',
            when: '!textInputFocus || editorReadonly'
        });
    }
}
