import * as assert from 'assert';
import * as fs from 'fs';
import * as vscode from 'vscode';

import {
    MappedRustDiagnostic,
    mapRustDiagnosticToVsCode,
    RustDiagnostic,
    SuggestionApplicability
} from '../../../utils/diagnostics/rust';

function loadDiagnosticFixture(name: string): RustDiagnostic {
    const jsonText = fs
        .readFileSync(
            // We're actually in our JavaScript output directory, climb out
            `${__dirname}/../../../../src/test/fixtures/rust-diagnostics/${name}.json`
        )
        .toString();

    return JSON.parse(jsonText);
}

function mapFixtureToVsCode(name: string): MappedRustDiagnostic {
    const rd = loadDiagnosticFixture(name);
    const mapResult = mapRustDiagnosticToVsCode(rd);

    if (!mapResult) {
        return assert.fail('Mapping unexpectedly failed');
    }
    return mapResult;
}

describe('mapRustDiagnosticToVsCode', () => {
    it('should map an incompatible type for trait error', () => {
        const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
            'error/E0053'
        );

        assert.strictEqual(
            diagnostic.severity,
            vscode.DiagnosticSeverity.Error
        );
        assert.strictEqual(diagnostic.source, 'rustc');
        assert.strictEqual(
            diagnostic.message,
            [
                `method \`next\` has an incompatible type for trait`,
                `expected type \`fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>\``,
                `   found type \`fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>\``
            ].join('\n')
        );
        assert.strictEqual(diagnostic.code, 'E0053');
        assert.strictEqual(diagnostic.tags, undefined);

        // No related information
        assert.deepStrictEqual(diagnostic.relatedInformation, []);

        // There are no suggested fixes
        assert.strictEqual(suggestedFixes.length, 0);
    });

    it('should map an unused variable warning', () => {
        const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
            'warning/unused_variables'
        );

        assert.strictEqual(
            diagnostic.severity,
            vscode.DiagnosticSeverity.Warning
        );
        assert.strictEqual(
            diagnostic.message,
            [
                'unused variable: `foo`',
                '#[warn(unused_variables)] on by default'
            ].join('\n')
        );
        assert.strictEqual(diagnostic.code, 'unused_variables');
        assert.strictEqual(diagnostic.source, 'rustc');
        assert.deepStrictEqual(diagnostic.tags, [
            vscode.DiagnosticTag.Unnecessary
        ]);

        // No related information
        assert.deepStrictEqual(diagnostic.relatedInformation, []);

        // One suggested fix available to prefix the variable
        assert.strictEqual(suggestedFixes.length, 1);
        const [suggestedFix] = suggestedFixes;
        assert.strictEqual(
            suggestedFix.title,
            'consider prefixing with an underscore: `_foo`'
        );
        assert.strictEqual(
            suggestedFix.applicability,
            SuggestionApplicability.MachineApplicable
        );
    });

    it('should map a wrong number of parameters error', () => {
        const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
            'error/E0061'
        );

        assert.strictEqual(
            diagnostic.severity,
            vscode.DiagnosticSeverity.Error
        );
        assert.strictEqual(
            diagnostic.message,
            [
                'this function takes 2 parameters but 3 parameters were supplied',
                'expected 2 parameters'
            ].join('\n')
        );
        assert.strictEqual(diagnostic.code, 'E0061');
        assert.strictEqual(diagnostic.source, 'rustc');
        assert.strictEqual(diagnostic.tags, undefined);

        // One related information for the original definition
        const relatedInformation = diagnostic.relatedInformation;
        if (!relatedInformation) {
            return assert.fail('Related information unexpectedly undefined');
        }
        assert.strictEqual(relatedInformation.length, 1);
        const [related] = relatedInformation;
        assert.strictEqual(related.message, 'defined here');

        // There are no suggested fixes
        assert.strictEqual(suggestedFixes.length, 0);
    });

    it('should map a Clippy copy pass by ref warning', () => {
        const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
            'clippy/trivially_copy_pass_by_ref'
        );

        assert.strictEqual(
            diagnostic.severity,
            vscode.DiagnosticSeverity.Warning
        );
        assert.strictEqual(diagnostic.source, 'clippy');
        assert.strictEqual(
            diagnostic.message,
            [
                'this argument is passed by reference, but would be more efficient if passed by value',
                '#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]',
                'for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref'
            ].join('\n')
        );
        assert.strictEqual(diagnostic.code, 'trivially_copy_pass_by_ref');
        assert.strictEqual(diagnostic.tags, undefined);

        // One related information for the lint definition
        const relatedInformation = diagnostic.relatedInformation;
        if (!relatedInformation) {
            return assert.fail('Related information unexpectedly undefined');
        }
        assert.strictEqual(relatedInformation.length, 1);
        const [related] = relatedInformation;
        assert.strictEqual(related.message, 'lint level defined here');

        // One suggested fix to pass by value
        assert.strictEqual(suggestedFixes.length, 1);
        const [suggestedFix] = suggestedFixes;
        assert.strictEqual(
            suggestedFix.title,
            'consider passing by value instead: `self`'
        );
        // Clippy does not mark this with any applicability
        assert.strictEqual(
            suggestedFix.applicability,
            SuggestionApplicability.Unspecified
        );
    });

    it('should map a mismatched type error', () => {
        const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
            'error/E0308'
        );

        assert.strictEqual(
            diagnostic.severity,
            vscode.DiagnosticSeverity.Error
        );
        assert.strictEqual(
            diagnostic.message,
            ['mismatched types', 'expected usize, found u32'].join('\n')
        );
        assert.strictEqual(diagnostic.code, 'E0308');
        assert.strictEqual(diagnostic.source, 'rustc');
        assert.strictEqual(diagnostic.tags, undefined);

        // No related information
        assert.deepStrictEqual(diagnostic.relatedInformation, []);

        // There are no suggested fixes
        assert.strictEqual(suggestedFixes.length, 0);
    });
});