package org.jivesoftware.xmpp.workgroup.spi;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.xmpp.workgroup.RequestQueue;
import org.jivesoftware.xmpp.workgroup.Workgroup;
import org.jivesoftware.xmpp.workgroup.WorkgroupManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;


/**
 * <code>ChatHistoryImpl</code> class is used for statistical reporting of
 * the Live Assistant Server.
 *
 * @author Derek DeMoro
 */
public final class ChatHistoryUtils {

    private static final Logger Log = LoggerFactory.getLogger(ChatHistoryUtils.class);

    private static final String ALL_SESSION_TIMES =
            "SELECT startTime, endTime FROM fpSession";
    private static final String ACCEPTED_CHATS_COUNT =
            "SELECT count(*) FROM fpSession WHERE state=2 AND workgroupID=? " +
            "AND startTime >= ? AND endTime <= ?";
    private static final String CHAT_TIMES_FOR_WORKGROUPS =
            "SELECT startTime, endTime FROM fpSession WHERE workgroupID=?";
    private static final String WORKGROUP_REQUEST_COUNT =
            "SELECT count(*) FROM fpSession WHERE workgroupID=? AND startTime >= ? " +
            "AND endTime <= ?";
    private static final String WORKGROUP_STATE_REQUEST_COUNT =
            "SELECT count(*) FROM fpSession where workgroupID=? AND state=? AND startTime >= ? " +
            "AND endTime <= ?";
    private static final String ALL_SESSIONS =
            "SELECT sessionID FROM fpSession";
    private static final String ALL_CHATS_COUNT =
            "SELECT count(*) FROM fpSession WHERE state=2";
    private static final String ALL_REQUESTS_COUNT =
            "SELECT count(*) FROM fpSession";
    private static final String TOTAL_WAIT_TIME =
            "SELECT sum(queueWaitTime) FROM fpSession";
    private static final String WORKGROUP_WAIT_TIME =
            "SELECT sum(queueWaitTime) FROM fpSession WHERE workgroupID=? AND startTime >= ? " +
            "AND endTime <= ?";

    /**
     * Creates a new ChatHistoryImpl object.
     */
    private ChatHistoryUtils() {
    }


    /**
     * Returns the average chat time for all workgroups in the server.
     *
     * @return the average time of all chats.
     */
    public static long getAverageChatLengthForServer() {
        int numberOfChats = getTotalChatsInSystem();
        long chatLength = getTotalTimeForAllChatsInServer();

        if(numberOfChats == 0 ) {
            return 0;
        }
        return chatLength / numberOfChats;
    }

    /**
     * Returns the total amount of time for all the chats in all workgroups.
     *
     * @return the total length of all chats in the system.
     */
    public static long getTotalTimeForAllChatsInServer() {
        int totalWorkgroupChatTime = 0;
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(ALL_SESSION_TIMES);

            rs = pstmt.executeQuery();
            while (rs.next()) {
                try {
                    String startTimeString = rs.getString(1);
                    String endTimeString = rs.getString(2);

                    if ((startTimeString != null) && (startTimeString.trim().length() > 0) &&
                            (endTimeString != null) && (endTimeString.trim().length() > 0)) {
                        long startLong = Long.parseLong(startTimeString);
                        long endLong = Long.parseLong(endTimeString);

                        totalWorkgroupChatTime += endLong - startLong;
                    }
                }
                catch (SQLException e) {
                    Log.error(e.getMessage(), e);
                }
                catch (NumberFormatException e) {
                    Log.error(e.getMessage(), e);
                }
            }
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }

