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

import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Range } from 'vs/editor/common/core/range';
import { MATCHES_LIMIT } from './findModel';

export interface FindReplaceStateChangedEvent {
	moveCursor: boolean;
	updateHistory: boolean;

	searchString: boolean;
	replaceString: boolean;
	isRevealed: boolean;
	isReplaceRevealed: boolean;
	isRegex: boolean;
	wholeWord: boolean;
	matchCase: boolean;
	preserveCase: boolean;
	searchScope: boolean;
	matchesPosition: boolean;
	matchesCount: boolean;
	currentMatch: boolean;
	loop: boolean;
	isSearching: boolean;
	filters: boolean;
}

export const enum FindOptionOverride {
	NotSet = 0,
	True = 1,
	False = 2
}

export interface INewFindReplaceState<T extends { update: (value: T) => void } = { update: () => {} }> {
	searchString?: string;
	replaceString?: string;
	isRevealed?: boolean;
	isReplaceRevealed?: boolean;
	isRegex?: boolean;
	isRegexOverride?: FindOptionOverride;
	wholeWord?: boolean;
	wholeWordOverride?: FindOptionOverride;
	matchCase?: boolean;
	matchCaseOverride?: FindOptionOverride;
	preserveCase?: boolean;
	preserveCaseOverride?: FindOptionOverride;
	searchScope?: Range[] | null;
	loop?: boolean;
	isSearching?: boolean;
	filters?: T;
}

function effectiveOptionValue(override: FindOptionOverride, value: boolean): boolean {
	if (override === FindOptionOverride.True) {
		return true;
	}
	if (override === FindOptionOverride.False) {
		return false;
	}
	return value;
}

export class FindReplaceState<T extends { update: (value: T) => void } = { update: () => {} }> extends Disposable {
	private _searchString: string;
	private _replaceString: string;
	private _isRevealed: boolean;
	private _isReplaceRevealed: boolean;
	private _isRegex: boolean;
	private _isRegexOverride: FindOptionOverride;
	private _wholeWord: boolean;
	private _wholeWordOverride: FindOptionOverride;
	private _matchCase: boolean;
	private _matchCaseOverride: FindOptionOverride;
	private _preserveCase: boolean;
	private _preserveCaseOverride: FindOptionOverride;
	private _searchScope: Range[] | null;
	private _matchesPosition: number;
	private _matchesCount: number;
	private _currentMatch: Range | null;
	private _loop: boolean;
	private _isSearching: boolean;
	private _filters: T | null;
	private readonly _onFindReplaceStateChange = this._register(new Emitter<FindReplaceStateChangedEvent>());

	public get searchString(): string { return this._searchString; }
	public get replaceString(): string { return this._replaceString; }
	public get isRevealed(): boolean { return this._isRevealed; }
	public get isReplaceRevealed(): boolean { return this._isReplaceRevealed; }
	public get isRegex(): boolean { return effectiveOptionValue(this._isRegexOverride, this._isRegex); }
	public get wholeWord(): boolean { return effectiveOptionValue(this._wholeWordOverride, this._wholeWord); }
	public get matchCase(): boolean { return effectiveOptionValue(this._matchCaseOverride, this._matchCase); }
	public get preserveCase(): boolean { return effectiveOptionValue(this._preserveCaseOverride, this._preserveCase); }

	public get actualIsRegex(): boolean { return this._isRegex; }
	public get actualWholeWord(): boolean { return this._wholeWord; }
	public get actualMatchCase(): boolean { return this._matchCase; }
	public get actualPreserveCase(): boolean { return this._preserveCase; }

	public get searchScope(): Range[] | null { return this._searchScope; }
	public get matchesPosition(): number { return this._matchesPosition; }
	public get matchesCount(): number { return this._matchesCount; }
	public get currentMatch(): Range | null { return this._currentMatch; }
	public get isSearching(): boolean { return this._isSearching; }
	public get filters(): T | null { return this._filters; }
	public readonly onFindReplaceStateChange: Event<FindReplaceStateChangedEvent> = this._onFindReplaceStateChange.event;

	constructor() {
		super();
		this._searchString = '';
		this._replaceString = '';
		this._isRevealed = false;
		this._isReplaceRevealed = false;
		this._isRegex = false;
		this._isRegexOverride = FindOptionOverride.NotSet;
		this._wholeWord = false;
		this._wholeWordOverride = FindOptionOverride.NotSet;
		this._matchCase = false;
		this._matchCaseOverride = FindOptionOverride.NotSet;
		this._preserveCase = false;
		this._preserveCaseOverride = FindOptionOverride.NotSet;
		this._searchScope = null;
		this._matchesPosition = 0;
		this._matchesCount = 0;
		this._currentMatch = null;
		this._loop = true;
		this._isSearching = false;
		this._filters = null;
	}

	public changeMatchInfo(matchesPosition: number, matchesCount: number, currentMatch: Range | undefined): void {
		const changeEvent: FindReplaceStateChangedEvent = {
			moveCursor: false,
			updateHistory: false,
			searchString: false,
			replaceString: false,
			isRevealed: false,
			isReplaceRevealed: false,
			isRegex: false,
			wholeWord: false,
			matchCase: false,
			preserveCase: false,
			searchScope: false,
			matchesPosition: false,
			matchesCount: false,
			currentMatch: false,
			loop: false,
			isSearching: false,
			filters: false
		};
		let somethingChanged = false;

		if (matchesCount === 0) {
			matchesPosition = 0;
		}
		if (matchesPosition > matchesCount) {
			matchesPosition = matchesCount;
		}

		if (this._matchesPosition !== matchesPosition) {
			this._matchesPosition = matchesPosition;
			changeEvent.matchesPosition = true;
			somethingChanged = true;
		}
		if (this._matchesCount !== matchesCount) {
			this._matchesCount = matchesCount;
			changeEvent.matchesCount = true;
			somethingChanged = true;
		}

		if (typeof currentMatch !== 'undefined') {
			if (!Range.equalsRange(this._currentMatch, currentMatch)) {
				this._currentMatch = currentMatch;
				changeEvent.currentMatch = true;
				somethingChanged = true;
			}
		}

		if (somethingChanged) {
			this._onFindReplaceStateChange.fire(changeEvent);
		}
	}

