package org.jivesoftware.smack.packet;

/**
 * A privacy item acts a rule that when matched defines if a packet should be blocked or not.
 * <p/>
 * Privacy Items can handle different kind of blocking communications based on JID, group,
 * subscription type or globally by:<ul>
 * <li>Allowing or blocking messages.
 * <li>Allowing or blocking inbound presence notifications.
 * <li>Allowing or blocking outbound presence notifications.
 * <li>Allowing or blocking IQ stanzas.
 * <li>Allowing or blocking all communications.
 * </ul>
 *
 * @author Francisco Vives
 */
public class PrivacyItem {
    /**
     * allow is the action associated with the item, it can allow or deny the communication.
     */
    private boolean allow;
    /**
     * order is a non-negative integer that is unique among all items in the list.
     */
    private int order;
    /**
     * rule hold the kind of communication ([jid|group|subscription]) it will allow or block and
     * identifier to apply the action.
     * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
     * If the type is "group", then the 'value' attribute SHOULD contain the name of a group
     * in the user's roster.
     * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
     * "from", or "none".
     */
    private PrivacyRule rule;

    /**
     * blocks incoming IQ stanzas.
     */
    private boolean filterIQ = false;
    /**
     * filterMessage blocks incoming message stanzas.
     */
    private boolean filterMessage = false;
    /**
     * blocks incoming presence notifications.
     */
    private boolean filterPresence_in = false;
    /**
     * blocks outgoing presence notifications.
     */
    private boolean filterPresence_out = false;

    /**
     * Creates a new privacy item.
     *
     * @param type the type.
     */
    public PrivacyItem(String type, boolean allow, int order) {
        this.setRule(PrivacyRule.fromString(type));
        this.setAllow(allow);
        this.setOrder(order);
    }

    /**
     * Returns the action associated with the item, it MUST be filled and will allow or deny
     * the communication.
     *
     * @return the allow communication status.
     */
    public boolean isAllow() {
        return allow;
    }

    /**
     * Sets the action associated with the item, it can allow or deny the communication.
     *
     * @param allow indicates if the receiver allow or deny the communication.
     */
    private void setAllow(boolean allow) {
        this.allow = allow;
    }


    /**
     * Returns whether the receiver allow or deny incoming IQ stanzas or not.
     *
     * @return the iq filtering status.
     */
    public boolean isFilterIQ() {
        return filterIQ;
    }


    /**
     * Sets whether the receiver allows or denies incoming IQ stanzas or not.
     *
     * @param filterIQ indicates if the receiver allows or denies incoming IQ stanzas.
     */
    public void setFilterIQ(boolean filterIQ) {
        this.filterIQ = filterIQ;
    }


    /**
     * Returns whether the receiver allows or denies incoming messages or not.
     *
     * @return the message filtering status.
     */
    public boolean isFilterMessage() {
        return filterMessage;
    }


    /**
     * Sets wheather the receiver allows or denies incoming messages or not.
     *
     * @param filterMessage indicates if the receiver allows or denies incoming messages or not.
     */
    public void setFilterMessage(boolean filterMessage) {
        this.filterMessage = filterMessage;
    }


    /**
     * Returns whether the receiver allows or denies incoming presence or not.
     *
     * @return the iq filtering incoming presence status.
     */
    public boolean isFilterPresence_in() {
        return filterPresence_in;
    }


    /**
     * Sets whether the receiver allows or denies incoming presence or not.
     *
     * @param filterPresence_in indicates if the receiver allows or denies filtering incoming presence.
     */
    public void setFilterPresence_in(boolean filterPresence_in) {
        this.filterPresence_in = filterPresence_in;
    }


    /**
     * Returns whether the receiver allows or denies incoming presence or not.
     *
     * @return the iq filtering incoming presence status.
     */
    public boolean isFilterPresence_out() {
        return filterPresence_out;
    }


