define([
    'jquery',
    'lodash',
    'bitbucket/internal/feature/file-content/diff-view-context/diff-view-context-internal',
    'bitbucket/internal/model/file-change'
], function (
    $,
    _,
    diffViewContext,
    FileChange
) {

    'use strict';

    module('diff-view-context');

    var CONTEXT = 20;

    function fetchContext(start, end, max, expected, lines /* Optional for the normal use-case */) {
        var count = 0;
        diffViewContext.fetchContext(start, end,function (s, limit) {
            count++;
            // size is optional for testing the end of file logic
            return $.Deferred().resolve({start: s, lines: new Array(lines || limit)});
        }, CONTEXT, {maxLimit: max}).then(expected);
        return count;
    }

    function checkResult(start, end) {
        var contexts = Array.prototype.slice.apply(arguments, [2]);
        return function (data) {
            strictEqual(data.startSep, start);
            strictEqual(data.endSep, end);
            strictEqual(data.contexts.length, contexts.length);
            _.zip(contexts, data.contexts).map(function (args) {
                return args[0](args[1]);
            });
        };
    }

    function checkContext(start, lines) {
        return function (data) {
            strictEqual(data.start, start);
            strictEqual(data.lines.length, lines);
        };
    }

    function checkNullContext() {
        return function (data) {
            strictEqual(data, null);
        };
    }

    test('Fetch hunk start within double-context', function () {
        fetchContext(0, 29, 100, checkResult(0, 0, checkContext(0, 30)));
    });

    test('Fetch hunk start with single line', function () {
        fetchContext(0, 0, 100, checkResult(0, 0, checkContext(0, 1)));
    });

    test('Fetch hunk start with more', function () {
        fetchContext(0, 49, 10, checkResult(0, 30, checkNullContext(), checkContext(30, 20)));
    });

    test('Fetch hunk single at end', function () {
        fetchContext(44, NaN, 100, checkResult(64, undefined, checkContext(44, 20), checkNullContext()));
    });

    test('Fetch hunk single at end with no more results', function () {
        fetchContext(44, NaN, 100, checkResult(64, undefined, checkContext(44, 20)), CONTEXT);
    });

    test('Fetch hunk single request, two hunks', function () {
        strictEqual(1, fetchContext(20, 70, 100, checkResult(40, 51, checkContext(20, 20), checkContext(51, 20))));
    });

    test('Fetch hunk two requests, two hunks', function () {
        strictEqual(2, fetchContext(20, 70, 10, checkResult(40, 51, checkContext(20, 20), checkContext(51, 20))));
    });

    function hunk(dl, ds, sl, ss) {
        var newHunk = { destinationLine: dl, destinationSpan: ds, sourceLine: sl, sourceSpan: ss };
        return newHunk;
    }

    function hunkArgs(start, end, offset, type) {
        return {start: start, end: end, offset: offset, type: type};
    }

    // We should _always_ get the same result for added/removed except for the offset (which is ignored anyway)
    function testGetSeparatedHunkHtml(hunks, expected) {
        function getSeparatedHunkHtml(type, newHunks, offsetCallback) {
            var changeType = FileChange.fromDiff({ hunks: newHunks }).getType();
            deepEqual(diffViewContext.getSeparatedHunkHtml(newHunks, changeType, hunkArgs), _.map(expected, function (e) {
                return _.extend({}, e, {type: type}, {offset: offsetCallback(e)});
            }));
        }

        getSeparatedHunkHtml('CONTEXT', hunks, function (e) {
            return e.offset;
        });
        getSeparatedHunkHtml('ADDED', _.map(hunks, function (h) {
            return hunk(h.sourceLine, h.sourceSpan, 0, 0);
        }), function () {
            return 0;
        });
        getSeparatedHunkHtml('REMOVED', _.map(hunks, function (h) {
            return hunk(0, 0, h.sourceLine, h.sourceSpan);
        }), function (e) {
            // Technically this should always be 0 too, but it's never displayed
            return e.offset === 0 ? 0 : e.start;
        });
    }

    test('Test getSeparatedHunkHtml', function () {
        testGetSeparatedHunkHtml([
            hunk(12, 30, 13, 32),
            hunk(56, 3, 59, 8)
        ], [
            {start: 1, end: 12, offset: 0},
            {start: 45, end: 58, offset: 3},
            {start: 67, end: NaN, offset: 8}
        ]);
    });

    function testDisplayExpandedContext(offset, changeType, result, expected) {
        var actual = diffViewContext.toHunks(offset, changeType, _.identity)(result);
        deepEqual(actual, expected, 'display expanded context: ' + [offset, changeType, result.contexts[1].start].join(','));
    }

    function testDisplayExpandedContextSimple(changeType, start, offset, dl, sl) {
        var result = {
            startSep: 10,
            endSep: 30,
            contexts: [
                null,
                {
                    start: start,
                    lines: [
                        {text: 'a'},
                        {text: 'b'}
                    ]},
                null
            ]
        };
        var expected = [
            {
                sourceLine: sl,
                destinationLine: dl,
                isLastPage: undefined,
                segments: [
                    {
                        lines: [
                            {line: 'a'},
                            {line: 'b'}
                        ],
                        type: changeType
                    }
                ]
            }
        ];
        testDisplayExpandedContext(offset, changeType, result, expected);
    }

    test('Test displaying of expanded contexts', function () {
        testDisplayExpandedContextSimple('CONTEXT', 34, 7, 28, 35);
        testDisplayExpandedContextSimple('ADDED', 34, 7, 28, 35);
    });

});