	public change(newState: INewFindReplaceState<T>, moveCursor: boolean, updateHistory: boolean = true): void {
		const changeEvent: FindReplaceStateChangedEvent = {
			moveCursor: moveCursor,
			updateHistory: updateHistory,
			searchString: false,
			replaceString: false,
			isRevealed: false,
			isReplaceRevealed: false,
			isRegex: false,
			wholeWord: false,
			matchCase: false,
			preserveCase: false,
			searchScope: false,
			matchesPosition: false,
			matchesCount: false,
			currentMatch: false,
			loop: false,
			isSearching: false,
			filters: false
		};
		let somethingChanged = false;

		const oldEffectiveIsRegex = this.isRegex;
		const oldEffectiveWholeWords = this.wholeWord;
		const oldEffectiveMatchCase = this.matchCase;
		const oldEffectivePreserveCase = this.preserveCase;

		if (typeof newState.searchString !== 'undefined') {
			if (this._searchString !== newState.searchString) {
				this._searchString = newState.searchString;
				changeEvent.searchString = true;
				somethingChanged = true;
			}
		}
		if (typeof newState.replaceString !== 'undefined') {
			if (this._replaceString !== newState.replaceString) {
				this._replaceString = newState.replaceString;
				changeEvent.replaceString = true;
				somethingChanged = true;
			}
		}
		if (typeof newState.isRevealed !== 'undefined') {
			if (this._isRevealed !== newState.isRevealed) {
				this._isRevealed = newState.isRevealed;
				changeEvent.isRevealed = true;
				somethingChanged = true;
			}
		}
		if (typeof newState.isReplaceRevealed !== 'undefined') {
			if (this._isReplaceRevealed !== newState.isReplaceRevealed) {
				this._isReplaceRevealed = newState.isReplaceRevealed;
				changeEvent.isReplaceRevealed = true;
				somethingChanged = true;
			}
		}
		if (typeof newState.isRegex !== 'undefined') {
			this._isRegex = newState.isRegex;
		}
		if (typeof newState.wholeWord !== 'undefined') {
			this._wholeWord = newState.wholeWord;
		}
		if (typeof newState.matchCase !== 'undefined') {
			this._matchCase = newState.matchCase;
		}
		if (typeof newState.preserveCase !== 'undefined') {
			this._preserveCase = newState.preserveCase;
		}
		if (typeof newState.searchScope !== 'undefined') {
			if (!newState.searchScope?.every((newSearchScope) => {
				return this._searchScope?.some(existingSearchScope => {
					return !Range.equalsRange(existingSearchScope, newSearchScope);
				});
			})) {
				this._searchScope = newState.searchScope;
				changeEvent.searchScope = true;
				somethingChanged = true;
			}
		}
		if (typeof newState.loop !== 'undefined') {
			if (this._loop !== newState.loop) {
				this._loop = newState.loop;
				changeEvent.loop = true;
				somethingChanged = true;
			}
		}

		if (typeof newState.isSearching !== 'undefined') {
			if (this._isSearching !== newState.isSearching) {
				this._isSearching = newState.isSearching;
				changeEvent.isSearching = true;
				somethingChanged = true;
			}
		}

		if (typeof newState.filters !== 'undefined') {
			if (this._filters) {
				this._filters.update(newState.filters);
			} else {
				this._filters = newState.filters;
			}

			changeEvent.filters = true;
			somethingChanged = true;
		}

		// Overrides get set when they explicitly come in and get reset anytime something else changes
		this._isRegexOverride = (typeof newState.isRegexOverride !== 'undefined' ? newState.isRegexOverride : FindOptionOverride.NotSet);
		this._wholeWordOverride = (typeof newState.wholeWordOverride !== 'undefined' ? newState.wholeWordOverride : FindOptionOverride.NotSet);
		this._matchCaseOverride = (typeof newState.matchCaseOverride !== 'undefined' ? newState.matchCaseOverride : FindOptionOverride.NotSet);
		this._preserveCaseOverride = (typeof newState.preserveCaseOverride !== 'undefined' ? newState.preserveCaseOverride : FindOptionOverride.NotSet);

		if (oldEffectiveIsRegex !== this.isRegex) {
			somethingChanged = true;
			changeEvent.isRegex = true;
		}
		if (oldEffectiveWholeWords !== this.wholeWord) {
			somethingChanged = true;
			changeEvent.wholeWord = true;
		}
		if (oldEffectiveMatchCase !== this.matchCase) {
			somethingChanged = true;
			changeEvent.matchCase = true;
		}

		if (oldEffectivePreserveCase !== this.preserveCase) {
			somethingChanged = true;
			changeEvent.preserveCase = true;
		}

		if (somethingChanged) {
			this._onFindReplaceStateChange.fire(changeEvent);
		}
	}

	public canNavigateBack(): boolean {
		return this.canNavigateInLoop() || (this.matchesPosition !== 1);
	}

	public canNavigateForward(): boolean {
		return this.canNavigateInLoop() || (this.matchesPosition < this.matchesCount);
	}

	private canNavigateInLoop(): boolean {
		return this._loop || (this.matchesCount >= MATCHES_LIMIT);
	}

}