    /**
     * Sets whether the receiver allows or denies outgoing presence or not.
     *
     * @param filterPresence_out indicates if the receiver allows or denies filtering outgoing presence
     */
    public void setFilterPresence_out(boolean filterPresence_out) {
        this.filterPresence_out = filterPresence_out;
    }


    /**
     * Returns the order where the receiver is processed. List items are processed in
     * ascending order.
     * <p/>
     * The order MUST be filled and its value MUST be a non-negative integer
     * that is unique among all items in the list.
     *
     * @return the order number.
     */
    public int getOrder() {
        return order;
    }


    /**
     * Sets the order where the receiver is processed.
     * <p/>
     * The order MUST be filled and its value MUST be a non-negative integer
     * that is unique among all items in the list.
     *
     * @param order indicates the order in the list.
     */
    public void setOrder(int order) {
        this.order = order;
    }

    /**
     * Sets the element identifier to apply the action.
     * <p/>
     * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
     * If the type is "group", then the 'value' attribute SHOULD contain the name of a group
     * in the user's roster.
     * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
     * "from", or "none".
     *
     * @param value is the identifier to apply the action.
     */
    public void setValue(String value) {
        if (!(this.getRule() == null && value == null)) {
            this.getRule().setValue(value);
        }
    }

    /**
     * Returns the type hold the kind of communication it will allow or block.
     * It MUST be filled with one of these values: jid, group or subscription.
     *
     * @return the type of communication it represent.
     */
    public Type getType() {
        if (this.getRule() == null) {
            return null;
        } else {
            return this.getRule().getType();
        }
    }

    /**
     * Returns the element identifier to apply the action.
     * <p/>
     * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
     * If the type is "group", then the 'value' attribute SHOULD contain the name of a group
     * in the user's roster.
     * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
     * "from", or "none".
     *
     * @return the identifier to apply the action.
     */
    public String getValue() {
        if (this.getRule() == null) {
            return null;
        } else {
            return this.getRule().getValue();
        }
    }


    /**
     * Returns whether the receiver allows or denies every kind of communication.
     * <p/>
     * When filterIQ, filterMessage, filterPresence_in and filterPresence_out are not set
     * the receiver will block all communications.
     *
     * @return the all communications status.
     */
    public boolean isFilterEverything() {
        return !(this.isFilterIQ() || this.isFilterMessage() || this.isFilterPresence_in()
                || this.isFilterPresence_out());
    }


    private PrivacyRule getRule() {
        return rule;
    }

    private void setRule(PrivacyRule rule) {
        this.rule = rule;
    }

    /**
     * Answer an xml representation of the receiver according to the RFC 3921.
     *
     * @return the text xml representation.
     */
    public String toXML() {
        StringBuilder buf = new StringBuilder();
        buf.append("<item");
        if (this.isAllow()) {
            buf.append(" action=\"allow\"");
        } else {
            buf.append(" action=\"deny\"");
        }
        buf.append(" order=\"").append(getOrder()).append("\"");
        if (getType() != null) {
            buf.append(" type=\"").append(getType()).append("\"");
        }
        if (getValue() != null) {
            buf.append(" value=\"").append(getValue()).append("\"");
        }
        if (isFilterEverything()) {
            buf.append("/>");
        } else {
            buf.append(">");
            if (this.isFilterIQ()) {
                buf.append("<iq/>");
            }
            if (this.isFilterMessage()) {
                buf.append("<message/>");
            }
            if (this.isFilterPresence_in()) {
                buf.append("<presence-in/>");
            }
            if (this.isFilterPresence_out()) {
                buf.append("<presence-out/>");
            }
            buf.append("</item>");
        }
        return buf.toString();
    }


    /**
     * Privacy Rule represents the kind of action to apply.
     * It holds the kind of communication ([jid|group|subscription]) it will allow or block and
     * identifier to apply the action.
     */

