/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2003-2008 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.cloud;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.Collection;
import java.util.Date;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;

import javax.annotation.CheckForNull;

import edu.umd.cs.findbugs.BugCollection;
import edu.umd.cs.findbugs.BugDesignation;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.IGuiCallback;
import edu.umd.cs.findbugs.SystemProperties;

/**
 * An interface for describing how a bug collection interacts with the FindBugs
 * Cloud.
 *
 * Each Cloud instance is associated with a BugCollection.
 */
public interface Cloud {

    CloudPlugin getPlugin();

    String getCloudName();

    BugCollection getBugCollection();

    IGuiCallback getGuiCallback();

    /**
     * Get a status message for the cloud; information about any errors, and
     * information about database synchronization
     */
    String getStatusMsg();

    public void printCloudSummary(PrintWriter w, Iterable<BugInstance> bugs, String[] packagePrefixes);

    public void addListener(CloudListener listener);

    public void removeListener(CloudListener listener);

    public void addStatusListener(CloudStatusListener cloudStatusListener);

    public void removeStatusListener(CloudStatusListener cloudStatusListener);

    // ========================== initialization
    // ====================================

    /**
     * Do we have the configuration information needed to try initializing the
     * cloud; calling this method should have no side effects and not display
     * any dialogs or make any network connections.
     *
     * @return true if we have the needed information
     */
    public boolean availableForInitialization();

    /**
     * Attempt to initialize the cloud
     *
     * @return true if successful
     */
    public boolean initialize() throws IOException;

    /** Return true if the cloud has been successfully initialized */
    public boolean isInitialized();
    /**
     * Waits until all new issues have been uploaded
     */
    public void waitUntilNewIssuesUploaded();

    /**
     * Waits until all data about this bug collection has been received from the
     * cloud.
     */
    public void waitUntilIssueDataDownloaded();

    /**
     * Returns true if communication has already been initiated (and perhaps completed).
     * 
     */
    public boolean communicationInitiated();
    /**
     * Called after the bugs in the bug collection are loaded; bugs should not
     * be synchronized before this method is called
     */
    public void bugsPopulated();

    /**
     * Initiate communication with the cloud. Clouds can implement lazy
     * communication, where they don't initiate communication with the cloud
     * until a request for cloud data is seen, or a call is made to
     * {@link #waitUntilIssueDataDownloaded()}. A call to this method forces
     * eager initiation of communication.
     */
    public void initiateCommunication();

    /** Shutdown the cloud, note termination of session, close connections */
    public void shutdown();

    // ================ signin / signout =================

    String getUser();

    SigninState getSigninState();

    /**
     * Whether the cloud should save login information, session ID's, etc. If
     * disabled, the user will need to re-authenticate each session.
     */
    void setSaveSignInInformation(boolean save);

    boolean isSavingSignInInformationEnabled();

    void signIn() throws IOException;

    void signOut();

    // ================================= misc settings
    // =============================

    /** Get voting mode */
    Mode getMode();

    /** Set voting mode */
    void setMode(Mode m);

    /** Does the cloud support source lines (e.g., to FishEye) */
    boolean supportsSourceLinks();

    /** Supports links to a bug database */
    boolean supportsBugLinks();

    /** Supports textual summaries about the status of a bug */
    boolean supportsCloudReports();

    /** Supports allowing users to claim a bug */
    boolean supportsClaims();

    boolean supportsCloudSummaries();

    /**
     * Get a list of names of FB projects that the given class
     * "may be a part of." Used for filing bugs.
     */
    Collection<String> getProjects(String className);

    // ===================================== bug instances
    // =============================

    /**
     * returns whether the bug is stored remotely or not. for bug collection
     * storage, always returns true
     */
    boolean isInCloud(BugInstance b);

    boolean isOnlineCloud();

    /** has the user said they will fix this bug */
    boolean getIWillFix(BugInstance b);

    /** Tool tip text for "view source" button */
    String getSourceLinkToolTip(@CheckForNull BugInstance b);

