'use strict';
import { Disposable, Event, EventEmitter, TextDocument, TextDocumentChangeEvent, TextEditor, window, workspace } from 'vscode';
import { CommandContext, setCommandContext } from './commands';
import { TextDocumentComparer } from './comparers';
import { GitService } from './gitService';

export interface BlameabilityChangeEvent {
    blameable: boolean;
    editor: TextEditor;
}

export class BlameabilityTracker extends Disposable {

    private _onDidChange = new EventEmitter<BlameabilityChangeEvent>();
    get onDidChange(): Event<BlameabilityChangeEvent> {
        return this._onDidChange.event;
    }

    private _disposable: Disposable;
    private _documentChangeDisposable: Disposable;
    private _editor: TextEditor;
    private _isBlameable: boolean;

    constructor(private git: GitService) {
        super(() => this.dispose());

        const subscriptions: Disposable[] = [];

        subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
        subscriptions.push(workspace.onDidSaveTextDocument(this._onTextDocumentSaved, this));
        subscriptions.push(this.git.onDidBlameFail(this._onBlameFailed, this));

        this._disposable = Disposable.from(...subscriptions);

        this._onActiveTextEditorChanged(window.activeTextEditor);
    }

    dispose() {
        this._disposable && this._disposable.dispose();
        this._documentChangeDisposable && this._documentChangeDisposable.dispose();
    }

    private _onActiveTextEditorChanged(editor: TextEditor) {
        this._editor = editor;
        let blameable = editor && editor.document && !editor.document.isDirty;

        if (blameable) {
            blameable = this.git.getBlameability(editor.document.fileName);
        }

        this._subscribeToDocumentChanges();
        this.updateBlameability(blameable, true);
    }

    private _onBlameFailed(key: string) {
        const fileName = this._editor && this._editor.document && this._editor.document.fileName;
        if (!fileName || key !== this.git.getCacheEntryKey(fileName)) return;

        this.updateBlameability(false);
    }

    private _onTextDocumentChanged(e: TextDocumentChangeEvent) {
        if (!TextDocumentComparer.equals(this._editor && this._editor.document, e && e.document)) return;

        // Can't unsubscribe here because undo doesn't trigger any other event
        //this._unsubscribeToDocumentChanges();
        //this.updateBlameability(false);

        // We have to defer because isDirty is not reliable inside this event
        setTimeout(() => this.updateBlameability(!e.document.isDirty), 1);
    }

    private _onTextDocumentSaved(e: TextDocument) {
        if (!TextDocumentComparer.equals(this._editor && this._editor.document, e)) return;

        // Don't need to resubscribe as we aren't unsubscribing on document changes anymore
        //this._subscribeToDocumentChanges();
        this.updateBlameability(!e.isDirty);
    }

    private _subscribeToDocumentChanges() {
        this._unsubscribeToDocumentChanges();
        this._documentChangeDisposable = workspace.onDidChangeTextDocument(this._onTextDocumentChanged, this);
    }

    private _unsubscribeToDocumentChanges() {
        this._documentChangeDisposable && this._documentChangeDisposable.dispose();
        this._documentChangeDisposable = undefined;
    }

    private updateBlameability(blameable: boolean, force: boolean = false) {
        if (!force && this._isBlameable === blameable) return;

        setCommandContext(CommandContext.IsBlameable, blameable);
        this._onDidChange.fire({
            blameable: blameable,
            editor: this._editor
        });
    }
}