/* eslint-disable eqeqeq */
/*---------------------------------------------------------
 * Copyright (C) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See LICENSE in the project root for license information.
 *--------------------------------------------------------*/

import jsDiff = require('diff');
import { Position, Range, TextEditorEdit, Uri, WorkspaceEdit } from 'vscode';
import { getBinPathFromEnvVar, getEnvPath } from './utils/pathUtils';

let diffToolAvailable: boolean | null = null;

export function isDiffToolAvailable(): boolean {
	if (diffToolAvailable == null) {
		const envPath = getEnvPath();
		if (!envPath) {
			return false;
		}
		diffToolAvailable = getBinPathFromEnvVar('diff', envPath, false) != null;
	}
	return diffToolAvailable;
}

export enum EditTypes {
	EDIT_DELETE,
	EDIT_INSERT,
	EDIT_REPLACE
}

export class Edit {
	public start: Position;
	public end: Position;
	public text: string;
	private action: number;

	constructor(action: number, start: Position) {
		this.action = action;
		this.start = start;
		this.end = start;
		this.text = '';
	}

	// Applies Edit using given TextEditorEdit
	public applyUsingTextEditorEdit(editBuilder: TextEditorEdit): void {
		switch (this.action) {
			case EditTypes.EDIT_INSERT:
				editBuilder.insert(this.start, this.text);
				break;

			case EditTypes.EDIT_DELETE:
				editBuilder.delete(new Range(this.start, this.end));
				break;

			case EditTypes.EDIT_REPLACE:
				editBuilder.replace(new Range(this.start, this.end), this.text);
				break;
		}
	}

	// Applies Edits to given WorkspaceEdit
	public applyUsingWorkspaceEdit(workspaceEdit: WorkspaceEdit, fileUri: Uri): void {
		switch (this.action) {
			case EditTypes.EDIT_INSERT:
				workspaceEdit.insert(fileUri, this.start, this.text);
				break;

			case EditTypes.EDIT_DELETE:
				workspaceEdit.delete(fileUri, new Range(this.start, this.end));
				break;

			case EditTypes.EDIT_REPLACE:
				workspaceEdit.replace(fileUri, new Range(this.start, this.end), this.text);
				break;
		}
	}
}

export interface FilePatch {
	fileName: string;
	edits: Edit[];
}

/**
 * Uses diff module to parse given array of IUniDiff objects and returns edits for files
 *
 * @param diffOutput jsDiff.IUniDiff[]
 *
 * @returns Array of FilePatch objects, one for each file
 */
function parseUniDiffs(diffOutput: jsDiff.IUniDiff[]): FilePatch[] {
	const filePatches: FilePatch[] = [];
	diffOutput.forEach((uniDiff: jsDiff.IUniDiff) => {
		let edit: Edit;
		const edits: Edit[] = [];
		uniDiff.hunks.forEach((hunk: jsDiff.IHunk) => {
			let startLine = hunk.oldStart;
			hunk.lines.forEach((line) => {
				switch (line.substr(0, 1)) {
					case '-':
						edit = new Edit(EditTypes.EDIT_DELETE, new Position(startLine - 1, 0));
						edit.end = new Position(startLine, 0);
						edits.push(edit);
						startLine++;
						break;
					case '+':
						edit = new Edit(EditTypes.EDIT_INSERT, new Position(startLine - 1, 0));
						edit.text += line.substr(1) + '\n';
						edits.push(edit);
						break;
					case ' ':
						startLine++;
						break;
				}
			});
		});

		const fileName = uniDiff.oldFileName;
		filePatches.push({ fileName, edits });
	});

	return filePatches;
}

/**
 * Returns a FilePatch object by generating diffs between given oldStr and newStr using the diff module
 *
 * @param fileName string: Name of the file to which edits should be applied
 * @param oldStr string
 * @param newStr string
 *
 * @returns A single FilePatch object
 */
export function getEdits(fileName: string, oldStr: string, newStr: string): FilePatch {
	if (process.platform === 'win32') {
		oldStr = oldStr.split('\r\n').join('\n');
		newStr = newStr.split('\r\n').join('\n');
	}
	const unifiedDiffs: jsDiff.IUniDiff = jsDiff.structuredPatch(fileName, fileName, oldStr, newStr, '', '');
	const filePatches: FilePatch[] = parseUniDiffs([unifiedDiffs]);
	return filePatches[0];
}

/**
 * Uses diff module to parse given diff string and returns edits for files
 *
 * @param diffStr : Diff string in unified format.
 * http://www.gnu.org/software/diffutils/manual/diffutils.html#Unified-Format
 *
 * @returns Array of FilePatch objects, one for each file
 */
export function getEditsFromUnifiedDiffStr(diffstr: string): FilePatch[] {
	const unifiedDiffs: jsDiff.IUniDiff[] = jsDiff.parsePatch(diffstr);
	const filePatches: FilePatch[] = parseUniDiffs(unifiedDiffs);
	return filePatches;
}