    /** URL to view the source for a bug instance */
    URL getSourceLink(BugInstance b);

    /** get the bug filing status for a bug instance */
    BugFilingStatus getBugLinkStatus(BugInstance b);

    /**
     * A textual description of the bug status (e.g., FIX_LATER, ASSIGNED,
     * OBSOLETE, WILL_NOT_FIX)
     */
    String getBugStatus(BugInstance b);

    /** has the issue been marked "will not be fixed" in a bug tracker */
    boolean getWillNotBeFixed(BugInstance b);

    /** does the issue have an unassigned issue in the bug tracker */
    boolean getBugIsUnassigned(BugInstance b);

    /** Get link for bug, either to file one or to view it */
    URL getBugLink(BugInstance b);

    String getBugLinkType(BugInstance instance);

    URL fileBug(BugInstance b);

    void setBugLinkOnCloudAndStoreIssueDetails(BugInstance b, String viewUrl, String linkType) throws IOException,
            SignInCancelledException;

    /** Updates the local cache of bug reporting status. Does not modify server code. */
    void updateBugStatusCache(BugInstance b, String status);

    /**
     * Note that we've initiated or completed a request to file a bug;
     *
     * @param b
     *            bug against which bug was filed
     * @param bugLink
     *            if we have any information about the result of filing the bug,
     *            it should go here
     */
    void bugFiled(BugInstance b, @CheckForNull Object bugLink);

    String getCloudReport(BugInstance b);

    String getCloudReportWithoutMe(BugInstance b);

    /** Get the user who has claimed a bug; null if no one has */
    @CheckForNull
    String claimedBy(BugInstance b);

    /**
     * Claim the bug
     *
     * @return true if no one else has already done so
     */
    boolean claim(BugInstance b);

    /** Return the time the user last changed their evaluation of this bug */
    long getUserTimestamp(BugInstance b);

    Date getUserDate(BugInstance b);

    /** Get the most recent BugDesignation from the current user */
    BugDesignation getPrimaryDesignation(BugInstance b);

    /** Get the user's designation for the bug */
    UserDesignation getUserDesignation(BugInstance b);

    /** Get free text evaluation of the bug */
    String getUserEvaluation(BugInstance b);

    double getClassificationScore(BugInstance b);

    double getClassificationVariance(BugInstance b);

    double getClassificationDisagreement(BugInstance b);

    double getPortionObsoleteClassifications(BugInstance b);

    int getNumberReviewers(BugInstance b);

    Set<String> getReviewers(BugInstance b);

    long getFirstSeen(BugInstance b);

    void addDateSeen(BugInstance b, long when);

    /**
     * @return {@link UserDesignation#UNCLASSIFIED} if no consensus has been reached
     */
    UserDesignation getConsensusDesignation(BugInstance b);

    boolean overallClassificationIsNotAProblem(BugInstance b);

    // =========================== mutators ===========================

    /**
     * Is this bug one that gets persisted to the cloud? We may decide that we
     * don't persist low confidence issues to the database to avoid overloading
     * it
     */
    boolean canStoreUserAnnotation(BugInstance bugInstance);

    /**
     * Update user designation and evaluation from information in bug instance
     * and push to database
     */
    void storeUserAnnotation(BugInstance bugInstance);

    // ========================= statistics ===========================

    interface CloudListener {
        void issueUpdated(BugInstance bug);

        void statusUpdated();

        void taskStarted(CloudTask task);
    }

    interface CloudStatusListener {
        void handleIssueDataDownloadedEvent();

        void handleStateChange(SigninState oldState, SigninState state);
    }

    interface CloudTask {
        String getName();

        String getStatusLine();

        double getPercentCompleted();

        void addListener(CloudTaskListener listener);

        void removeListener(CloudTaskListener listener);

        boolean isFinished();

        void setUseDefaultListener(boolean enabled);
    }

    interface CloudTaskListener {
        void taskStatusUpdated(String statusLine, double percentCompleted);

        void taskFinished();

        void taskFailed(String message);
    }

