define([
    'jquery',
    'bitbucket/internal/util/handler-registry'
], function (
    $,
    HandlerRegistry
) {

    'use strict';

    var registry = new HandlerRegistry();

    module('util-handler-registry', {
        teardown: function () {
            registry._clear();
        }
    });

    function doneArg(promise, i) {
        var result = null;
        promise.done(function () {
            result = arguments[i];
        });
        return result;
    }

    function done(promise) {
        return doneArg(promise, 0);
    }

    function doneErrors(promise) {
        return doneArg(promise, 1);
    }

    function fail(promise) {
        var result = null;
        promise.fail(function () {
            result = arguments[0];
        });
        return result;
    }

    function getContentModeHandler(contentMode) {
        return function (options) {
            if (options.contentMode === contentMode) {
                return $.Deferred().resolve(options);
            }
            return $.Deferred().reject('fail ' + contentMode);
        };
    }

    function register(weight, handlerOrType) {
        var handler = typeof handlerOrType === 'function' ? handlerOrType : getContentModeHandler(handlerOrType);
        return registry.register({
            weight: weight,
            handle: handler
        });
    }

    function handle(type) {
        return registry._handle({contentMode: type});
    }

    test('multiple registered handlers', function() {
        register(1, 'source');
        register(2, 'diff');

        equal(done(handle('diff')).contentMode, 'diff', 'Diff should be handled');
        equal(done(handle('source')).contentMode, 'source', 'Source should be handled');
        deepEqual(doneErrors(handle('diff')), ['fail source'], 'Errors should be returned');
        deepEqual(doneErrors(handle('source')), [], 'Errors should be returned but empty');
        ok(fail(handle('side-by-side')) instanceof Error, 'No Side by side diff file handler registered');
    });

    test('register handlers', function() {
        throws(function() {
            registry.register({weight: 1, handle: null});
        }, 'Error thrown if handle function is null');
        throws(function() {
            registry.register({weight: 1, handle: 'str'});
        }, 'Error thrown if handle function is not a function');

        register('invalidWeight', 'source');
        equal(done(handle('source')).contentMode, 'source', 'Should still register sourcehandler despite non-number weight');
    });

    test('unregister handlers', function() {
        var unregister = register(1, 'source');
        equal(done(handle('source')).contentMode, 'source', 'Source handler should be registered');
        unregister();
        ok(fail(handle('source')) instanceof Error, 'Source handler should have been unregistered');
    });

    test('handler does not return promise', function() {
        register(1, function() {
            return false;
        });
        ok(fail(handle('source')) instanceof Error, 'No handler for source');
        register(2, 'source');
        ok(done(handle('source')), 'Handler that returns non-falsey value handles source');
    });

    test('handle method is abortable.', function() {
        var delayedReject = $.Deferred();
        delayedReject.abort = sinon.spy();
        register(1, function() {
            return delayedReject;
        });

        var secondHandler = sinon.spy();
        register(2, secondHandler);

        var handlePromise = handle('source');
        ok(handlePromise.abort, 'Can abort a file-handle request');

        handlePromise.abort();
        equal(delayedReject.abort.callCount, 1, 'Abort is propagated');
        equal(handlePromise.state(), 'pending', 'Handler promise not resolved while work is being done.');

        delayedReject.reject();
        equal(handlePromise.state(), 'rejected', 'Handler promise rejects after last handler done.');
        equal(secondHandler.callCount, 0, 'Second handler never called');

        handlePromise.fail(function(reason) {
            ok(/abort/.test(reason));
        });
    });

});