/*
 * Copyright (C) 2008-2020, Juick
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.juick.model;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.juick.util.adapters.SimpleDateAdapter;

import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import org.apache.commons.lang3.builder.ToStringBuilder;

import javax.annotation.Nonnull;
import java.io.Serializable;
import java.net.URI;
import java.time.Instant;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * @author Ugnich Anton
 */
@XmlRootElement(name = "juick", namespace = "http://juick.com/message")
@XmlAccessorType()
public class Message implements Comparable<Message>, Serializable {
    private int mid = 0;
    private int rid = 0;
    private int replyto = 0;
    private String text = null;
    private User user = null;
    private Set<Tag> tags;
    private Instant created;
    private Instant updated;
    private Instant updatedAt;
    private boolean unread;
    @JsonIgnore
    private int privacy = 1;
    @XmlTransient
    @JsonIgnore
    public boolean FriendsOnly = false;
    @XmlTransient
    @JsonIgnore
    public boolean ReadOnly = false;
    @XmlTransient
    @JsonIgnore
    public boolean Hidden = false;
    @JsonIgnore
    @XmlTransient
    public boolean VisitorCanComment = true;
    private int replies = 0;
    private String repliesBy;
    private String attachmentType;
    @XmlTransient
    private Photo photo;
    @XmlTransient
    private Attachment attachment;
    private User to;
    private String replyQuote;
    @XmlTransient
    private Set<Reaction> reactions;
    private boolean service;
    private URI replyUri;
    private URI replyToUri;
    private boolean html;

    private Set<User> recommendations;

    private List<Entity> entities;

    public Message() {
        tags = new LinkedHashSet<>();
        reactions = new HashSet<>();
        recommendations = new HashSet<>();
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("mid", mid)
                .append("rid", rid)
                .append("replyto", replyto)
                .append("privacy", privacy)
                .append("FriendsOnly", FriendsOnly)
                .append("ReadOnly", ReadOnly)
                .append("Hidden", Hidden)
                .append("VisitorCanComment", VisitorCanComment)
                .append("replies", replies)
                .append("likes", getLikes())
                .append("reactions", reactions)
                .toString();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;

        if (!(obj instanceof Message))
            return false;

        Message jmsg = (Message) obj;
        return (this.getMid() == jmsg.getMid() && this.getRid() == jmsg.getRid());
    }

    @Override
    public int compareTo(Message jmsg) throws ClassCastException {
        if (jmsg == this)
            return 0;

        int cmp = Integer.compare(jmsg.getMid(), getMid());

        if (cmp == 0)
            cmp = Integer.compare(getRid(), jmsg.getRid());

        return cmp;
    }

    @JsonProperty("mid")
    @XmlAttribute(name = "mid")
    public int getMid() {
        return mid;
    }

    public void setMid(int mid) {
        this.mid = mid;
    }

    @JsonProperty("rid")
    @XmlAttribute(name = "rid")
    public int getRid() {
        return rid;
    }

    public void setRid(int rid) {
        this.rid = rid;
    }

    @XmlElement(name = "user", namespace = "http://juick.com/user")
    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @JsonProperty("body")
    @XmlElement(name = "body")
    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    @JsonProperty("timestamp")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
    @XmlAttribute(name = "ts")
    @XmlJavaTypeAdapter(SimpleDateAdapter.class)
    public Instant getCreated() {
        return created;
    }

    public void setCreated(Instant timestamp) {
        this.created = timestamp;
    }

    @XmlTransient
    public User getTo() {
        return to;
    }

    public void setTo(User to) {
        this.to = to;
    }

    @XmlAttribute(name = "quote")
    public String getReplyQuote() {
        return replyQuote;
    }

    public void setReplyQuote(String quote) {
        replyQuote = quote;
    }

    @JsonProperty("replyto")
    @XmlAttribute(name = "replyto")
    public int getReplyto() {
        return replyto;
    }

    public void setReplyto(int replyto) {
        this.replyto = replyto;
    }

    @JsonProperty("tags")
    @XmlElement(name = "tag")
    public Set<Tag> getTags() {
        return tags;
    }

    public void setTags(Set<Tag> tags) {
        this.tags = tags;
    }

    @XmlAttribute
    public int getPrivacy() {
        return privacy;
    }

    public void setPrivacy(int privacy) {
        this.privacy = privacy;
    }

    public Photo getPhoto() {
        return photo;
    }

    public void setPhoto(Photo photo) {
        this.photo = photo;
    }

    @XmlAttribute(name = "attach")
    @JsonProperty("attach")
    public String getAttachmentType() {
        return attachmentType;
    }

    public void setAttachmentType(String attachmentType) {
        this.attachmentType = attachmentType;
    }

    @XmlTransient
    public int getReplies() {
        return replies;
    }

    public void setReplies(int replies) {
        this.replies = replies;
    }

    @XmlTransient
    @Deprecated
    public int getLikes() {
        return recommendations.size();
    }

    @JsonProperty("repliesby")
    public String getRepliesBy() {
        return repliesBy;
    }

    public void setRepliesBy(String repliesBy) {
        this.repliesBy = repliesBy;
    }

    public Attachment getAttachment() {
        return attachment;
    }
    public void setAttachment(Attachment attachment) {
        this.attachment = attachment;
    }

    /**
     * @return timestamp of the last comment
     */
    @XmlTransient
    public Instant getUpdated() {
        return updated;
    }

    public void setUpdated(Instant updated) {
        this.updated = updated;
    }

    @XmlTransient
    public boolean isUnread() {
        return unread;
    }

    public void setUnread(boolean unread) {
        this.unread = unread;
    }


    @XmlTransient
    public Set<Reaction> getReactions() {
        return reactions;
    }

    public void setReactions(Set<Reaction> reactions) {
        this.reactions = reactions;
    }

    @Override
    public int hashCode() {
        return Objects.hash(mid, rid);
    }

    public boolean isService() {
        return service;
    }

    public void setService(boolean service) {
        this.service = service;
    }

    public Set<User> getRecommendations() {
        return recommendations;
    }

    public void setRecommendations(Set<User> recommendations) {
        this.recommendations = recommendations;
    }

    /**
     * @return timestamp of the last edit
     */
    @XmlTransient
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
    @JsonProperty("updated_at")
    public Instant getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(Instant updatedAt) {
        this.updatedAt = updatedAt;
    }

    public URI getReplyUri() {
        return replyUri;
    }

    public void setReplyUri(URI replyUri) {
        this.replyUri = replyUri;
    }

    @XmlAttribute(name = "html")
    public boolean isHtml() {
        return html;
    }

    public void setHtml(boolean html) {
        this.html = html;
    }

    @XmlTransient
    public URI getReplyToUri() {
        return replyToUri;
    }

    public void setReplyToUri(URI replyToUri) {
        this.replyToUri = replyToUri;
    }

    public List<Entity> getEntities() {
        return entities;
    }

    public void setEntities(List<Entity> entities) {
        this.entities = entities;
    }
}