/* jslint node: true */
'use strict';

const _ = require('lodash');

const FTN_ADDRESS_REGEXP = /^([0-9]+:)?([0-9]+)(\/[0-9]+)?(\.[0-9]+)?(@[a-z0-9\-.]+)?$/i;
const FTN_PATTERN_REGEXP =
    /^([0-9*]+:)?([0-9*]+)(\/[0-9*]+)?(\.[0-9*]+)?(@[a-z0-9\-.*]+)?$/i;

module.exports = class Address {
    constructor(addr) {
        if (addr) {
            if (_.isObject(addr)) {
                Object.assign(this, addr);
            } else if (_.isString(addr)) {
                const temp = Address.fromString(addr);
                if (temp) {
                    Object.assign(this, temp);
                }
            }
        }
    }

    static isValidAddress(addr) {
        return addr && addr.isValid();
    }

    isValid() {
        //  FTN address is valid if we have at least a net/node
        return _.isNumber(this.net) && _.isNumber(this.node);
    }

    isEqual(other) {
        if (_.isString(other)) {
            other = Address.fromString(other);
        }

        return (
            this.net === other.net &&
            this.node === other.node &&
            this.zone === other.zone &&
            this.point === other.point &&
            this.domain === other.domain
        );
    }

    getMatchAddr(pattern) {
        const m = FTN_PATTERN_REGEXP.exec(pattern);
        if (m) {
            let addr = {};

            if (m[1]) {
                addr.zone = m[1].slice(0, -1);
                if ('*' !== addr.zone) {
                    addr.zone = parseInt(addr.zone);
                }
            } else {
                addr.zone = '*';
            }

            if (m[2]) {
                addr.net = m[2];
                if ('*' !== addr.net) {
                    addr.net = parseInt(addr.net);
                }
            } else {
                addr.net = '*';
            }

            if (m[3]) {
                addr.node = m[3].substr(1);
                if ('*' !== addr.node) {
                    addr.node = parseInt(addr.node);
                }
            } else {
                addr.node = '*';
            }

            if (m[4]) {
                addr.point = m[4].substr(1);
                if ('*' !== addr.point) {
                    addr.point = parseInt(addr.point);
                }
            } else {
                addr.point = '*';
            }

            if (m[5]) {
                addr.domain = m[5].substr(1);
            } else {
                addr.domain = '*';
            }

            return addr;
        }
    }

    /*
    getMatchScore(pattern) {
        let score = 0;
        const addr = this.getMatchAddr(pattern);
        if(addr) {
            const PARTS = [ 'net', 'node', 'zone', 'point', 'domain' ];
            for(let i = 0; i < PARTS.length; ++i) {
                const member = PARTS[i];
                if(this[member] === addr[member]) {
                    score += 2;
                } else if('*' === addr[member]) {
                    score += 1;
                } else {
                    break;
                }
            }
        }

        return score;
    }
    */

    isPatternMatch(pattern) {
        const addr = this.getMatchAddr(pattern);
        if (addr) {
            return (
                ('*' === addr.net || this.net === addr.net) &&
                ('*' === addr.node || this.node === addr.node) &&
                ('*' === addr.zone || this.zone === addr.zone) &&
                ('*' === addr.point || this.point === addr.point) &&
                ('*' === addr.domain || this.domain === addr.domain)
            );
        }

        return false;
    }

    static fromString(addrStr) {
        const m = FTN_ADDRESS_REGEXP.exec(addrStr);

        if (m && m[2] && m[3]) {
            //  start with a 2D
            let addr = {
                net: parseInt(m[2]),
                node: parseInt(m[3].substr(1)),
            };

            //  3D: Addition of zone if present
            if (m[1]) {
                addr.zone = parseInt(m[1].slice(0, -1));
            }

            //  4D if optional point is present
            if (m[4]) {
                addr.point = parseInt(m[4].substr(1));
            }

            //  5D with @domain
            if (m[5]) {
                addr.domain = m[5].substr(1);
            }

            return new Address(addr);
        }
    }

    toString(dimensions) {
        dimensions = dimensions || '5D';

        let addrStr = `${this.zone}:${this.net}`;

        //  allow for e.g. '4D' or 5
        const dim = parseInt(dimensions.toString()[0]);

        if (dim >= 3) {
            addrStr += `/${this.node}`;
        }

        //  missing & .0 are equiv for point
        if (dim >= 4 && this.point) {
            addrStr += `.${this.point}`;
        }

        if (5 === dim && this.domain) {
            addrStr += `@${this.domain.toLowerCase()}`;
        }

        return addrStr;
    }

    static getComparator() {
        return function (left, right) {
            let c = (left.zone || 0) - (right.zone || 0);
            if (0 !== c) {
                return c;
            }

            c = (left.net || 0) - (right.net || 0);
            if (0 !== c) {
                return c;
            }

            c = (left.node || 0) - (right.node || 0);
            if (0 !== c) {
                return c;
            }

            return (left.domain || '').localeCompare(right.domain || '');
        };
    }
};
