// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.


/*jslint browser: true, continue: true, plusplus: true */
/*global $: false, angular: false, console: false, validLangs: false */

var syncthing = angular.module('syncthing', [
    'angularUtils.directives.dirPagination',
    'pascalprecht.translate', 'ngSanitize',

    'syncthing.core'
]);

var urlbase = 'rest';
var authUrlbase = urlbase + '/noauth/auth';

// keep consistent with ShortIDStringLength in lib/protocol/deviceid.go
var shortIDStringLength = 7;

syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvider) {
    // language and localisation

    $translateProvider.useSanitizeValueStrategy('escape');
    $translateProvider.useStaticFilesLoader({
        prefix: 'assets/lang/lang-',
        suffix: '.json'
    });
    $translateProvider.fallbackLanguage('en');

    LocaleServiceProvider.setAvailableLocales(validLangs);
    LocaleServiceProvider.setDefaultLocale('en');

    $httpProvider.useApplyAsync(true);

    if (!window.metadata) {
        // Most likely we're not authenticated yet, in which case we can't proceed with the rest of the setup.
        // Do nothing and wait for the page reload on successful login.
        return;
    }

    $httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token-' + metadata.deviceIDShort;
    $httpProvider.defaults.xsrfCookieName = 'CSRF-Token-' + metadata.deviceIDShort;
});

// @TODO: extract global level functions into separate service(s)

function deviceCompare(a, b) {
    if (typeof a.name !== 'undefined' && typeof b.name !== 'undefined') {
        if (a.name < b.name)
            return -1;
        return a.name > b.name;
    }
    if (a.deviceID < b.deviceID) {
        return -1;
    }
    return a.deviceID > b.deviceID;
}

function folderCompare(a, b) {
    var labelA = a.id;
    if (typeof a.label !== 'undefined' && a.label !== null && a.label.length > 0) {
        labelA = a.label;
    }

    var labelB = b.id;
    if (typeof b.label !== 'undefined' && b.label !== null && b.label.length > 0) {
        labelB = b.label;
    }

    if (labelA < labelB) {
        return -1;
    }
    return labelA > labelB;
}

function deviceMap(l) {
    var m = {};
    l.forEach(function (r) {
        m[r.deviceID] = r;
    });
    return m;
}

function deviceList(m) {
    var l = [];
    for (var id in m) {
        l.push(m[id]);
    }
    l.sort(deviceCompare);
    return l;
}

function folderMap(l) {
    var m = {};
    l.forEach(function (r) {
        m[r.id] = r;
    });
    return m;
}

function folderList(m) {
    var l = [];
    for (var id in m) {
        l.push(m[id]);
    }
    l.sort(folderCompare);
    return l;
}

function isEmptyObject(obj) {
    var name;
    for (name in obj) {
        return false;
    }
    return true;
}

function debounce(func, wait) {
    var timeout, args, context, timestamp, result, again;

    var later = function () {
        var last = Date.now() - timestamp;
        if (last < wait) {
            timeout = setTimeout(later, wait - last);
        } else {
            timeout = null;
            if (again) {
                again = false;
                result = func.apply(context, args);
                context = args = null;
            }
        }
    };

    return function () {
        context = this;
        args = arguments;
        timestamp = Date.now();
        if (!timeout) {
            timeout = setTimeout(later, wait);
            result = func.apply(context, args);
            context = args = null;
        } else {
            again = true;
        }

        return result;
    };
}

function buildTree(children) {
    /* Converts
    *
    * {
    *   'foo/bar': [...],
    *   'foo/baz': [...]
    * }
    *
    * to
    *
    * [
    *   {
    *     title: 'foo',
    *     children: [
    *       {
    *         title: 'bar',
    *         versions: [...],
    *         ...
    *       },
    *       {
    *         title: 'baz',
    *         versions: [...],
    *         ...
    *       }
    *     ],
    *   }
    * ]
    */
    var root = {
        children: []
    }

    $.each(children, function (path, data) {
        var parts = path.split('/');
        var name = parts.splice(-1)[0];

        var keySoFar = [];
        var parent = root;
        while (parts.length > 0) {
            var part = parts.shift();
            keySoFar.push(part);
            var found = false;
            for (var i = 0; i < parent.children.length; i++) {
                if (parent.children[i].title == part && parent.children[i].folder === true) {
                    parent = parent.children[i];
                    found = true;
                    break;
                }
            }
            if (!found) {
                var child = {
                    title: part,
                    key: keySoFar.join('/'),
                    folder: true,
                    children: []
                };
                parent.children.push(child);
                parent = child;
            }
        }

        parent.children.push({
            title: name,
            key: path,
            folder: false,
            versions: data,
        });
    });

    return root.children;
}

// unitPrefixed converts the input such that it returns a string representation
// <1000 (<1024) with the metric unit prefix suffixed. I.e. when calling this with
// binary == true, you need to suffix an addition 'i'.  The "biggest" prefix used
// is 'T', numbers > 1000T are just returned as such big numbers. If ever deemed
// useful 'P' can be added easily.
function unitPrefixed(input, binary) {
    if (input === undefined || isNaN(input)) {
        return '0 ';
    }
    var factor = 1000;
    var i = '';
    if (binary) {
        factor = 1024;
        i = 'i';
    }
    if (input > factor * factor * factor * factor * 1000) {
        // Don't show any decimals for more than 4 digits
        input /= factor * factor * factor * factor;
        return input.toLocaleString(undefined, { maximumFractionDigits: 0 }) + ' T' + i;
    }
    // Show 3 significant digits (e.g. 123T or 2.54T)
    if (input > factor * factor * factor * factor) {
        input /= factor * factor * factor * factor;
        return input.toLocaleString(undefined, { maximumSignificantDigits: 3 }) + ' T' + i;
    }
    if (input > factor * factor * factor) {
        input /= factor * factor * factor;
        if (binary && input >= 1000) {
            return input.toLocaleString(undefined, { maximumFractionDigits: 0 }) + ' G' + i;
        }
        return input.toLocaleString(undefined, { maximumSignificantDigits: 3 }) + ' G' + i;
    }
    if (input > factor * factor) {
        input /= factor * factor;
        if (binary && input >= 1000) {
            return input.toLocaleString(undefined, { maximumFractionDigits: 0 }) + ' M' + i;
        }
        return input.toLocaleString(undefined, { maximumSignificantDigits: 3 }) + ' M' + i;
    }
    if (input > factor) {
        input /= factor;
        var prefix = ' k';
        if (binary) {
            prefix = ' K';
        }
        if (binary && input >= 1000) {
            return input.toLocaleString(undefined, { maximumFractionDigits: 0 }) + prefix + i;
        }
        return input.toLocaleString(undefined, { maximumSignificantDigits: 3 }) + prefix + i;
    }
    return Math.round(input).toLocaleString() + ' ';
};
