import * as vscode from 'vscode';
import * as Request from './Requests';
import * as ReplyCallback from './ReplyCallbacks';
import { SemanticQueryView } from './SemanticQueryView';
import { RefactorErlProcess } from './RefactorErlProcess';
import { DuplicateCodeView } from './DuplicateCodeView';
import { DepGraphView } from './DepGraphView';

/**
 * Initialize View classes
 */
let RefErlModule = new RefactorErlProcess();
let DuplicateView = new DuplicateCodeView();
let GraphView = new DepGraphView();
let SQ_View = new SemanticQueryView();
let RefErlOut = vscode.window.createOutputChannel("RefactorErl Output");

/**
 * Initializes the extension
 * Cretes the commands, event listeners & statusbar items
 * Also reads the extension settings & sets up event listeners for changes
 * @param context Extension context which the extension uses
 */
export function activate(context: vscode.ExtensionContext) {

	console.log('RefactorErl Extension is running!');

	let ToggleButton = createStatusBarIcon("referl.toggle", `$(bell)`, "Turn on RefactorErl", 2, "Red");
	context.subscriptions.push(ToggleButton);

	let UndoButton = createStatusBarIcon("referl.undo", `$(reply)`, "RefactorErl - Undo last transform");
	context.subscriptions.push(UndoButton);

	if(vscode.workspace.getConfiguration('RefactorErl').get('icons')){
		ToggleButton.show();
		UndoButton.show();
	}

	SetDataDir();

	vscode.workspace.onDidChangeConfiguration(() => {
		SetDataDir();
		if(vscode.workspace.getConfiguration('RefactorErl').get('icons')){
			ToggleButton.show();
			UndoButton.show();
		}
		else{
			ToggleButton.hide();
			UndoButton.hide();
		}
	});

	RefErlModule.RefErlEvents.on('error', (ErrorInfo:string) => { WriteOutput("RefactorErl Error: "+ErrorInfo); });
	RefErlModule.RefErlEvents.on('output', (received:string) => { WriteOutput(received); });
	RefErlModule.RefErlEvents.on('sq_result', (received:any) => { SQ_View.updateResult(received); });
	RefErlModule.RefErlEvents.on('dup_code', (received:any) => { DuplicateView.updateCodeList(received); });
	RefErlModule.RefErlEvents.on('dep_graph', (received:any) => { GraphView.UpdateGraph(received); });
	RefErlModule.RefErlEvents.on('StateChange', (info:any) => {
		ToggleButton.color = info.Col;
		ToggleButton.tooltip = info.Prompt;
		WriteOutput(info.Msg);
	});

	context.subscriptions.push(vscode.commands.registerCommand('referl.toggle', () => { RefErlModule.toggle(); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.kill', () => { RefErlModule.ForceStop(); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.ls', () => { RefErlModule.Request('{filelist}.\n', ReplyCallback.CreateLsResponse); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.resetdb', () => { RefErlModule.Request('{reset}.\n');	}));
	context.subscriptions.push(vscode.commands.registerCommand('referl.undo', () => { RefErlModule.Request('{undo, []}.\n');	}));

	context.subscriptions.push(vscode.commands.registerCommand('referl.add', (arg) => { SendRequest("add", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.drop', (arg) => { SendRequest("drop", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.updateState', (arg) => { SendRequest("status", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.add_dir', async () => { 
		let dir = await vscode.window.showOpenDialog({canSelectFiles:false, canSelectFolders:true, canSelectMany:false});
		if(dir !== undefined){ SendRequest("add_dir", getFileUri(dir[0])); }
	 }));
	 context.subscriptions.push(vscode.commands.registerCommand('referl.drop_dir', async () => { 
		 let dir = await vscode.window.showOpenDialog({canSelectFiles:false, canSelectFolders:true, canSelectMany:false});
		 if(dir !== undefined){ SendRequest("drop_dir", getFileUri(dir[0])); }
	  }));

	context.subscriptions.push(vscode.commands.registerCommand('referl.renameModule', (arg) => { Transform("rename_mod", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.renameHeader', (arg) => { Transform("rename_header", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.renameMac', (arg) => { Transform("rename_mac", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.renameRec', (arg) => { Transform("rename_rec", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.renameFun', (arg) => { Transform("rename_fun", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.moveMac', (arg) => { Transform("move_mac", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.moveRec', (arg) => { Transform("move_rec", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.moveFun', (arg) => { Transform("move_fun", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.renameRecField', (arg) => { Transform("rename_recfield", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.reordFunParams', (arg) => { Transform("reorder_funpar", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.renameUnusVars', (arg) => { Transform("rename_unused_vars", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.renameVar', (arg) => { Transform("rename_var", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.elimFunCall', (arg) => { Transform("inline_fun", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.elimFunExpr', (arg) => { Transform("expand_funexpr", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.elimMacSub', (arg) => { Transform("inline_mac", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.elimVar', (arg) => { Transform("elim_var", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.elimImport', (arg) => { Transform("eliminate_import", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.introduceFun', (arg) => { Transform("extract_fun", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.introduceRec', (arg) => { Transform("introduce_rec", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.introduceVar', (arg) => { Transform("merge", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.introduceTuple', (arg) => { Transform("tuple_funpar", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.introduceImport', (arg) => { Transform("introduce_import", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.introduceProc', (arg) => { Transform("funapp_to_proc", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.introduceFunArg', (arg) => { Transform("gen", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.genfunspec', (arg) => { Transform("genspec", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.transfListComp', (arg) => { Transform("list_comp", getFileUri(arg), getPosParam()); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.clustering', (arg) => { Transform("clustering", getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.elimDuplicate', (arg) => { Transform("dupcode", getFileUri(arg)); }));

	context.subscriptions.push(vscode.commands.registerCommand('referl.runQuery', (arg) => { SemanticQuery(getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.showLastSQ', () => { SQ_View.ShowLastResult(); }));

	context.subscriptions.push(vscode.commands.registerCommand('referl.metricq', () => { MetricQ(); }));

	context.subscriptions.push(vscode.commands.registerCommand('referl.duplicateCheck', (arg) => { DuplicateAnalysis(getFileUri(arg)); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.showDupl', () => { DuplicateView.Display(); }));

	context.subscriptions.push(vscode.commands.registerCommand('referl.drawDepGraph', () => { DepGraph(); }));
	context.subscriptions.push(vscode.commands.registerCommand('referl.showDepGraph', () => { GraphView.Display(); }));
}

/**
 * Deactivates the extension
 */
export function deactivate() {
	RefErlModule.ForceStop();
	GraphView.clean();
}

/**
 * Sends a simple request
 * @param command Command of the request
 * @param Path Path of the file
 */
function SendRequest(command:string, Path?:string):void{
	if(Path === undefined){
		WriteOutput("RefactorErl Error: Not an Erlang file.");
		return;
	}
	RefErlModule.Request(Request.SimpleReq(command, Path));
}

/**
 * Sends a transform request
 * @param command Command of the request
 * @param Path Path of the file
 * @param Args Extra request arguments if necessary
 */
function Transform(command:string, Path?:string, Args:string=""):void{
	if(Path === undefined){
		WriteOutput("RefactorErl Error: Not an Erlang file.");
		return;
	}
	RefErlModule.Request(Request.TransformReq(command, Path, Args), ReplyCallback.CreateTransformResponse);
}

/**
 * Starts a duplicate code analysis
 * @param Path Path of the file
 */
async function DuplicateAnalysis(Path?:string):Promise<void>{
	if(Path === undefined){
		WriteOutput("RefactorErl Error: Not an Erlang file.");
		return;
	}
	let picked = await vscode.window.showQuickPick(
			["Matrix", "Suffix Tree", "SW Metrics", "MatrixFilter", "Filtered Suffix Tree"],
			{placeHolder:"Select a search method"});
	let Pos = getPosNumbers();
	if(picked === undefined || Pos === undefined){ return; }
	let option = picked.replace(new RegExp(" ", 'g'), "_").toLowerCase();
	RefErlModule.Request(
		Request.DuplicateCodeReq(option, Path, Pos.Start, Pos.End),
		ReplyCallback.CreateDuplicateResponse
	);
}

/**
 * Stars a semantic query request
 * @param Path Path of the file
 */
async function SemanticQuery(Path?:string):Promise<void>{
	if(Path === undefined){
		WriteOutput("RefactorErl Error: Not an Erlang file.");
		return;
	}
	let querystr = await vscode.window.showInputBox(
					{prompt: "Enter query command", value:"mods.funs"});
	if(querystr === undefined){ return; }
	RefErlModule.Request(
		Request.SemQuery(FormatQueryString(querystr),
		Path, getPosParam(true)), ReplyCallback.CreateSQResponse
	);
}

/** Starts a metric query request */
async function MetricQ():Promise<void>{
	let querystr = await vscode.window.showInputBox({prompt: "Enter query command"});
	if(querystr === undefined){ return; }
	RefErlModule.Request(
		Request.MetricQuery(FormatQueryString(querystr)),
							ReplyCallback.CreateMetricQueryResponse);
}

/** Starts a dependece analysis */
async function DepGraph():Promise<void>{
	let command = await GraphView.newGraph();
	if(command !== undefined){
		RefErlModule.Request(command, ReplyCallback.CreateGraphResponse);
	}
}

/**
 * Write message on the extension output
 * @param Received message to write
 */
function WriteOutput(Received:string):void{
	if(RefErlOut === undefined){
		vscode.window.createOutputChannel("RefactorErl Output");
	}
	RefErlOut.append(Received + '\n');
	RefErlOut.show();
}

/** Sets the data directory of the extension if necessary */
function SetDataDir():void{
	let current_config = vscode.workspace.getConfiguration('RefactorErl');
	const fs = require('fs');
	if(current_config.get<string>('DataDirectory') === "" || !fs.existsSync(current_config.get<string>('DataDirectory'))){
		current_config.update('DataDirectory', current_config.get<string>('BasePath')+"/data",
								vscode.ConfigurationTarget.Global);
	}
}

/**
 * Formats the '\' & '""' chars in the query command
 * @param str Query command
 * @returns Formatted command
 */
function FormatQueryString(str:string):string{
    return str.replace(/\\/g, "\\\\")
            .replace(new RegExp('\"', 'g'), "\\\"");
}

/**
 * Creates a statusbar item for the extension
 * @param command Command to call upon click
 * @param text Text of the icon
 * @param tooltip Tooltip on hover
 * @param priority Priority on the statusbar
 * @param color Color of the icon
 * @returns Created statusbar item
 */
function createStatusBarIcon(command:string, text:string, tooltip:string,
                                    priority:number = 1, color:string = "white"):vscode.StatusBarItem{
	let NewIcon = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, priority);
    NewIcon.command=command;  NewIcon.text=text;
    NewIcon.tooltip=tooltip;  NewIcon.color=color;
	return NewIcon;
}

/**
 * Gets the path of the selected file from the Uri object
 * In case of windows, it removes the first '/' from the path
 * @param arg Received Uri object
 * @returns Path of the selected file as string
 */
function getFileUri(arg:vscode.Uri|undefined):string|undefined{
	if(arg !== undefined){
        return process.platform === "win32" ? arg.path.substr(1) : arg.path;
    }
	const editor = vscode.window.activeTextEditor;
	if(editor === undefined || editor.document.languageId !== "erlang"){
        return undefined;
    }
    return process.platform === "win32" ?
                editor.document.uri.path.substr(1) : editor.document.uri.path;
}

/**
 * Creates a position param for requests
 * @param OnlySingle Function ignores text selection if true
 * @returns Range param in case of text selection, single position param otherwise
 */
function getPosParam(OnlySingle:boolean=false):string{
	let Pos = getPosNumbers();
    if(Pos === undefined){ return ""; }
    if(OnlySingle){return ",{position, "+Pos.Start+"}"; }
    return Pos.Start === Pos.End ?
            ",{position, "+Pos.Start+"}" : ",{posrange, {"+Pos.Start+","+Pos.End+"}}";
}

/**
 * Gets the absolute starting & ending position of the selected text
 * @returns an object with the absolute start & end position
 */
function getPosNumbers():{Start:number, End:number}|undefined{
	const editor = vscode.window.activeTextEditor;
    if(editor === undefined){ return undefined; }
    return { Start: editor.document.offsetAt(editor.selection.start) - 
                    (editor.document.eol === 2 ? editor.selection.start.line - 1 : 0),
                End: editor.document.offsetAt(editor.selection.end) - 
                    (editor.document.eol === 2 ? editor.selection.end.line - 1 : 0)
                };
}