function decompose(endpoint) {
    // Note that we allow spaces in targets and sources but they are trimmed
    var regExp = /^(?:([\w\-]|(?:[\w\.\-]+[\w\-])?)=)?([^\|#]+)(?:#(.*))?$/;
    var matches = endpoint.match(regExp);
    var target;
    var error;

    if (!matches) {
        error = new Error('Invalid endpoint: ' + endpoint);
        error.code = 'EINVEND';
        throw error;
    }

    target = trim(matches[3]);

    return {
        name: trim(matches[1]),
        source: trim(matches[2]),
        target: isWildcard(target) ? '*' : target
    };
}

function compose(decEndpoint) {
    var name = trim(decEndpoint.name);
    var source = trim(decEndpoint.source);
    var target = trim(decEndpoint.target);
    var composed = '';

    if (name) {
        composed += name + '=';
    }

    composed += source;

    if (!isWildcard(target)) {
        composed += '#' + target;
    }

    return composed;
}

function json2decomposed(key, value) {
    var endpoint;
    var split;
    var error;

    key = trim(key);
    value = trim(value);

    if (!key) {
        error = new Error('The key must be specified');
        error.code = 'EINVEND';
        throw error;
    }

    endpoint = key + '=';
    split = value.split('#').map(trim);

    // If # was found, the source was specified
    if (split.length > 1) {
        endpoint += (split[0] || key) + '#' + split[1];
        // Check if value looks like a source
    } else if (isSource(value)) {
        endpoint += value + '#*';
        // Otherwise use the key as the source
    } else {
        endpoint += key + '#' + split[0];
    }

    return decompose(endpoint);
}

function decomposed2json(decEndpoint) {
    var error;
    var name = trim(decEndpoint.name);
    var source = trim(decEndpoint.source);
    var target = trim(decEndpoint.target);
    var value = '';
    var ret = {};

    if (!name) {
        error = new Error('Decomposed endpoint must have a name');
        error.code = 'EINVEND';
        throw error;
    }

    // Add source only if different than the name
    if (source !== name) {
        value += source;
    }

    // If value is empty, we append the target always
    if (!value) {
        if (isWildcard(target)) {
            value += '*';
        } else {
            if (target.indexOf('/') !== -1) {
                value += '#' + target;
            } else {
                value += target;
            }
        }
        // Otherwise append only if not a wildcard or source does not look like a source
    } else if (!isWildcard(target) || !isSource(source)) {
        value += '#' + (target || '*');
    }

    ret[name] = value;

    return ret;
}

function trim(str) {
    return str ? str.trim() : '';
}

function isWildcard(target) {
    return !target || target === '*' || target === 'latest';
}

function isSource(value) {
    return /[\/\\@]/.test(value);
}

module.exports.decompose = decompose;
module.exports.compose = compose;
module.exports.json2decomposed = json2decomposed;
module.exports.decomposed2json = decomposed2json;
