'use strict';

const api = require('../api.js');
const uri = require('../util/uri.js');
const tags = require('../tags.js');
const events = require('../events.js');
const NoteList = require('./note_list.js');
const CommentList = require('./comment_list.js');
const misc = require('../util/misc.js');

function _syncObservableCollection(target, plainList) {
    target.clear();
    for (let item of (plainList || [])) {
        target.add(target.constructor._itemClass.fromResponse(item));
    }
}

class Post extends events.EventTarget {
    constructor() {
        super();
        this._orig = {};

        for (let obj of [this, this._orig]) {
            obj._notes = new NoteList();
            obj._comments = new CommentList();
        }

        this._updateFromResponse({});
    }

    get id()                 { return this._id; }
    get type()               { return this._type; }
    get mimeType()           { return this._mimeType; }
    get creationTime()       { return this._creationTime; }
    get user()               { return this._user; }
    get safety()             { return this._safety; }
    get contentUrl()         { return this._contentUrl; }
    get thumbnailUrl()       { return this._thumbnailUrl; }
    get canvasWidth()        { return this._canvasWidth || 800; }
    get canvasHeight()       { return this._canvasHeight || 450; }
    get fileSize()           { return this._fileSize || 0; }
    get newContent()         { throw 'Invalid operation'; }
    get newThumbnail()       { throw 'Invalid operation'; }

    get flags()              { return this._flags; }
    get tags()               { return this._tags; }
    get notes()              { return this._notes; }
    get comments()           { return this._comments; }
    get relations()          { return this._relations; }

    get score()              { return this._score; }
    get commentCount()       { return this._commentCount; }
    get favoriteCount()      { return this._favoriteCount; }
    get ownFavorite()        { return this._ownFavorite; }
    get ownScore()           { return this._ownScore; }
    get hasCustomThumbnail() { return this._hasCustomThumbnail; }

    set flags(value)         { this._flags = value; }
    set tags(value)          { this._tags = value; }
    set safety(value)        { this._safety = value; }
    set relations(value)     { this._relations = value; }
    set newContent(value)    { this._newContent = value; }
    set newThumbnail(value)  { this._newThumbnail = value; }

    static fromResponse(response) {
        const ret = new Post();
        ret._updateFromResponse(response);
        return ret;
    }

    static reverseSearch(content) {
        let apiPromise = api.post(
            uri.formatApiLink('posts', 'reverse-search'),
            {},
            {content: content});
        let returnedPromise = apiPromise
            .then(response => {
                if (response.exactPost) {
                    response.exactPost = Post.fromResponse(response.exactPost);
                }
                for (let item of response.similarPosts) {
                    item.post = Post.fromResponse(item.post);
                }
                return Promise.resolve(response);
            });
        returnedPromise.abort = () => apiPromise.abort();
        return returnedPromise;
    }

    static get(id) {
        return api.get(uri.formatApiLink('post', id))
            .then(response => {
                return Promise.resolve(Post.fromResponse(response));
            });
    }

    isTaggedWith(tagName) {
        return this._tags
            .map(s => s.toLowerCase())
            .includes(tagName.toLowerCase());
    }

    addTag(tagName, addImplications) {
        if (this.isTaggedWith(tagName)) {
            return;
        }
        this._tags.push(tagName);
        if (addImplications !== false) {
            for (let otherTag of tags.getAllImplications(tagName)) {
                this.addTag(otherTag, addImplications);
            }
        }
    }

    removeTag(tagName) {
        this._tags = this._tags.filter(
            s => s.toLowerCase() != tagName.toLowerCase());
    }

    save(anonymous) {
        const files = {};
        const detail = {version: this._version};

        // send only changed fields to avoid user privilege violation
        if (anonymous === true) {
            detail.anonymous = true;
        }
        if (this._safety !== this._orig._safety) {
            detail.safety = this._safety;
        }
        if (misc.arraysDiffer(this._flags, this._orig._flags)) {
            detail.flags = this._flags;
        }
        if (misc.arraysDiffer(this._tags, this._orig._tags)) {
            detail.tags = this._tags;
        }
        if (misc.arraysDiffer(this._relations, this._orig._relations)) {
            detail.relations = this._relations;
        }
        if (misc.arraysDiffer(this._notes, this._orig._notes)) {
            detail.notes = [...this._notes].map(note => ({
                polygon: [...note.polygon].map(
                    point => [point.x, point.y]),
                text: note.text,
            }));
        }
        if (this._newContent) {
            files.content = this._newContent;
        }
        if (this._newThumbnail !== undefined) {
            files.thumbnail = this._newThumbnail;
        }

        let apiPromise = this._id ?
            api.put(uri.formatApiLink('post', this.id), detail, files) :
            api.post(uri.formatApiLink('posts'), detail, files);

        return apiPromise.then(response => {
            this._updateFromResponse(response);
            this.dispatchEvent(
                new CustomEvent('change', {detail: {post: this}}));
            if (this._newContent) {
                this.dispatchEvent(
                    new CustomEvent('changeContent', {detail: {post: this}}));
            }
            if (this._newThumbnail) {
                this.dispatchEvent(
                    new CustomEvent('changeThumbnail', {detail: {post: this}}));
            }
            return Promise.resolve();
        }, error => {
            if (error.response &&
                    error.response.name === 'PostAlreadyUploadedError') {
                error.message =
                    `Post already uploaded (@${error.response.otherPostId})`;
            }
            return Promise.reject(error);
        });
    }