    enum SigninState {
        NO_SIGNIN_REQUIRED, UNAUTHENTICATED, SIGNING_IN, SIGNED_IN, SIGNIN_FAILED, SIGNIN_DECLINED, SIGNED_OUT, DISCONNECTED;
        
        
        /** Can download issues without asking to sign in */
        public boolean canDownload() {
            switch (this) {
            case NO_SIGNIN_REQUIRED:
            case SIGNING_IN:
            case SIGNED_IN:
            case UNAUTHENTICATED:
                return true;
            default:
                return false;
            }
            
        }
        
        /** Can upload issues without asking to sign in */
        public boolean canUpload() {
            switch (this) {
            case NO_SIGNIN_REQUIRED:
            case SIGNING_IN:
            case SIGNED_IN:
                return true;
            default:
                return false;
            }
            
        }
        /** Should ask to sign in if new issues to upload found */
        public boolean shouldAskToSignIn() {
            switch (this) {
            case UNAUTHENTICATED:
            case SIGNED_OUT:
            case SIGNIN_FAILED:
                return true;
            default:
                return false;
            }
        }

        /** Could ask to sign in if new issues to upload found */
        public boolean couldSignIn() {
            switch (this) {
            case UNAUTHENTICATED:
            case DISCONNECTED:
            case SIGNED_OUT:
            case SIGNIN_FAILED:
            case SIGNIN_DECLINED:
                return true;
            default:
                return false;
            }
        }
        static final ResourceBundle names = ResourceBundle.getBundle(Cloud.class.getName(), Locale.getDefault());

        
        @Override
        public String toString() {
            try {
                return names.getString(this.name()).trim();
            } catch (MissingResourceException e) {
                return this.name();
            }
        }
    }

    enum UserDesignation {
        UNCLASSIFIED, NEEDS_STUDY, BAD_ANALYSIS, NOT_A_BUG, MOSTLY_HARMLESS, SHOULD_FIX, MUST_FIX, I_WILL_FIX, OBSOLETE_CODE;

        public int score() {
            switch (this) {

            case BAD_ANALYSIS:
                return -3;
            case NOT_A_BUG:
            case OBSOLETE_CODE:
                return -2;
            case MOSTLY_HARMLESS:
                return -1;
            case SHOULD_FIX:
                return 1;
            case MUST_FIX:
            case I_WILL_FIX:
                return 2;
            default:
                return 0;
            }
        }

        /**
         * @return
         */
        public boolean nonVoting() {
            return this == UserDesignation.OBSOLETE_CODE || this == UserDesignation.NEEDS_STUDY
                    || this == UserDesignation.UNCLASSIFIED;
        }
        
        public boolean notAProblem() {
            return this.score() < 0;  
        }
        public boolean shouldFix() {
            return this.score() > 0;  
        }
    }

    enum Mode {
        COMMUNAL, VOTING, SECRET
    }

    enum BugFilingStatus {
        /** No bug yet filed */
        FILE_BUG(SystemProperties.getProperty("findbugs.filebug.label", "File bug")) {
            @Override
            public boolean bugIsFiled() {
                return false;
            }
        },
        /** URL was sent to browser, but request has expired */
        FILE_AGAIN("File again"),

        /** Sent a URL to a browser to file a bug, no information yet */
        BUG_PENDING("Bug pending") {
            @Override
            public boolean linkEnabled() {
                return false;
            }
        },
        /** synchronized bug instance with bug database */
        VIEW_BUG(SystemProperties.getProperty("findbugs.viewbug.label", "View bug")),

        /* Not applicable, bug linking not supported */
        NA("") {
            @Override
            public boolean linkEnabled() {
                return false;
            }

            @Override
            public boolean bugIsFiled() {
                return false;
            }
        };

        final String displayName;

        public boolean bugIsFiled() {
            return true;
        }

        public boolean linkEnabled() {
            return true;
        }

        BugFilingStatus(String name) {
            this.displayName = name;
        }

        @Override
        public String toString() {
            return displayName;
        }

    }
}
