/*
 * Copyright (c) 2014, Peter Thorson. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the WebSocket++ Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#ifndef WEBSOCKETPP_ERROR_HPP
#define WEBSOCKETPP_ERROR_HPP

#include <exception>
#include <string>
#include <utility>

#include <websocketpp/common/cpp11.hpp>
#include <websocketpp/common/system_error.hpp>

namespace websocketpp {

/// Combination error code / string type for returning two values
typedef std::pair<lib::error_code,std::string> err_str_pair;

/// Library level error codes
namespace error {
enum value {
    /// Catch-all library error
    general = 1,

    /// send attempted when endpoint write queue was full
    send_queue_full,

    /// Attempted an operation using a payload that was improperly formatted
    /// ex: invalid UTF8 encoding on a text message.
    payload_violation,

    /// Attempted to open a secure connection with an insecure endpoint
    endpoint_not_secure,

    /// Attempted an operation that required an endpoint that is no longer
    /// available. This is usually because the endpoint went out of scope
    /// before a connection that it created.
    endpoint_unavailable,

    /// An invalid uri was supplied
    invalid_uri,

    /// The endpoint is out of outgoing message buffers
    no_outgoing_buffers,

    /// The endpoint is out of incoming message buffers
    no_incoming_buffers,

    /// The connection was in the wrong state for this operation
    invalid_state,

    /// Unable to parse close code
    bad_close_code,

    /// Close code is in a reserved range
    reserved_close_code,

    /// Close code is invalid
    invalid_close_code,

    /// Invalid UTF-8
    invalid_utf8,

    /// Invalid subprotocol
    invalid_subprotocol,

    /// An operation was attempted on a connection that did not exist or was
    /// already deleted.
    bad_connection,

    /// Unit testing utility error code
    test,

    /// Connection creation attempted failed
    con_creation_failed,

    /// Selected subprotocol was not requested by the client
    unrequested_subprotocol,

    /// Attempted to use a client specific feature on a server endpoint
    client_only,

    /// Attempted to use a server specific feature on a client endpoint
    server_only,

    /// HTTP connection ended
    http_connection_ended,

    /// WebSocket opening handshake timed out
    open_handshake_timeout,

    /// WebSocket close handshake timed out
    close_handshake_timeout,

    /// Invalid port in URI
    invalid_port,

    /// An async accept operation failed because the underlying transport has been
    /// requested to not listen for new connections anymore.
    async_accept_not_listening,

    /// The requested operation was canceled
    operation_canceled,

    /// Connection rejected
    rejected,

    /// Upgrade Required. This happens if an HTTP request is made to a
    /// WebSocket++ server that doesn't implement an http handler
    upgrade_required,

    /// Invalid WebSocket protocol version
    invalid_version,

    /// Unsupported WebSocket protocol version
    unsupported_version,

    /// HTTP parse error
    http_parse_error,
    
    /// Extension negotiation failed
    extension_neg_failed
}; // enum value


class category : public lib::error_category {
public:
    category() {}

    char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ {
        return "websocketpp";
    }

    std::string message(int value) const {
        switch(value) {
            case error::general:
                return "Generic error";
            case error::send_queue_full:
                return "send queue full";
            case error::payload_violation:
                return "payload violation";
            case error::endpoint_not_secure:
                return "endpoint not secure";
            case error::endpoint_unavailable:
                return "endpoint not available";
            case error::invalid_uri:
                return "invalid uri";
            case error::no_outgoing_buffers:
                return "no outgoing message buffers";
            case error::no_incoming_buffers:
                return "no incoming message buffers";
            case error::invalid_state:
                return "invalid state";
            case error::bad_close_code:
                return "Unable to extract close code";
            case error::invalid_close_code:
                return "Extracted close code is in an invalid range";
            case error::reserved_close_code:
                return "Extracted close code is in a reserved range";
            case error::invalid_utf8:
                return "Invalid UTF-8";
            case error::invalid_subprotocol:
                return "Invalid subprotocol";
            case error::bad_connection:
                return "Bad Connection";
            case error::test:
                return "Test Error";
            case error::con_creation_failed:
                return "Connection creation attempt failed";
            case error::unrequested_subprotocol:
                return "Selected subprotocol was not requested by the client";
            case error::client_only:
                return "Feature not available on server endpoints";
            case error::server_only:
                return "Feature not available on client endpoints";
            case error::http_connection_ended:
                return "HTTP connection ended";
            case error::open_handshake_timeout:
                return "The opening handshake timed out";
            case error::close_handshake_timeout:
                return "The closing handshake timed out";
            case error::invalid_port:
                return "Invalid URI port";
            case error::async_accept_not_listening:
                return "Async Accept not listening";
            case error::operation_canceled:
                return "Operation canceled";
            case error::rejected:
                return "Connection rejected";
            case error::upgrade_required:
                return "Upgrade required";
            case error::invalid_version:
                return "Invalid version";
            case error::unsupported_version:
                return "Unsupported version";
            case error::http_parse_error:
                return "HTTP parse error";
            case error::extension_neg_failed:
                return "Extension negotiation failed";
            default:
                return "Unknown";
        }
    }
};

inline const lib::error_category& get_category() {
    static category instance;
    return instance;
}

inline lib::error_code make_error_code(error::value e) {
    return lib::error_code(static_cast<int>(e), get_category());
}

} // namespace error
} // namespace websocketpp

_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_
template<> struct is_error_code_enum<websocketpp::error::value>
{
    static bool const value = true;
};
_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_

namespace websocketpp {

class exception : public std::exception {
public:
    exception(std::string const & msg, lib::error_code ec = make_error_code(error::general))
      : m_msg(msg.empty() ? ec.message() : msg), m_code(ec)
    {}

    explicit exception(lib::error_code ec)
      : m_msg(ec.message()), m_code(ec)
    {}

    ~exception() throw() {}

    virtual char const * what() const throw() {
        return m_msg.c_str();
    }

    lib::error_code code() const throw() {
        return m_code;
    }

    const std::string m_msg;
    lib::error_code m_code;
};

} // namespace websocketpp

#endif // WEBSOCKETPP_ERROR_HPP