/*
*   DSFML - SFML Library binding in D language.
*   Copyright (C) 2008 Julien Dagorn (sirjulio13@gmail.com)
*
*   This software is provided 'as-is', without any express or
*   implied warranty. In no event will the authors be held
*   liable for any damages arising from the use of this software.
*
*   Permission is granted to anyone to use this software for any purpose,
*   including commercial applications, and to alter it and redistribute
*   it freely, subject to the following restrictions:
*
*   1.  The origin of this software must not be misrepresented;
*       you must not claim that you wrote the original software.
*       If you use this software in a product, an acknowledgment
*       in the product documentation would be appreciated but
*       is not required.
*
*   2.  Altered source versions must be plainly marked as such,
*       and must not be misrepresented as being the original software.
*
*   3.  This notice may not be removed or altered from any
*       source distribution.
*/

module dsfml.network.http;

import dsfml.system.stringutil;
import dsfml.system.common;

/**
*   HTTP methods enumeration  
*/
enum HttpMethod
{
    GET,  ///< Request in get mode, standard method to retrieve a page
    POST, ///< Request in post mode, usually to send data to a page
    HEAD  ///< Request a page's header only
}

/**
*   HTTP response status code     
*/
enum HttpStatus
{
    // 2xx: success
    OK        = 200, ///< Most common code returned when operation was successful
    CREATED   = 201, ///< The resource has successfully been created
    ACCEPTED  = 202, ///< The request has been accepted, but will be processed later by the server
    NOCONTENT = 204, ///< Sent when the server didn't send any data in return

    // 3xx: redirection
    MULTIPLECHOICES  = 300, ///< The requested page can be accessed from several locations
    MOVEDPERMANENTLY = 301, ///< The requested page has permanently moved to a new location
    MOVEDTEMPORARILY = 302, ///< The requested page has temporarily moved to a new location
    NOTMODIFIED      = 304, ///< For conditionnal requests, means the requested page hasn't changed and doesn't need to be refreshed

    // 4xx: client error
    BADREQUEST   = 400, ///< The server couldn't understand the request (syntax error)
    UNAUTHORIZED = 401, ///< The requested page needs an authentification to be accessed
    FORBIDDEN    = 403, ///< The requested page cannot be accessed at all, even with authentification
    NOTFOUND     = 404, ///< The requested page doesn't exist

    // 5xx: server error
    INTERNALSERVERERROR = 500, ///< The server encountered an unexpected error
    NOTIMPLEMENTED      = 501, ///< The server doesn't implement a requested feature
    BADGATEWAY          = 502, ///< The gateway server has received an error from the source server
    SERVICENOTAVAILABLE = 503, ///< The server is temporarily unavailable (overloaded, in maintenance, ...)

    // 10xx: SFML custom codes
    INVALIDRESPONSE  = 1000, ///< Response is not a valid HTTP one
    CONNECTIONFAILED = 1001  ///< Connection with server failed
}

/**
*   This class provides methods for manipulating the HTTP protocol (described in
*   RFC 1945).
*   It can connect to a website, get files, send requests
*/ 
class Http : DSFMLObject
{
    /**
    *   Wrapper for a http request, which is basically :
    *       - a header with a method, a target URI and a set of field/value pairs   
    *       - an optional body (for POST requests)          
    */ 
    static class Response : DSFMLObject
    {
        override void dispose()
        {
            sfHttpResponse_Destroy(m_ptr);
        }
        
        /**
        *   Get the value of a field
        *   
        *   Params:
        *       field = Name of the field to get (case-insensitive)        
        *   Returns:
        *       Value of the field, or enpty string if not found        
        */
        char[] getField(char[] field)
        {
            return fromStringz(sfHttpResponse_GetField(m_ptr, toStringz(field)));
        }
        
        /**
        *   Get the header status code
        *   
        *   Returns:
        *       header status code        
        */        
        HttpStatus getStatus()
        {
            return sfHttpResponse_GetStatus(m_ptr);
        }
    
        /**
        *   Get the major HTTP version number of the response
        *   
        *   Returns:
        *       Major version number                        
        */
        uint getMajorHTTPVersion()
        {
            return sfHttpResponse_GetMajorVersion(m_ptr);
        }
        
        /**
        *   Get the minor HTTP version number of the response
        *   
        *   Returns:
        *       Minor version number        
        */
        uint getMinorHTTPVersion()
        {
            return sfHttpResponse_GetMinorVersion(m_ptr);
        }
        
        /**
        *   Get the body of the response. The body can contain :
        *       - the requested page (for GET requests)
        *       - a response from the server (for POST requests)
        *       - nothing (for HEAD requests)
        *       - an error message (in case of an error)
        *       
        *   Returns:
        *       the response body                        
        */
        char[] getBody()
        {
            return fromStringz(sfHttpResponse_GetBody(m_ptr));
        }
             
