/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2006, University of Maryland
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs;

import java.io.IOException;
import java.io.Serializable;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

import edu.umd.cs.findbugs.cloud.Cloud.UserDesignation;
import edu.umd.cs.findbugs.util.Util;
import edu.umd.cs.findbugs.xml.XMLAttributeList;
import edu.umd.cs.findbugs.xml.XMLOutput;
import edu.umd.cs.findbugs.xml.XMLWriteable;

/**
 * class to hold the user annotation and user designation for a BugInstance
 */
public class BugDesignation implements XMLWriteable, Serializable, Comparable<BugDesignation> {

    /**
     * The default key for the user designation. Bad things could happen if this
     * key isn't in getUserDesignations()
     */
    public static final String UNCLASSIFIED = UserDesignation.UNCLASSIFIED.name();

    /**
     * user designation -- value should be one of the keys returned by
     * I18N.getInstance().getUserDesignations()
     */
    @Nonnull
    private String designation = UNCLASSIFIED;

    @Override
    public String toString() {
        String result = designation;
        if (user != null)
            result += " by " + user;
        if (annotationText != null && annotationText.length() > 0)
            result += " : " + annotationText;
        return result;
    }

    private boolean dirty;

    public boolean isDirty() {
        return dirty;
    }

    public void cleanDirty() {
        setDirty(false);
    }
    public void setDirty(boolean dirty) {
        if (this.dirty == dirty)
            return;
        this.dirty = dirty;
//        if (dirty) {
//            System.out.println("Setting dirty bit");
//            new RuntimeException("Setting dirty bit").printStackTrace(System.out);
//        } else {
//            System.out.println("Clearing dirty bit");
//            new RuntimeException("Clearing dirty bit").printStackTrace(System.out);
//        }
    }

    private @javax.annotation.CheckForNull
    String user;

    public BugDesignation() {
    }

    /**
     * @param designation
     * @param timestamp
     * @param annotationText
     * @param user
     */
    public BugDesignation(String designation, long timestamp, String annotationText, String user) {
        this.designation = designation;
        this.timestamp = timestamp;
        this.annotationText = annotationText;
        this.user = user;
    }

    public BugDesignation(BugDesignation that) {
        this(that.designation, that.timestamp, that.annotationText, that.user);
    }

    private long timestamp = System.currentTimeMillis();

    /** free text from the user */
    // TODO: make this @CheckForNull
    private String annotationText;

    /**
     * return the user designation E.g., "MOSTLY_HARMLESS", "CRITICAL",
     * "NOT_A_BUG", etc.
     *
     * Note that this is the key, suitable for writing to XML, but not for
     * showing to the user.
     *
     * @see I18N#getUserDesignation(String key)
     */
    @Nonnull
    public String getDesignationKey() {
        return designation;
    }

    public boolean hasDesignationKey() {
        return designation.length() > 0 && !designation.equals(UNCLASSIFIED);
    }
    /**
     * set the user designation E.g., "MOSTLY_HARMLESS", "CRITICAL",
     * "NOT_A_BUG", etc.
     *
     * If the argument is null, it will be treated as UNCLASSIFIED.
     *
     * Note that this is the key, suitable for writing to XML, but not what the
     * user sees. Strange things could happen if designationKey is not one of
     * the keys returned by I18N.instance().getUserDesignations().
     *
     * @see I18N#getUserDesignationKeys()
     */
    public void setDesignationKey(String designationKey) {
        if ("".equals(designationKey)) {
            assert false;
            designationKey = null;
        }
        if (designation.equals(designationKey))
            return;
        setDirty(true);
        timestamp = System.currentTimeMillis();
        designation = (designationKey != null ? designationKey : UNCLASSIFIED);
    }

    @CheckForNull
    public String getUser() {
        return user;
    }

    public void setUser(String u) {
        user = u;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long ts) {
        if (timestamp != ts) {
            timestamp = ts;
            if (false && !hasAnnotationText() && !hasDesignationKey())
                new RuntimeException("Setting timestamp on bug designation without annotation or designation").printStackTrace(System.out);
            setDirty(true);
        }
    }

    @CheckForNull
    public String getAnnotationText() {
        return annotationText;
    }

    public boolean hasAnnotationText() {
        return annotationText != null && annotationText.length() > 0;
    }

    @Nonnull
    public String getNonnullAnnotationText() {
        if (annotationText == null)
            return "";
        return annotationText;
    }

    public void setAnnotationText(String s) {
        if (s.equals(annotationText))
            return;
        setDirty(true);
        annotationText = s;
        timestamp = System.currentTimeMillis();
    }

    public void writeXML(XMLOutput xmlOutput) throws IOException {
        XMLAttributeList attributeList = new XMLAttributeList();
        // all three of these xml attributes are optional
        if (hasDesignationKey())
            attributeList.addAttribute("designation", designation);
        if (user != null && !"".equals(user))
            attributeList.addAttribute("user", user);
        if (dirty)
            attributeList.addAttribute("needsSync", "true");
        if (timestamp > 0)
            attributeList.addAttribute("timestamp", String.valueOf(timestamp));

        if ((annotationText != null && !"".equals(annotationText))) {
            xmlOutput.openTag("UserAnnotation", attributeList);
            xmlOutput.writeCDATA(annotationText);
            xmlOutput.closeTag("UserAnnotation");
        } else {
            xmlOutput.openCloseTag("UserAnnotation", attributeList);
        }
    }

    /**
     * replace unset fields of this user designation with values set in the
     * other
     */
    public void merge(@CheckForNull BugDesignation other) {
        if (other == null)
            return;
        boolean changed = false;
        if ((annotationText == null || annotationText.length() == 0) && other.annotationText != null
                && other.annotationText.length() > 0) {
            annotationText = other.annotationText;
            setDirty(true);
            changed = true;
        }
        if (!hasDesignationKey() && other.hasDesignationKey()) {
            designation = other.designation;
            setDirty(true);
            changed = true;
        }
        if (!changed)
            return; // if no changes don't even try to copy user or timestamp

        if ((user == null || user.length() == 0) && other.user != null && other.user.length() > 0) {
            user = other.user;
        }
        if (timestamp == 0 && other.timestamp != 0) {
            timestamp = other.timestamp;
        }
    }

    @Override
    public int hashCode() {
        int hash = (int) this.timestamp;
        if (user != null)
            hash += user.hashCode();
        hash += designation.hashCode();
        if (annotationText != null)
            hash += annotationText.hashCode();
        return hash;
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof BugDesignation))
            return false;
        return this.compareTo((BugDesignation) o) == 0;
    }

    public int compareTo(BugDesignation o) {
        if (this == o)
            return 0;
        int result = Util.compare(o.timestamp, this.timestamp);
        if (result != 0)
            return result;

        result = Util.nullSafeCompareTo(this.user, o.user);
        if (result != 0)
            return result;

        result = this.designation.compareTo(o.designation);
        if (result != 0)
            return result;
        result = Util.nullSafeCompareTo(this.annotationText, o.annotationText);
        if (result != 0)
            return result;

        return 0;

    }

}