    feature() {
        return api.post(
                uri.formatApiLink('featured-post'),
                {id: this._id})
            .then(response => {
                return Promise.resolve();
            });
    }

    delete() {
        return api.delete(
                uri.formatApiLink('post', this.id),
                {version: this._version})
            .then(response => {
                this.dispatchEvent(new CustomEvent('delete', {
                    detail: {
                        post: this,
                    },
                }));
                return Promise.resolve();
            });
    }

    merge(targetId, useOldContent) {
        return api.get(uri.formatApiLink('post', targetId))
            .then(response => {
                return api.post(uri.formatApiLink('post-merge'), {
                    removeVersion: this._version,
                    remove: this._id,
                    mergeToVersion: response.version,
                    mergeTo: targetId,
                    replaceContent: useOldContent,
                });
            }).then(response => {
                this._updateFromResponse(response);
                this.dispatchEvent(new CustomEvent('change', {
                    detail: {
                        post: this,
                    },
                }));
                return Promise.resolve();
            });
    }

    setScore(score) {
        return api.put(
                uri.formatApiLink('post', this.id, 'score'),
                {score: score})
            .then(response => {
                const prevFavorite = this._ownFavorite;
                this._updateFromResponse(response);
                if (this._ownFavorite !== prevFavorite) {
                    this.dispatchEvent(new CustomEvent('changeFavorite', {
                        detail: {
                            post: this,
                        },
                    }));
                }
                this.dispatchEvent(new CustomEvent('changeScore', {
                    detail: {
                        post: this,
                    },
                }));
                return Promise.resolve();
            });
    }

    addToFavorites() {
        return api.post(uri.formatApiLink('post', this.id, 'favorite'))
            .then(response => {
                const prevScore = this._ownScore;
                this._updateFromResponse(response);
                if (this._ownScore !== prevScore) {
                    this.dispatchEvent(new CustomEvent('changeScore', {
                        detail: {
                            post: this,
                        },
                    }));
                }
                this.dispatchEvent(new CustomEvent('changeFavorite', {
                    detail: {
                        post: this,
                    },
                }));
                return Promise.resolve();
            });
    }

    removeFromFavorites() {
        return api.delete(uri.formatApiLink('post', this.id, 'favorite'))
            .then(response => {
                const prevScore = this._ownScore;
                this._updateFromResponse(response);
                if (this._ownScore !== prevScore) {
                    this.dispatchEvent(new CustomEvent('changeScore', {
                        detail: {
                            post: this,
                        },
                    }));
                }
                this.dispatchEvent(new CustomEvent('changeFavorite', {
                    detail: {
                        post: this,
                    },
                }));
                return Promise.resolve();
            });
    }

    mutateContentUrl() {
        this._contentUrl =
            this._orig._contentUrl +
            '?bypass-cache=' +
            Math.round(Math.random() * 1000);
    }

    _updateFromResponse(response) {
        const map = () => ({
            _version:       response.version,
            _id:            response.id,
            _type:          response.type,
            _mimeType:      response.mimeType,
            _creationTime:  response.creationTime,
            _user:          response.user,
            _safety:        response.safety,
            _contentUrl:    response.contentUrl,
            _thumbnailUrl:  response.thumbnailUrl,
            _canvasWidth:   response.canvasWidth,
            _canvasHeight:  response.canvasHeight,
            _fileSize:      response.fileSize,

            _flags:         [...response.flags || []],
            _tags:          [...response.tags || []],
            _relations:     [...response.relations || []],

            _score:         response.score,
            _commentCount:  response.commentCount,
            _favoriteCount: response.favoriteCount,
            _ownScore:      response.ownScore,
            _ownFavorite:   response.ownFavorite,
            _hasCustomThumbnail: response.hasCustomThumbnail,
        });

        for (let obj of [this, this._orig]) {
            _syncObservableCollection(obj._notes, response.notes);
            _syncObservableCollection(obj._comments, response.comments);
        }

        Object.assign(this, map());
        Object.assign(this._orig, map());
    }
};

module.exports = Post;