    private:
        this(void* ptr)
        {
            super(ptr);
        }
    // External ================================================================
        extern (C)
        {
            typedef void function(void*) pf_sfHttpResponse_Destroy; 
            typedef char* function(void*, char*) pf_sfHttpResponse_GetField;
            typedef HttpStatus function(void*) pf_sfHttpResponse_GetStatus; 
            typedef uint function(void*) pf_sfHttpResponse_GetMajorVersion;
            typedef uint function(void*) pf_sfHttpResponse_GetMinorVersion;
            typedef char* function(void*) pf_sfHttpResponse_GetBody;

            static pf_sfHttpResponse_Destroy sfHttpResponse_Destroy; 
            static pf_sfHttpResponse_GetField sfHttpResponse_GetField;
            static pf_sfHttpResponse_GetStatus sfHttpResponse_GetStatus; 
            static pf_sfHttpResponse_GetMajorVersion sfHttpResponse_GetMajorVersion;
            static pf_sfHttpResponse_GetMinorVersion sfHttpResponse_GetMinorVersion;
            static pf_sfHttpResponse_GetBody sfHttpResponse_GetBody;
        }
    
        static this()
        {
            DllLoader dll = DllLoader.load("csfml-network");
            
            sfHttpResponse_Destroy = cast(pf_sfHttpResponse_Destroy)dll.getSymbol("sfHttpResponse_Destroy"); 
            sfHttpResponse_GetField = cast(pf_sfHttpResponse_GetField)dll.getSymbol("sfHttpResponse_GetField");
            sfHttpResponse_GetStatus = cast(pf_sfHttpResponse_GetStatus)dll.getSymbol("sfHttpResponse_GetStatus"); 
            sfHttpResponse_GetMajorVersion = cast(pf_sfHttpResponse_GetMajorVersion)dll.getSymbol("sfHttpResponse_GetMajorVersion");
            sfHttpResponse_GetMinorVersion = cast(pf_sfHttpResponse_GetMinorVersion)dll.getSymbol("sfHttpResponse_GetMinorVersion");
            sfHttpResponse_GetBody = cast(pf_sfHttpResponse_GetBody)dll.getSymbol("sfHttpResponse_GetBody");
        }
    }

    /**
    *   Wrapper for a HTTP response which is basically :
    *       - a header with a status code and a set of field/value pairs
    *       - a body (the content of the requested resource)                    
    */ 
    static class Request : DSFMLObject
    {
        /**
        *   Constructor
        *   
        *   Params:
        *       requestMethod = Method to use for the request (Get by default)
        *       uri = Target URI ("/" by default -- index page)
        *       requestBody = Content of the request's body (empty by default)                      
        */
        this(HttpMethod requestMethod = HttpMethod.GET, char[] uri = "/", char[] requestBody = "")
        {
            super(sfHttpRequest_Create());
            sfHttpRequest_SetMethod(m_ptr, requestMethod);
            sfHttpRequest_SetURI(m_ptr, toStringz(uri));
            sfHttpRequest_SetBody(m_ptr, toStringz(requestBody));
        }
        
        /**
        *   Set the value of a field. Field is created if it doesn't exists.                                                                                         
        *   
        *   Params:
        *       field = name of the field to set (case-insensitive)
        *       value = value of the field
        */
        void setField(char[] field, char[] value)
        {
            sfHttpRequest_SetField(m_ptr, toStringz(field), toStringz(value));
        }
    
        /**
        *   Set the request method.
        *   
        *   Params:
        *       requestMethod = Method to use for the request.        
        */
        void setMethod(HttpMethod requestMethod)
        {
            sfHttpRequest_SetMethod(m_ptr, requestMethod);
        }
    
        /**
        *   Set the target URI of the request.
        *   
        *   Params:
        *       uri = URI to request, local to the host.
        *   Returns:
        */
        void setURI(char[] uri)
        {
            sfHttpRequest_SetURI(m_ptr, toStringz(uri));
        }
    
        /**
        *   Set the HTTP version of the request.
        *   
        *   Params:
        *       major = Major version number
        *       minor = Minor version number                
        */
        void setHttpVersion(uint major, uint minor)
        {
            sfHttpRequest_SetHttpVersion(m_ptr, major, minor);
        }
        
        /**
        *   Set the body of the request. This parameter is optionnal and make sense
        *   only for POST requests.        
        *   
        *   Params:
        *       requestBody = Content of the request body.        
        */
        void setBody(char[] requestBody)
        {
            sfHttpRequest_SetBody(m_ptr, toStringz(requestBody));
        }
    
    private:
    
