/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import * as strings from 'vs/base/common/strings';
import * as objects from 'vs/base/common/objects';
import * as paths from 'vs/base/common/paths';
import * as platform from 'vs/base/common/platform';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { IRawAdapter } from 'vs/workbench/parts/debug/common/debug';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';

export class Adapter {

	constructor(private rawAdapter: IRawAdapter, public extensionDescription: IExtensionDescription,
		@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService,
		@IConfigurationService private configurationService: IConfigurationService,
		@ICommandService private commandService: ICommandService
	) {
		if (rawAdapter.windows) {
			rawAdapter.win = rawAdapter.windows;
		}
	}

	public get runtime(): string {
		let runtime = this.getAttributeBasedOnPlatform('runtime');
		if (runtime && runtime.indexOf('./') === 0) {
			runtime = this.configurationResolverService ? this.configurationResolverService.resolve(runtime) : runtime;
			runtime = paths.join(this.extensionDescription.extensionFolderPath, runtime);
		}

		return runtime;
	}

	public get program(): string {
		let program = this.getAttributeBasedOnPlatform('program');
		if (program) {
			program = this.configurationResolverService ? this.configurationResolverService.resolve(program) : program;
			program = paths.join(this.extensionDescription.extensionFolderPath, program);
		}

		return program;
	}

	public get runtimeArgs(): string[] {
		return this.getAttributeBasedOnPlatform('runtimeArgs');
	}

	public get args(): string[] {
		return this.getAttributeBasedOnPlatform('args');
	}

	public get aiKey(): string {
		return this.rawAdapter.aiKey;
	}

	public get label(): string {
		return this.rawAdapter.label || this.rawAdapter.type;
	}

	public get type(): string {
		return this.rawAdapter.type;
	}

	public get variables(): { [key: string]: string } {
		return this.rawAdapter.variables;
	}

	public merge(secondRawAdapter: IRawAdapter, extensionDescription: IExtensionDescription): void {
		if (secondRawAdapter.program) {
			secondRawAdapter.program = paths.join(extensionDescription.extensionFolderPath, secondRawAdapter.program);
		}
		if (secondRawAdapter.runtime) {
			secondRawAdapter.runtime = paths.join(extensionDescription.extensionFolderPath, secondRawAdapter.runtime);
		}
		objects.mixin(this.rawAdapter, secondRawAdapter, true);
	}

	public getInitialConfigFileContent(): TPromise<string> {
		const editorConfig = this.configurationService.getConfiguration<any>();
		if (typeof this.rawAdapter.initialConfigurations === 'string') {
			// Contributed initialConfigurations is a command that needs to be invoked
			// Debug adapter will dynamically provide the full launch.json
			return this.commandService.executeCommand<string>(<string>this.rawAdapter.initialConfigurations).then(content => {
				// Debug adapter returned the full content of the launch.json - return it after format
				if (editorConfig.editor.insertSpaces) {
					content = content.replace(new RegExp('\t', 'g'), strings.repeat(' ', editorConfig.editor.tabSize));
				}

				return content;
			});
		}

		return TPromise.as(JSON.stringify(
			{
				version: '0.2.0',
				configurations: this.rawAdapter.initialConfigurations || []
			},
			null,
			editorConfig.editor.insertSpaces ? strings.repeat(' ', editorConfig.editor.tabSize) : '\t'
		));
	};

	public getSchemaAttributes(): IJSONSchema[] {
		if (!this.rawAdapter.configurationAttributes) {
			return null;
		}
		// fill in the default configuration attributes shared by all adapters.
		return Object.keys(this.rawAdapter.configurationAttributes).map(request => {
			const attributes: IJSONSchema = this.rawAdapter.configurationAttributes[request];
			const defaultRequired = ['name', 'type', 'request'];
			attributes.required = attributes.required && attributes.required.length ? defaultRequired.concat(attributes.required) : defaultRequired;
			attributes.additionalProperties = false;
			attributes.type = 'object';
			if (!attributes.properties) {
				attributes.properties = {};
			}
			const properties = attributes.properties;
			properties['type'] = {
				enum: [this.type],
				description: nls.localize('debugType', "Type of configuration.")
			};
			properties['name'] = {
				type: 'string',
				description: nls.localize('debugName', "Name of configuration; appears in the launch configuration drop down menu."),
				default: 'Launch'
			};
			properties['request'] = {
				enum: [request],
				description: nls.localize('debugRequest', "Request type of configuration. Can be \"launch\" or \"attach\"."),
			};
			properties['debugServer'] = {
				type: 'number',
				description: nls.localize('debugServer', "For debug extension development only: if a port is specified VS Code tries to connect to a debug adapter running in server mode")
			};
			properties['preLaunchTask'] = {
				type: ['string', 'null'],
				default: null,
				description: nls.localize('debugPrelaunchTask', "Task to run before debug session starts.")
			};
			properties['internalConsoleOptions'] = {
				enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart'],
				default: 'openOnFirstSessionStart',
				description: nls.localize('internalConsoleOptions', "Controls behavior of the internal debug console.")
			};

			const warnRelativePaths = (attribute: IJSONSchema) => {
				if (attribute) {
					attribute.pattern = '^\\${.*}.*|' + paths.isAbsoluteRegex.source;
					attribute.errorMessage = nls.localize('relativePathsNotConverted', "Relative paths will no longer be automatically converted to absolute ones. Consider using ${workspaceRoot} as a prefix.");
				}
			};
			warnRelativePaths(properties['outDir']);
			warnRelativePaths(properties['program']);
			warnRelativePaths(properties['cwd']);

			const osProperties = objects.deepClone(properties);
			properties['windows'] = {
				type: 'object',
				description: nls.localize('debugWindowsConfiguration', "Windows specific launch configuration attributes."),
				properties: osProperties
			};
			properties['osx'] = {
				type: 'object',
				description: nls.localize('debugOSXConfiguration', "OS X specific launch configuration attributes."),
				properties: osProperties
			};
			properties['linux'] = {
				type: 'object',
				description: nls.localize('debugLinuxConfiguration', "Linux specific launch configuration attributes."),
				properties: osProperties
			};

			return attributes;
		});
	}

	private getAttributeBasedOnPlatform(key: string): any {
		let result: any;
		if (platform.isWindows && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') && this.rawAdapter.winx86) {
			result = this.rawAdapter.winx86[key];
		} else if (platform.isWindows && this.rawAdapter.win) {
			result = this.rawAdapter.win[key];
		} else if (platform.isMacintosh && this.rawAdapter.osx) {
			result = this.rawAdapter.osx[key];
		} else if (platform.isLinux && this.rawAdapter.linux) {
			result = this.rawAdapter.linux[key];
		}

		return result || this.rawAdapter[key];
	}
}
