const _debug = require('ghost-ignition').debug._base,
    debug = _debug('ghost:services:url:service'),
    _ = require('lodash'),
    common = require('../../lib/common'),
    UrlGenerator = require('./UrlGenerator'),
    Queue = require('./Queue'),
    Urls = require('./Urls'),
    Resources = require('./Resources'),
    localUtils = require('./utils');

class UrlService {
    constructor() {
        this.utils = localUtils;

        this.finished = false;
        this.urlGenerators = [];

        this.urls = new Urls();
        this.queue = new Queue();
        this.resources = new Resources(this.queue);

        this._listeners();
    }

    _listeners() {
        /**
         * The purpose of this event is to notify the url service as soon as a router get's created.
         */
        this._onRouterAddedListener = this._onRouterAddedType.bind(this);
        common.events.on('router.created', this._onRouterAddedListener);

        /**
         * The queue will notify us if url generation has started/finished.
         */
        this._onQueueStartedListener = this._onQueueStarted.bind(this);
        this.queue.addListener('started', this._onQueueStartedListener);

        this._onQueueEndedListener = this._onQueueEnded.bind(this);
        this.queue.addListener('ended', this._onQueueEnded.bind(this));
    }

    _onQueueStarted(event) {
        if (event === 'init') {
            this.finished = false;
        }
    }

    _onQueueEnded(event) {
        if (event === 'init') {
            this.finished = true;
        }
    }

    _onRouterAddedType(router) {
        // CASE: there are router types which do not generate resource urls
        //       e.g. static route router
        //       we are listening on the general `router.created` event - every router throws this event
        if (!router || !router.getPermalinks()) {
            return;
        }

        debug('router.created');

        let urlGenerator = new UrlGenerator(router, this.queue, this.resources, this.urls, this.urlGenerators.length);
        this.urlGenerators.push(urlGenerator);
    }

    /**
     * You have a url and want to know which the url belongs to.
     *
     * It's in theory possible that multiple resources generate the same url,
     * but they both would serve different content.
     *
     * e.g. if we remove the slug uniqueness and you create a static
     * page and a post with the same slug. And both are served under `/` with the permalink `/:slug/`.
     *
     *
     * Each url is unique and it depends on the hierarchy of router registration is configured.
     * There is no url collision, everything depends on registration order.
     *
     * e.g. posts which live in a collection are stronger than a static page.
     *
     * We only return the resource, which would be served.
     *
     * @NOTE: only accepts relative urls at the moment.
     */
    getResource(url, options) {
        options = options || {};

        let objects = this.urls.getByUrl(url);

        if (!objects.length) {
            if (!this.hasFinished()) {
                throw new common.errors.InternalServerError({
                    message: 'UrlService is processing.',
                    code: 'URLSERVICE_NOT_READY'
                });
            } else {
                return null;
            }
        }

        if (objects.length > 1) {
            objects = _.reduce(objects, (toReturn, object) => {
                if (!toReturn.length) {
                    toReturn.push(object);
                } else {
                    const i1 = _.findIndex(this.urlGenerators, {uid: toReturn[0].generatorId});
                    const i2 = _.findIndex(this.urlGenerators, {uid: object.generatorId});

                    if (i2 < i1) {
                        toReturn = [];
                        toReturn.push(object);
                    }
                }

                return toReturn;
            }, []);
        }

        if (options.returnEverything) {
            return objects[0];
        }

        return objects[0].resource;
    }

    getResourceById(resourceId) {
        const object = this.urls.getByResourceId(resourceId);

        if (!object) {
            throw new common.errors.InternalServerError({
                message: 'Resource not found.',
                code: 'URLSERVICE_RESOURCE_NOT_FOUND'
            });
        }

        return object.resource;
    }

    hasFinished() {
        return this.finished;
    }

    /**
     * Get url by resource id.
     * e.g. tags, authors, posts, pages
     *
     * If we can't find a url for an id, we have to return a url.
     * There are many components in Ghost which call `getUrlByResourceId` and
     * based on the return value, they set the resource url somewhere e.g. meta data.
     * Or if you define no collections in your yaml file and serve a page.
     * You will see a suggestion of posts, but they all don't belong to a collection.
     * They would show localhost:2368/null/.
     */
    getUrlByResourceId(id, options) {
        options = options || {};

        const obj = this.urls.getByResourceId(id);

        if (obj) {
            if (options.absolute) {
                return this.utils.createUrl(obj.url, options.absolute, options.secure);
            }

            if (options.withSubdirectory) {
                return this.utils.createUrl(obj.url, false, options.secure, true);
            }

            return obj.url;
        }

        if (options.absolute) {
            return this.utils.createUrl('/404/', options.absolute, options.secure);
        }

        if (options.withSubdirectory) {
            return this.utils.createUrl('/404/', false, options.secure);
        }

        return '/404/';
    }

    owns(routerId, url) {
        debug('owns', routerId, url);

        let urlGenerator;

        this.urlGenerators.every((_urlGenerator) => {
            if (_urlGenerator.router.identifier === routerId) {
                urlGenerator = _urlGenerator;
                return false;
            }

            return true;
        });

        if (!urlGenerator) {
            return false;
        }

        return urlGenerator.hasUrl(url);
    }

    getPermalinkByUrl(url, options) {
        options = options || {};

        const object = this.getResource(url, {returnEverything: true});

        if (!object) {
            return null;
        }

        return _.find(this.urlGenerators, {uid: object.generatorId}).router.getPermalinks()
            .getValue(options);
    }

    reset() {
        debug('reset');
        this.urlGenerators = [];

        this.urls.reset();
        this.queue.reset();
        this.resources.reset();

        this._onQueueStartedListener && this.queue.removeListener('started', this._onQueueStartedListener);
        this._onQueueEndedListener && this.queue.removeListener('ended', this._onQueueEndedListener);
        this._onRouterAddedListener && common.events.removeListener('router.created', this._onRouterAddedListener);
    }

    resetGenerators() {
        debug('resetGenerators');
        this.finished = false;
        this.urlGenerators = [];
        this.urls.reset();
        this.queue.reset();
        this.resources.softReset();
    }

    softReset() {
        debug('softReset');
        this.finished = false;
        this.urls.softReset();
        this.queue.softReset();
        this.resources.softReset();
    }
}

module.exports = UrlService;
