// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import * as vscMockPosition from './position';
import * as vscMockRange from './range';

/**
 * A selection in the editor.
 * The selection is a range that has an orientation.
 */
export interface ISelection {
    /**
     * The line number on which the selection has started.
     */
    readonly selectionStartLineNumber: number;
    /**
     * The column on `selectionStartLineNumber` where the selection has started.
     */
    readonly selectionStartColumn: number;
    /**
     * The line number on which the selection has ended.
     */
    readonly positionLineNumber: number;
    /**
     * The column on `positionLineNumber` where the selection has ended.
     */
    readonly positionColumn: number;
}

/**
 * The direction of a selection.
 */
export enum SelectionDirection {
    /**
     * The selection starts above where it ends.
     */
    LTR,
    /**
     * The selection starts below where it ends.
     */
    RTL,
}

/**
 * A selection in the editor.
 * The selection is a range that has an orientation.
 */
export class Selection extends vscMockRange.Range {
    /**
     * The line number on which the selection has started.
     */
    public readonly selectionStartLineNumber: number;

    /**
     * The column on `selectionStartLineNumber` where the selection has started.
     */
    public readonly selectionStartColumn: number;

    /**
     * The line number on which the selection has ended.
     */
    public readonly positionLineNumber: number;

    /**
     * The column on `positionLineNumber` where the selection has ended.
     */
    public readonly positionColumn: number;

    constructor(
        selectionStartLineNumber: number,
        selectionStartColumn: number,
        positionLineNumber: number,
        positionColumn: number,
    ) {
        super(selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn);
        this.selectionStartLineNumber = selectionStartLineNumber;
        this.selectionStartColumn = selectionStartColumn;
        this.positionLineNumber = positionLineNumber;
        this.positionColumn = positionColumn;
    }

    /**
     * Clone this selection.
     */
    public clone(): Selection {
        return new Selection(
            this.selectionStartLineNumber,
            this.selectionStartColumn,
            this.positionLineNumber,
            this.positionColumn,
        );
    }

    /**
     * Transform to a human-readable representation.
     */
    public toString(): string {
        return `[${this.selectionStartLineNumber},${this.selectionStartColumn} -> ${this.positionLineNumber},${this.positionColumn}]`;
    }

    /**
     * Test if equals other selection.
     */
    public equalsSelection(other: ISelection): boolean {
        return Selection.selectionsEqual(this, other);
    }

    /**
     * Test if the two selections are equal.
     */
    public static selectionsEqual(a: ISelection, b: ISelection): boolean {
        return (
            a.selectionStartLineNumber === b.selectionStartLineNumber &&
            a.selectionStartColumn === b.selectionStartColumn &&
            a.positionLineNumber === b.positionLineNumber &&
            a.positionColumn === b.positionColumn
        );
    }

    /**
     * Get directions (LTR or RTL).
     */
    public getDirection(): SelectionDirection {
        if (this.selectionStartLineNumber === this.startLineNumber && this.selectionStartColumn === this.startColumn) {
            return SelectionDirection.LTR;
        }
        return SelectionDirection.RTL;
    }

    /**
     * Create a new selection with a different `positionLineNumber` and `positionColumn`.
     */
    public setEndPosition(endLineNumber: number, endColumn: number): Selection {
        if (this.getDirection() === SelectionDirection.LTR) {
            return new Selection(this.startLineNumber, this.startColumn, endLineNumber, endColumn);
        }
        return new Selection(endLineNumber, endColumn, this.startLineNumber, this.startColumn);
    }

    /**
     * Get the position at `positionLineNumber` and `positionColumn`.
     */
    public getPosition(): vscMockPosition.Position {
        return new vscMockPosition.Position(this.positionLineNumber, this.positionColumn);
    }

    /**
     * Create a new selection with a different `selectionStartLineNumber` and `selectionStartColumn`.
     */
    public setStartPosition(startLineNumber: number, startColumn: number): Selection {
        if (this.getDirection() === SelectionDirection.LTR) {
            return new Selection(startLineNumber, startColumn, this.endLineNumber, this.endColumn);
        }
        return new Selection(this.endLineNumber, this.endColumn, startLineNumber, startColumn);
    }

    // ----

    /**
     * Create a `Selection` from one or two positions
     */
    public static fromPositions(start: vscMockPosition.IPosition, end: vscMockPosition.IPosition = start): Selection {
        return new Selection(start.lineNumber, start.column, end.lineNumber, end.column);
    }

    /**
     * Create a `Selection` from an `ISelection`.
     */
    public static liftSelection(sel: ISelection): Selection {
        return new Selection(
            sel.selectionStartLineNumber,
            sel.selectionStartColumn,
            sel.positionLineNumber,
            sel.positionColumn,
        );
    }

    /**
     * `a` equals `b`.
     */
    public static selectionsArrEqual(a: ISelection[], b: ISelection[]): boolean {
        if ((a && !b) || (!a && b)) {
            return false;
        }
        if (!a && !b) {
            return true;
        }
        if (a.length !== b.length) {
            return false;
        }
        for (let i = 0, len = a.length; i < len; i += 1) {
            if (!this.selectionsEqual(a[i], b[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * Test if `obj` is an `ISelection`.
     */
    public static isISelection(obj?: {
        selectionStartLineNumber: unknown;
        selectionStartColumn: unknown;
        positionLineNumber: unknown;
        positionColumn: unknown;
    }): obj is ISelection {
        return (
            obj !== undefined &&
            typeof obj.selectionStartLineNumber === 'number' &&
            typeof obj.selectionStartColumn === 'number' &&
            typeof obj.positionLineNumber === 'number' &&
            typeof obj.positionColumn === 'number'
        );
    }

    /**
     * Create with a direction.
     */
    public static createWithDirection(
        startLineNumber: number,
        startColumn: number,
        endLineNumber: number,
        endColumn: number,
        direction: SelectionDirection,
    ): Selection {
        if (direction === SelectionDirection.LTR) {
            return new Selection(startLineNumber, startColumn, endLineNumber, endColumn);
        }

        return new Selection(endLineNumber, endColumn, startLineNumber, startColumn);
    }
}
