import ngModule from '../module';
import {ng} from '../vendor';

function stringify(value) {
    if (value === null) { // null || undefined
        return '';
    }
    switch (typeof value) {
    case 'string':
        break;
    case 'number':
        value = String(value);
        break;
    default:
        value = angular.toJson(value);
    }

    return value;
}

var $interpolateMinErr = ng.$interpolateMinErr = ng.$$minErr('$interpolate');

$interpolateMinErr.throwNoconcat = function(text) {
    throw $interpolateMinErr('noconcat',
        'Error while interpolating: {0}\nStrict Contextual Escaping disallows ' +
        'interpolations that concatenate multiple expressions when a trusted value is ' +
        'required.  See http://docs.angularjs.org/api/ng.$sce', text);
};

$interpolateMinErr.interr = function(text, err) {
    return $interpolateMinErr('interr', 'Can\'t interpolate: {0}\n{1}', text, err.toString());
};

function $get($parse, $exceptionHandler, $sce) {
    let startSymbolLength = this._startSymbol.length;
    let endSymbolLength = this._endSymbol.length;
    let escapedStartRegexp = new RegExp(this._startSymbol.replace(/./g, escape), 'g');
    let escapedEndRegexp = new RegExp(this._endSymbol.replace(/./g, escape), 'g');
    let self = this;

    function escape(ch) {
        return '\\\\\\' + ch;
    }

    function unescapeText(text) {
        return text.replace(escapedStartRegexp, self._startSymbol)
                .replace(escapedEndRegexp, self._endSymbol);
    }

        // TODO: this is the same as the constantWatchDelegate in parse.js
    function constantWatchDelegate(scope, listener, objectEquality, constantInterp) {
        var unwatch = scope.$watch(function constantInterpolateWatch(scope) {
            unwatch();
            return constantInterp(scope);
        }, listener, objectEquality);
        return unwatch;
    }

    function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
            // Provide a quick exit and simplified result function for text with no interpolation
        if (!text.length || text.indexOf(self._startSymbol) === -1) {
            var constantInterp;
            if (!mustHaveExpression) {
                var unescapedText = unescapeText(text);
                constantInterp = valueFn(unescapedText);
                constantInterp.exp = text;
                constantInterp.expressions = [];
                constantInterp.$$watchDelegate = constantWatchDelegate;
            }
            return constantInterp;
        }

        allOrNothing = Boolean(allOrNothing);
        let startIndex;
        let endIndex;
        let index = 0;
        let expressions = [];
        let parseFns = [];
        let textLength = text.length;
        let exp;
        let concat = [];
        let expressionPositions = [];

        while (index < textLength) {
            if (((startIndex = text.indexOf(self._startSymbol, index)) !== -1) &&
                    ((endIndex = text.indexOf(self._endSymbol, startIndex + startSymbolLength)) !== -1)) {
                if (index !== startIndex)
                    concat.push(unescapeText(text.substring(index, startIndex)));
                exp = text.substring(startIndex + startSymbolLength, endIndex);
                expressions.push(exp);
                parseFns.push($parse(exp, parseStringifyInterceptor));
                index = endIndex + endSymbolLength;
                expressionPositions.push(concat.length);
                concat.push('');
            } else {
                    // we did not find an interpolation, so we have to add the remainder to the separators array
                if (index !== textLength)
                    concat.push(unescapeText(text.substring(index)));
                break;
            }
        }

        if (trustedContext && concat.length > 1) {
            $interpolateMinErr.throwNoconcat(text);
        }

        var getValue = function(value) {
            return trustedContext ?
                    $sce.getTrusted(trustedContext, value) :
                    $sce.valueOf(value);
        };

        if (!mustHaveExpression || expressions.length) {
            var compute = function(values) {
                for (var i = 0, ii = expressions.length; i < ii; i++) {
                    if (allOrNothing && isUndefined(values[i])) return;
                    concat[expressionPositions[i]] = values[i];
                }
                return concat.join('');
            };

            return angular.extend(function interpolationFn(context) {
                var i = 0;
                var ii = expressions.length;
                var values = new Array(ii);

                try {
                    for (; i < ii; i++) {
                        values[i] = parseFns[i](context);
                    }

                    return compute(values);
                } catch (err) {
                    $exceptionHandler($interpolateMinErr.interr(text, err));
                }
            }, {
                // all of these properties are undocumented for now
                exp: text, // just for compatibility with regular watchers created via $watch
                expressions: expressions
            });
        }

        function parseStringifyInterceptor(value) {
            try {
                value = getValue(value);
                return allOrNothing && !isDefined(value) ? value : stringify(value);
            } catch (err) {
                $exceptionHandler($interpolateMinErr.interr(text, err));
            }
        }
    }

    $interpolate.startSymbol = function() {
        return startSymbol;
    };

    $interpolate.endSymbol = function() {
        return endSymbol;
    };

    return $interpolate;
}

$get.$inject = ['$parse', '$exceptionHandler', '$sce'];

export class Interpolate {
    constructor() {
        this._startSymbol = '*[';
        this._endSymbol = ']*';
    }
    set startSymbol(value) {
        if (value) {
            this._startSymbol = value;
            return this;
        }
        return this._startSymbol;
    }
    set endSymbol(value) {
        if (value) {
            this._endSymbol = value;
            return this;
        }
        return this._endSymbol;
    }
}

Interpolate.prototype.$get = $get;
var interpolate = new Interpolate();
ngModule.provider('vnInterpolate', () => interpolate);