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

import 'vs/css!./media/panel';

import { Event, Emitter } from 'vs/base/common/event';
import * as DOM from 'vs/base/browser/dom';
import { IAction } from 'vs/base/common/actions';
import { IActionOptions, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Color } from 'vs/base/common/color';
import { isUndefinedOrNull } from 'vs/base/common/types';

export interface ITabbedPanelStyles {
	titleSelectedForeground?: Color;
	titleSelectedBorder?: Color;
	titleUnSelectedForeground?: Color;
	focusBorder?: Color;
	outline?: Color;
	selectedBackgroundForVerticalLayout?: Color;
	border?: Color;
	selectedTabContrastBorder?: Color;
}

export interface IPanelOptions {
	showHeaderWhenSingleView?: boolean;
}

export interface IPanelView {
	render(container: HTMLElement): void;
	layout(dimension: DOM.Dimension): void;
	remove?(): void;
	onShow?(): void;
	onHide?(): void;
}

export interface IPanelTab {
	title: string;
	identifier: string;
	view: IPanelView;
	tabSelectedHandler?(): void;
}

interface IInternalPanelTab {
	tab: IPanelTab;
	header: HTMLElement;
	disposables: DisposableStore;
	label: HTMLElement;
	body?: HTMLElement;
	destroyTabBody?: boolean;
}

const defaultOptions: IPanelOptions = {
	showHeaderWhenSingleView: true
};

export type PanelTabIdentifier = string;

export class TabbedPanel extends Disposable {
	private _tabMap = new Map<PanelTabIdentifier, IInternalPanelTab>();
	private _shownTabId?: PanelTabIdentifier;
	public readonly headersize = 35;
	private header: HTMLElement;
	private tabList: HTMLElement;
	private body: HTMLElement;
	private parent: HTMLElement;
	private _actionbar: ActionBar;
	private _currentDimensions?: DOM.Dimension;
	private _collapsed = false;
	private _headerVisible: boolean;
	private _styleElement: HTMLStyleElement;

	private _onTabChange = this._register(new Emitter<PanelTabIdentifier>());
	public onTabChange: Event<PanelTabIdentifier> = this._onTabChange.event;

	private tabHistory: string[] = [];
	private _tabOrder: PanelTabIdentifier[] = [];

	constructor(container: HTMLElement, private options: IPanelOptions = defaultOptions) {
		super();
		this.parent = DOM.$('.tabbedPanel');
		this._styleElement = DOM.createStyleSheet(this.parent);
		container.appendChild(this.parent);
		this.header = DOM.$('.composite.title');
		this.tabList = DOM.$('.tabList');
		this.tabList.setAttribute('role', 'tablist');
		this.tabList.style.height = this.headersize + 'px';
		this.header.appendChild(this.tabList);
		let actionbarcontainer = DOM.$('.title-actions');
		this._actionbar = new ActionBar(actionbarcontainer);
		this.header.appendChild(actionbarcontainer);
		if (options.showHeaderWhenSingleView) {
			this._headerVisible = true;
			this.parent.appendChild(this.header);
		} else {
			this._headerVisible = false;
		}
		this.body = DOM.$('.tabBody');
		this.body.setAttribute('role', 'tabpanel');
		this.parent.appendChild(this.body);
	}

	public get element(): HTMLElement {
		return this.parent;
	}

	public get selectedTabId(): string | undefined {
		return this._shownTabId;
	}

	public override dispose() {
		this.header.remove();
		this.tabList.remove();
		this.body.remove();
		this.parent.remove();
		this._styleElement.remove();
	}

	public contains(tabId: string): boolean {
		return this._tabMap.has(tabId);
	}

	public pushTab(tab: IPanelTab, index?: number, destroyTabBody?: boolean): PanelTabIdentifier {
		let internalTab = { tab } as IInternalPanelTab;
		internalTab.disposables = new DisposableStore();
		internalTab.destroyTabBody = destroyTabBody;
		this._tabMap.set(tab.identifier, internalTab);
		this._createTab(internalTab, index);
		if (!this._shownTabId) {
			this.showTab(tab.identifier);
		}
		if (this._tabMap.size > 1 && !this._headerVisible) {
			this.parent.insertBefore(this.header, this.parent.firstChild);
			this._headerVisible = true;
			if (this._currentDimensions) {
				this.layout(this._currentDimensions);
			}
		}
		return tab.identifier as PanelTabIdentifier;
	}

	public pushAction(arg: IAction | IAction[], options: IActionOptions = {}): void {
		this._actionbar.push(arg, options);
	}