    // External ================================================================
        extern (C)
        {
            typedef void* function() pf_sfHttpRequest_Create;
            typedef void function(void*) pf_sfHttpRequest_Destroy;
            typedef void function(void*, char*, char*) pf_sfHttpRequest_SetField;
            typedef void function(void*, HttpMethod) pf_sfHttpRequest_SetMethod;
            typedef void function(void*, char*) pf_sfHttpRequest_SetURI;
            typedef void function(void*, uint, uint) pf_sfHttpRequest_SetHttpVersion;
            typedef void function(void*, char*) pf_sfHttpRequest_SetBody;
            
            static pf_sfHttpRequest_Create sfHttpRequest_Create;
            static pf_sfHttpRequest_Destroy sfHttpRequest_Destroy;
            static pf_sfHttpRequest_SetField sfHttpRequest_SetField;
            static pf_sfHttpRequest_SetMethod sfHttpRequest_SetMethod;
            static pf_sfHttpRequest_SetURI sfHttpRequest_SetURI;
            static pf_sfHttpRequest_SetHttpVersion sfHttpRequest_SetHttpVersion;
            static pf_sfHttpRequest_SetBody sfHttpRequest_SetBody;
        }
    
        static this()
        {
            DllLoader dll = DllLoader.load("csfml-network");
            
            sfHttpRequest_Create = cast(pf_sfHttpRequest_Create)dll.getSymbol("sfHttpRequest_Create");
            sfHttpRequest_Destroy = cast(pf_sfHttpRequest_Destroy)dll.getSymbol("sfHttpRequest_Destroy");
            sfHttpRequest_SetField = cast(pf_sfHttpRequest_SetField)dll.getSymbol("sfHttpRequest_SetField");
            sfHttpRequest_SetMethod = cast(pf_sfHttpRequest_SetMethod)dll.getSymbol("sfHttpRequest_SetMethod");
            sfHttpRequest_SetURI = cast(pf_sfHttpRequest_SetURI)dll.getSymbol("sfHttpRequest_SetURI");
            sfHttpRequest_SetHttpVersion = cast(pf_sfHttpRequest_SetHttpVersion)dll.getSymbol("sfHttpRequest_SetHttpVersion");
            sfHttpRequest_SetBody = cast(pf_sfHttpRequest_SetBody)dll.getSymbol("sfHttpRequest_SetBody");
        }
    }
    
    /**
    *   Constructor
    */
    this()
    {
        super(sfHttp_Create());
    }

    /**
    *   Constructor
    *   
    *   Params:
    *       host = Web server to connect to
    *       port = port to use for connection (0 by default -- use the standard port of the protocol)        
    */
    this(char[] host, ushort port = 0)
    {
        super(sfHttp_Create());
        sfHttp_SetHost(m_ptr, toStringz(host), port);
    }

    override void dispose()
    {
        sfHttp_Destroy(m_ptr);
    }
    
    /**
    *   Set the target host.
    *   
    *   Params:
    *       host = Web server to connect to
    *       port = port to use for connection (0 by default -- use the standard port of the protocol)                   
    */
    void setHost(char[] host, ushort port = 0)
    {
        sfHttp_SetHost(m_ptr, toStringz(host), port);
    }
    
    /**
    *   Send a HTTP request and return the server's response.
    *   You must be connected to a host before sending requests.
    *   Any missing mandatory header field will be added with an appropriate value.
    *       
    *   Warning : this function waits for the server's response and may
    *   not return instantly; use a thread if you don't want to block your
    *   application.
    *
    *   Params:
    *       req = Request to send
    *
    *   Returns:
    *       Server's response
    */

    Response sendRequest(Request req)
    {
        return new Response( sfHttp_SendRequest(m_ptr, req.getNativePointer) ); 
    } 
      
private:

// External ====================================================================

    extern (C)
    {
        typedef void* function() pf_sfHttp_Create;
        typedef void function(void*) pf_sfHttp_Destroy;
        typedef void function(void*, char*, ushort) pf_sfHttp_SetHost;
        typedef void* function(void*, void*) pf_sfHttp_SendRequest;
        
        static pf_sfHttp_Create sfHttp_Create;
        static pf_sfHttp_Destroy sfHttp_Destroy;
        static pf_sfHttp_SetHost sfHttp_SetHost; 
        static pf_sfHttp_SendRequest sfHttp_SendRequest;
    }

    static this()
    {
        DllLoader dll = DllLoader.load("csfml-network");
        
        sfHttp_Create = cast(pf_sfHttp_Create)dll.getSymbol("sfHttp_Create");
        sfHttp_Destroy = cast(pf_sfHttp_Destroy)dll.getSymbol("sfHttp_Destroy");
        sfHttp_SetHost = cast(pf_sfHttp_SetHost)dll.getSymbol("sfHttp_SetHost"); 
        sfHttp_SendRequest = cast(pf_sfHttp_SendRequest)dll.getSymbol("sfHttp_SendRequest");
    }
}