import {Kernel, KernelMessage} from '@jupyterlab/services';
import {Diagnostic} from '@codemirror/lint';

// interfaces for the annotation object handed over to the CodeMirror instances
interface CellAnnotation {
    message: string,
    severity: 'warning' | 'error',
    from: {line: number, col: number},
    to: {line: number, col: number}
}

// interface for the collection of CellAnnotations for the whole notebook
interface Annotations {
    [cellIndex: number]: Array<CellAnnotation>
}

// interfaces for the objects received by the kernel
interface KernelCellAnnotation {
    message: string,
    severity: 'warning' | 'error',
    start: {line: number, col: number}
    stop: {line: number, col: number}
}

interface KernelAnnotations {
    [cellIndex: number]: Array<KernelCellAnnotation>
}

export class LintHelper {
    private annotations: Annotations;

    constructor(private readonly kernel: Kernel.IKernelConnection, private readonly path: string) {
    };

    /**
     * asynchronous function which waits for receiving the Annotations of this object's notebook of the specified path from the kernel
     */
    public async setup() {
        this.annotations = await LintHelper.getAnnotations(this.kernel, this.path);
    }

    /**
     * requests annotations for the notebook specified by the path from the kernel for lint-analysis
     * saves the result in the annotations object variable
     * @param kernel a Kernel.IKernelConnection object to connect with
     * @param path the path of the notebook requesting linting annotations
     */
    public static async getAnnotations(kernel: Kernel.IKernelConnection, path: string): Promise<Annotations> {
        if (!kernel) {
            throw new Error('LintHelper has no kernel');
        }
        let content: KernelMessage.IExecuteRequestMsg['content'] = {
            code: "",
            user_expressions: {lintMode: true, path: path},
            silent: true
        };

        let future = kernel.requestExecute(content);
        return future.done
            .then((reply: KernelMessage.IShellMessage) => {
                if ((reply.content as any).status == 'ok') {
                    // construct the CodeMirror 6 Diagnostic objects from the annotations received by the kernel
                    let res: Annotations = {};
                    let data: KernelAnnotations = (reply.content as any).data;
                    for (let cellIndex in data) {
                        // initialize the array of the resulting annotations and fill it
                        let cellAnnotations = new Array<CellAnnotation>(data[cellIndex].length);
                        data[cellIndex].forEach((annotation, index) =>
                            cellAnnotations[index] = {
                                message: annotation.message,
                                severity: annotation.severity,
                                from: {line: annotation.start.line, col: annotation.start.col},
                                to: {line: annotation.stop.line, col: annotation.stop.col}
                            });
                        res[cellIndex] = cellAnnotations;
                    }
                    return res;
                } else {
                    return undefined;
                }
            })
            .catch((reason: any) => {
                console.log('getAnnotations() got rejected by the kernel: ', reason);
                return undefined;
            });
    }

    /**
     * Get diagnostics for a specific cell index
     * @param cellIndex the index of the cell
     */
    public getDiagnostics(cellIndex: number): Diagnostic[] {
        if (this.annotations === undefined || this.annotations[cellIndex] === undefined) {
            return [];
        }
        
        // Convert annotations to CodeMirror 6 Diagnostics
        return this.annotations[cellIndex].map(ann => ({
            from: 0, // Would need document to calculate actual position
            to: 0,   // Would need document to calculate actual position
            severity: ann.severity,
            message: ann.message
        }));
    }

    /**
     * Linter function used by the CodeMirror instances. Each CodeMirror editor instance has to give its current
     * cell index to the function to get its corresponding list of diagnostic objects.
     * @param cellIndex the cell index
     */
    public performLint = (cellIndex: number): Diagnostic[] => {
        return this.getDiagnostics(cellIndex);
    }
}