	public set actionBarContext(context: any) {
		this._actionbar.context = context;
	}

	private _createTab(tab: IInternalPanelTab, index?: number): void {
		let tabHeaderElement = DOM.$('.tab-header');
		tabHeaderElement.setAttribute('tabindex', '-1');
		tabHeaderElement.setAttribute('role', 'tab');
		tabHeaderElement.setAttribute('aria-selected', 'false');
		tabHeaderElement.setAttribute('aria-controls', tab.tab.identifier);
		let tabElement = DOM.$('.tab');
		tabHeaderElement.appendChild(tabElement);
		let tabLabel = DOM.$('a.tabLabel');
		tabLabel.innerText = tab.tab.title;
		tabElement.appendChild(tabLabel);
		const invokeTabSelectedHandler = () => {
			if (tab.tab.tabSelectedHandler) {
				tab.tab.tabSelectedHandler();
			}
		};
		tab.disposables.add(DOM.addDisposableListener(tabHeaderElement, DOM.EventType.CLICK, e => {
			this.showTab(tab.tab.identifier);
			invokeTabSelectedHandler();
		}));

		tab.disposables.add(DOM.addDisposableListener(tabHeaderElement, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
			let event = new StandardKeyboardEvent(e);
			if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
				this.showTab(tab.tab.identifier);
				invokeTabSelectedHandler();
				e.stopImmediatePropagation();
			}
			if (event.equals(KeyCode.RightArrow)) {
				let currentIndex = this._tabOrder.findIndex(x => x === tab.tab.identifier);
				this.focusNextTab(currentIndex + 1);
			}
			if (event.equals(KeyCode.LeftArrow)) {
				let currentIndex = this._tabOrder.findIndex(x => x === tab.tab.identifier);
				this.focusNextTab(currentIndex - 1);
			}
		}));

		const insertBefore = !isUndefinedOrNull(index) ? this.tabList.children.item(index) : undefined;
		if (insertBefore) {
			this._tabOrder.splice(index!, 0, tab.tab.identifier);
			this.tabList.insertBefore(tabHeaderElement, insertBefore);
		} else {
			this.tabList.append(tabHeaderElement);
			this._tabOrder.push(tab.tab.identifier);
		}
		tab.header = tabHeaderElement;
		tab.label = tabLabel;
	}

	public showTab(id: PanelTabIdentifier): void {
		if (this._shownTabId === id || !this._tabMap.has(id)) {
			return;
		}

		if (this._shownTabId) {
			const shownTab = this._tabMap.get(this._shownTabId);
			if (shownTab) {
				shownTab.label.classList.remove('selected');
				shownTab.header.classList.remove('selected');
				shownTab.header.setAttribute('aria-selected', 'false');
				shownTab.header.tabIndex = -1;
				if (shownTab.body) {
					shownTab.body.remove();
					if (shownTab.tab.view.onHide) {
						shownTab.tab.view.onHide();
					}
				}
			}
		}

		this._shownTabId = id;
		this.tabHistory.push(id);
		const tab = this._tabMap.get(this._shownTabId)!; // @anthonydresser we know this can't be undefined since we check further up if the map contains the id
		tab.header.tabIndex = 0;
		if (tab.destroyTabBody && tab.body) {
			tab.body.remove();
			tab.body = undefined;
		}

		if (!tab.body) {
			tab.body = DOM.$('.tab-container');
			tab.body.style.width = '100%';
			tab.body.style.height = '100%';
			tab.tab.view.render(tab.body);
		}
		this.body.appendChild(tab.body);
		this.body.setAttribute('aria-labelledby', tab.tab.identifier);
		tab.label.classList.add('selected');
		tab.header.classList.add('selected');
		tab.header.setAttribute('aria-selected', 'true');
		this._onTabChange.fire(id);
		if (tab.tab.view.onShow) {
			tab.tab.view.onShow();
		}
		if (this._currentDimensions) {
			const tabHeight = this._currentDimensions.height - (this._headerVisible ? this.headersize : 0);
			this._layoutCurrentTab(new DOM.Dimension(this._currentDimensions.width, tabHeight));
		}
	}

	public clearTabs(): void {
		this._tabMap.forEach((value, key, map) => {
			this.removeTab(key);
		});
	}

	public removeTab(tab: PanelTabIdentifier) {
		const actualTab = this._tabMap.get(tab);
		if (!actualTab) {
			return;
		}
		if (actualTab.tab.view && actualTab.tab.view.remove) {
			actualTab.tab.view.remove();
		}
		if (actualTab.header && actualTab.header.remove) {
			actualTab.header.remove();
		}
		if (actualTab.body && actualTab.body.remove) {
			actualTab.body.remove();
		}
		actualTab.disposables.dispose();
		this._tabMap.delete(tab);
		let index = this._tabOrder.findIndex(t => t === tab);
		this._tabOrder.splice(index, 1);
		if (this._shownTabId === tab) {
			this._shownTabId = undefined;
			while (this._shownTabId === undefined && this.tabHistory.length > 0) {
				let lastTab = this.tabHistory.shift();
				if (lastTab) {
					if (this._tabMap.get(lastTab)) {
						this.showTab(lastTab);
					}
				}
			}
			if (!this._shownTabId && this._tabMap.size > 0) {
				this.showTab(this._tabMap.values().next().value.tab.identifier);
			}
		}

		if (!this.options.showHeaderWhenSingleView && this._tabMap.size === 1 && this._headerVisible) {
			this.header.remove();
			this._headerVisible = false;
			if (this._currentDimensions) {
				this.layout(this._currentDimensions);
			}
		}
	}

	private focusNextTab(index: number): void {
		if (index < 0 || index > this.tabList.children.length) {
			return;
		}
		let tab = (<HTMLElement>this.tabList.children[index]);
		if (tab) {
			tab.focus();
		}
	}

	public focusCurrentTab(): void {
		if (this._shownTabId) {
			const tab = this._tabMap.get(this._shownTabId);
			if (tab) {
				tab.header.focus();
			}
		}
	}

	public style(styles: ITabbedPanelStyles): void {
		const content: string[] = [];

		if (styles.border) {
			content.push(`
			.tabbedPanel {
				border-color: ${styles.border};
			}`);
		}

		if (styles.titleSelectedForeground && styles.titleSelectedBorder) {
			content.push(`
			.tabbedPanel > .title .tabList .tab:hover .tabLabel,
			.tabbedPanel > .title .tabList .tab .tabLabel.selected {
				color: ${styles.titleSelectedForeground};
				border-bottom-color: ${styles.titleSelectedBorder};
				border-bottom-width: 2px;
			}`);
		}

		if (styles.titleUnSelectedForeground) {
			content.push(`
			.tabbedPanel > .title .tabList .tab .tabLabel {
				color: ${styles.titleUnSelectedForeground};
			}`);
		}

		if (styles.focusBorder && styles.titleSelectedForeground) {
			content.push(`
			.tabbedPanel > .title .tabList .tab .tabLabel:focus {
				color: ${styles.titleSelectedForeground};
				border-bottom-color: ${styles.focusBorder} !important;
				border-bottom: 1px solid;
				outline: none;
			}`);
		}

		if (styles.outline) {
			content.push(`
			.tabbedPanel > .title .tabList .tab-header.selected,
			.tabbedPanel > .title .tabList .tab-header:hover {
				outline-color: ${styles.outline};
				outline-width: 1px;
				outline-style: solid;
				padding-bottom: 0;
				outline-offset: -5px;
			}

			.tabbedPanel > .title .tabList .tab-header:hover:not(.selected) {
				outline-style: dashed;
			}`);
		}

		const newStyles = content.join('\n');
		if (newStyles !== this._styleElement.innerHTML) {
			this._styleElement.innerHTML = newStyles;
		}
	}

	public layout(dimension: DOM.Dimension): void {
		if (dimension) {
			this._currentDimensions = dimension;
			this.parent.style.height = dimension.height + 'px';
			this.header.style.width = dimension.width + 'px';
			this.body.style.width = dimension.width + 'px';
			const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0);
			this.body.style.height = bodyHeight + 'px';
			this._layoutCurrentTab(new DOM.Dimension(dimension.width, bodyHeight));
		}
	}

	private _layoutCurrentTab(dimension: DOM.Dimension): void {
		if (this._shownTabId) {
			const tab = this._tabMap.get(this._shownTabId);
			if (tab && tab.body) {
				tab.body.style.width = dimension.width + 'px';
				tab.body.style.height = dimension.height + 'px';
				tab.tab.view.layout(dimension);
			}
		}
	}

	public set collapsed(val: boolean) {
		if (val === this._collapsed) {
			return;
		}

		this._collapsed = val === false ? false : true;
		if (this.collapsed) {
			this.body.remove();
		} else {
			this.parent.appendChild(this.body);
		}
	}

	public get collapsed(): boolean {
		return this._collapsed;
	}
}