        return totalWorkgroupChatTime;
    }

    /**
     * Returns the number of chat requests that were accepted.
     *
     * @param workgroupName the name of the workgroup where the request(s) were made.
     * @param startDate the start date.
     * @param endDate the end date.
     * @return the number of chats requests accepted by the workgroup.
     */
    public static int getNumberOfChatsAccepted(String workgroupName, Date startDate, Date endDate) {
        Workgroup workgroup = null;
        try {
            workgroup = WorkgroupManager.getInstance().getWorkgroup(new JID(workgroupName));
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }
        if (workgroup == null) {
            return 0;
        }

        int count = 0;
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(ACCEPTED_CHATS_COUNT);
            pstmt.setLong(1, workgroup.getID());
            pstmt.setString(2, StringUtils.dateToMillis(startDate));
            pstmt.setString(3, StringUtils.dateToMillis(endDate));

            rs = pstmt.executeQuery();
            if (rs.next()) {
                count = rs.getInt(1);
            }
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }

        return count;
    }

    /**
     * Returns the total chat length of an individual workgroup.
     *
     * @param workgroupName the name of the workgroup.
     * @return the total length of all chats in the specified workgroup.
     */
    public static long getTotalChatTimeForWorkgroup(String workgroupName) {
        Workgroup workgroup = null;

        try {
            workgroup = WorkgroupManager.getInstance().getWorkgroup(new JID(workgroupName));
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }

        int totalWorkgroupChatTime = 0;
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(CHAT_TIMES_FOR_WORKGROUPS);
            pstmt.setLong(1, workgroup.getID());
            rs = pstmt.executeQuery();

            while (rs.next()) {
                String startTimeString = rs.getString(1);
                String endTimeString = rs.getString(2);

                if ((startTimeString != null) && (startTimeString.trim().length() > 0) &&
                        (endTimeString != null) && (endTimeString.trim().length() > 0)) {
                    long startLong = Long.parseLong(startTimeString);
                    long endLong = Long.parseLong(endTimeString);

                    totalWorkgroupChatTime += endLong - startLong;
                }
            }
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }

        return totalWorkgroupChatTime;
    }

    /**
     * Returns the number of request made to a workgroup between
     * specified dates.
     *
     * @param workgroupName the workgroup to search
     * @param startDate the time to begin the search from.
     * @param endDate the time to end the search.
     * @return the total number of requests
     */
    public static int getNumberOfRequestsForWorkgroup(String workgroupName, Date startDate, Date endDate) {
        Workgroup workgroup = getWorkgroup(workgroupName);
        if (workgroup == null) {
            return 0;
        }

        int count = 0;
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(WORKGROUP_REQUEST_COUNT);
            pstmt.setLong(1, workgroup.getID());
            pstmt.setString(2, StringUtils.dateToMillis(startDate));
            pstmt.setString(3, StringUtils.dateToMillis(endDate));

            rs = pstmt.executeQuery();
            if (rs.next()) {
                count = rs.getInt(1);
            }
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }

        return count;
    }

    /**
     * Returns the number of canceled requests.
     *
     * @param workgroupName the workgroup to search
     * @param startDate the time to begin the search from.
     * @param endDate the time to end the search.
     * @return the total number of requests
     */
    public static int getNumberOfRequestsCancelledByUser(String workgroupName, Date startDate, Date endDate) {
        Workgroup workgroup = null;
        try {
            workgroup = WorkgroupManager.getInstance().getWorkgroup(new JID(workgroupName));
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }
        if (workgroup == null) {
            return 0;
        }

        int count = 0;
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(WORKGROUP_STATE_REQUEST_COUNT);
            pstmt.setLong(1, workgroup.getID());
            // Set the state the cancelled requests.
            pstmt.setInt(2, 0);
            pstmt.setString(3, StringUtils.dateToMillis(startDate));
            pstmt.setString(4, StringUtils.dateToMillis(endDate));

            rs = pstmt.executeQuery();
            if (rs.next()) {
                count = rs.getInt(1);
            }
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }

        return count;
    }

    /**
     * Returns an iterator of all sessionID's in the system.
     *
     * @return an iterator of sessionID's.
     */
    public static Iterator<String> getSessionIDs() {
        final List<String> sessionList = new ArrayList<String>();
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(ALL_SESSIONS);
            rs = pstmt.executeQuery();
            while (rs.next()) {
                sessionList.add(rs.getString(1));
            }
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }
        return sessionList.iterator();
    }

    /**
     * Returns the number of canceled requests.
     *
     * @param workgroupName the workgroup to search
     * @param startDate the time to begin the search from.
     * @param endDate the time to end the search.
     * @return the total number of requests
     */
    public static int getNumberOfRequestsNeverPickedUp(String workgroupName, Date startDate, Date endDate) {
        Workgroup workgroup = null;
        try {
            workgroup = WorkgroupManager.getInstance().getWorkgroup(new JID(workgroupName));
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }
        if (workgroup == null) {
            return 0;
        }

        int count = 0;
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(WORKGROUP_STATE_REQUEST_COUNT);
            pstmt.setLong(1, workgroup.getID());
            // Set the state the ignored requests.
            pstmt.setInt(2, 1);
            pstmt.setString(3, StringUtils.dateToMillis(startDate));
            pstmt.setString(4, StringUtils.dateToMillis(endDate));

            rs = pstmt.executeQuery();
            if (rs.next()) {
                count = rs.getInt(1);
            }
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }

        return count;
    }

    /**
     * Returns the total number of chats that have occured within a workgroup.
     *
     * @param workgroupName the jid of the workgroup.
     * @return the total number of chats that have occured within a workgroup.
     */
    public static int getNumberOfChatsForWorkgroup(String workgroupName) {
        Workgroup workgroup = null;

        try {
            workgroup = WorkgroupManager.getInstance().getWorkgroup(new JID(workgroupName));
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }

        int count = 0;
        for (RequestQueue requestQueue : workgroup.getRequestQueues()){
            count += requestQueue.getTotalChatCount();
        }

        return count;
    }

    /**
     * Returns the average wait time in the system.
     *
     * @return the average wait time.
     */
    public static String getAverageWaitTimeForServer() {
        int totalRequests = getTotalRequestCountForSystem();
        long totalWaitTime = getTotalWaitTimeForServer();
        if (totalRequests == 0 ) {
            return "0 sec.";
        }

        long averageWaitTime = totalWaitTime / totalRequests;

        return getDateFromLong(averageWaitTime);
    }

    /**
     * Returns the average wait time for a specified workgroup between two dates.
     *
     * @param workgroupName the Live Assistant Workgroup.
     * @param startDate     the startDate.
     * @param endTime       the end date.
     * @return the average wait time for this workgroup.
     */
    public static long getAverageWaitTimeForWorkgroup(String workgroupName, Date startDate, Date endTime) {
        int totalRequests = getNumberOfRequestsForWorkgroup(workgroupName, startDate, endTime);
        long waitTime = getTotalWaitTimeForWorkgroup(workgroupName, startDate, endTime);

        if (totalRequests == 0) {
            return 0;
        }

       return waitTime / totalRequests;
    }

    /**
     * Returns the total number of chats.
     *
     * @return the total number of chats.
     */
    public static int getTotalChatsInSystem() {
        int count = 0;
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(ALL_CHATS_COUNT);
            rs = pstmt.executeQuery();
            rs.next();
            count = rs.getInt(1);
        }
        catch (SQLException e) {
            Log.error(e.getMessage(), e);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }
        return count;
    }

    /**
     * Retruns the total number of requests.
     *
     * @return the total number of requests.
     */
    public static int getTotalRequestCountForSystem() {
        int count = 0;
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(ALL_REQUESTS_COUNT);
            rs = pstmt.executeQuery();
            rs.next();
            count = rs.getInt(1);
        }
        catch (SQLException e) {
            Log.error(e.getMessage(), e);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }
        return count;
    }

    /**
     * Returns the total waitTime for all incoming requests.
     *
     * @return the total wait time.
     */
    public static long getTotalWaitTimeForServer() {
        int totalWaitTime = 0;
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(TOTAL_WAIT_TIME);
            rs = pstmt.executeQuery();
            rs.next();
            totalWaitTime = rs.getInt(1);
        }
        catch (SQLException e) {
            Log.error(e.getMessage(), e);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }
        return totalWaitTime;
    }

    /**
     * Returns the number of canceled requests.
     *
     * @param workgroupName the workgroup to search
     * @param startDate the time to begin the search from.
     * @param endDate the time to end the search.
     * @return the total number of requests
     */
    public static long getTotalWaitTimeForWorkgroup(String workgroupName, Date startDate, Date endDate) {
        Workgroup workgroup = null;
        try {
            workgroup = WorkgroupManager.getInstance().getWorkgroup(new JID(workgroupName));
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }
        if (workgroup == null) {
            return 0;
        }

        int waitTime = 0;
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(WORKGROUP_WAIT_TIME);
            pstmt.setLong(1, workgroup.getID());
            // Set the state the ignored requests.
            pstmt.setInt(2, 1);
            pstmt.setString(2, StringUtils.dateToMillis(startDate));
            pstmt.setString(3, StringUtils.dateToMillis(endDate));

            rs = pstmt.executeQuery();
            rs.next();
            waitTime = rs.getInt(1);
        }
        catch (Exception ex) {
            Log.error(ex.getMessage(), ex);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
        }

        return waitTime;
    }

    /**
     * Returns a formatted string to display minutes and seconds.
     *
     * @param time the number of milliseconds.
     * @return a formatted string.
     */
    public static String getDateFromLong(long time) {
        int aTime;
        int minutes;
        int seconds;
        String displayString;

        if (time > 0) {
            aTime = (int) (time) / 1000;
            minutes = aTime / 60;
            seconds = aTime % 60;

            if (minutes != 0) {
                displayString = minutes + " min, " + seconds + " sec.";
            }
            else {
                displayString = seconds + " seconds";
            }
        }
        else {
            return "0 seconds";
        }

        return displayString;
    }

    /**
     * Returns a <code>Workgroup</code> based on it's full jid.
     * @param workgroupJID the full jid of the workgroup (ex. demo@workgroup.jivesoftware.com)
     * @return the Workgroup
     */
    public static  Workgroup getWorkgroup(String workgroupJID) {
        Workgroup workgroup = null;
        try {
            workgroup = WorkgroupManager.getInstance().getWorkgroup(new JID(workgroupJID));
        }
        catch (UserNotFoundException e) {
            Log.error("Error retrieving Workgroup", e);
        }
        return workgroup;
    }
}