    public static class PrivacyRule {
        /**
         * Type defines if the rule is based on JIDs, roster groups or presence subscription types.
         * Available values are: [jid|group|subscription]
         */
        private Type type;
        /**
         * The value hold the element identifier to apply the action.
         * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
         * If the type is "group", then the 'value' attribute SHOULD contain the name of a group
         * in the user's roster.
         * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
         * "from", or "none".
         */
        private String value;

        /**
         * If the type is "subscription", then the 'value' attribute MUST be one of "both",
         * "to", "from", or "none"
         */
        public static final String SUBSCRIPTION_BOTH = "both";
        public static final String SUBSCRIPTION_TO = "to";
        public static final String SUBSCRIPTION_FROM = "from";
        public static final String SUBSCRIPTION_NONE = "none";

        /**
         * Returns the type constant associated with the String value.
         */
        protected static PrivacyRule fromString(String value) {
            if (value == null) {
                return null;
            }
            PrivacyRule rule = new PrivacyRule();
            rule.setType(Type.valueOf(value.toLowerCase()));
            return rule;
        }

        /**
         * Returns the type hold the kind of communication it will allow or block.
         * It MUST be filled with one of these values: jid, group or subscription.
         *
         * @return the type of communication it represent.
         */
        public Type getType() {
            return type;
        }

        /**
         * Sets the action associated with the item, it can allow or deny the communication.
         *
         * @param type indicates if the receiver allows or denies the communication.
         */
        private void setType(Type type) {
            this.type = type;
        }

        /**
         * Returns the element identifier to apply the action.
         * <p/>
         * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
         * If the type is "group", then the 'value' attribute SHOULD contain the name of a group
         * in the user's roster.
         * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
         * "from", or "none".
         *
         * @return the identifier to apply the action.
         */
        public String getValue() {
            return value;
        }

        /**
         * Sets the element identifier to apply the action.
         * <p/>
         * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
         * If the type is "group", then the 'value' attribute SHOULD contain the name of a group
         * in the user's roster.
         * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
         * "from", or "none".
         *
         * @param value is the identifier to apply the action.
         */
        protected void setValue(String value) {
            if (this.isSuscription()) {
                setSuscriptionValue(value);
            } else {
                this.value = value;
            }
        }

        /**
         * Sets the element identifier to apply the action.
         * <p/>
         * The 'value' attribute MUST be one of "both", "to", "from", or "none".
         *
         * @param value is the identifier to apply the action.
         */
        private void setSuscriptionValue(String value) {
            String setValue;
            if (value == null) {
                // Do nothing
            }
            if (SUBSCRIPTION_BOTH.equalsIgnoreCase(value)) {
                setValue = SUBSCRIPTION_BOTH;
            } else if (SUBSCRIPTION_TO.equalsIgnoreCase(value)) {
                setValue = SUBSCRIPTION_TO;
            } else if (SUBSCRIPTION_FROM.equalsIgnoreCase(value)) {
                setValue = SUBSCRIPTION_FROM;
            } else if (SUBSCRIPTION_NONE.equalsIgnoreCase(value)) {
                setValue = SUBSCRIPTION_NONE;
            }
            // Default to available.
            else {
                setValue = null;
            }
            this.value = setValue;
        }

        /**
         * Returns if the receiver represents a subscription rule.
         *
         * @return if the receiver represents a subscription rule.
         */
        public boolean isSuscription() {
            return this.getType() == Type.subscription;
        }
    }

    /**
     * Type defines if the rule is based on JIDs, roster groups or presence subscription types.
     */
    public static enum Type {
        /**
         * JID being analyzed should belong to a roster group of the list's owner.
         */
        group,
        /**
         * JID being analyzed should have a resource match, domain match or bare JID match.
         */
        jid,
        /**
         * JID being analyzed should belong to a contact present in the owner's roster with
         * the specified subscription status.
         */
        subscription
    }
}
