/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * 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
 * OpenSceneGraph Public License for more details.
*/

#include "RestHttpDevice.hpp"
#include <OpenThreads/Thread>
#include <osg/ValueObject>
#include <osgDB/FileUtils>
#include "request_handler.hpp"

namespace RestHttp {


class StandardRequestHandler : public RestHttpDevice::RequestHandler {
public:
    StandardRequestHandler() : RestHttpDevice::RequestHandler("") {}
    virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply)
    {
        OSG_INFO << "RestHttpDevice :: handling request " << full_request_path << " as user-event" << std::endl;
        
        osg::ref_ptr<osgGA::GUIEventAdapter> event = new osgGA::GUIEventAdapter();
        event->setEventType(osgGA::GUIEventAdapter::USER);
        event->setName(full_request_path);
        event->setTime(getDevice()->getEventQueue()->getTime());
        
        for(Arguments::const_iterator i = arguments.begin(); i != arguments.end(); ++i)
        {
            event->setUserValue(i->first,i->second);
        }
        getDevice()->getEventQueue()->addEvent(event.get());
        
        return sendOkReply(reply);
    }
    
    virtual void describeTo(std::ostream& out) const
    {
        out << getRequestPath() << ": fall-through request-handler, catches all requests w/o registered handler and report them to the console" << std::dec;
    }
};

class HomeRequestHandler : public RestHttpDevice::RequestHandler {
public:
    HomeRequestHandler()
        : RestHttpDevice::RequestHandler("/home")
    {
    }
    
    virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply)
    {
        double time = getLocalTime(arguments, reply);
        getDevice()->getEventQueue()->keyPress(' ', time);
        getDevice()->getEventQueue()->keyRelease(' ', time);
        
        return sendOkReply(reply);
    }
    
    virtual void describeTo(std::ostream& out) const
    {
        out << getRequestPath() << ": sets the mouse-input-range arguments: 'x_min','y_min', 'x_max' and 'y_max'" << std::dec;
    }
};

class SetMouseInputRangeRequestHandler : public RestHttpDevice::RequestHandler {
public:
    SetMouseInputRangeRequestHandler()
        : RestHttpDevice::RequestHandler("/mouse/set_input_range")
    {
    }
    
    virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply)
    {
        int x_min(0), y_min(0), x_max(0), y_max(0);
        
        if (   getIntArgument(arguments, "x_min", reply, x_min)
            && getIntArgument(arguments, "y_min", reply, y_min)
            && getIntArgument(arguments, "x_max", reply, x_max)
            && getIntArgument(arguments, "y_max", reply, y_max))
        {
            getDevice()->getEventQueue()->setMouseInputRange(x_min, y_min, x_max, y_max);
        }
        
        return sendOkReply(reply);
    }
    
    virtual void describeTo(std::ostream& out) const
    {
        out << getRequestPath() << ": sets the mouse-input-range arguments: 'x_min','y_min', 'x_max' and 'y_max'" << std::dec;
    }
};





class KeyCodeRequestHandler : public RestHttpDevice::RequestHandler {
public:
    KeyCodeRequestHandler(bool handle_key_press)
        : RestHttpDevice::RequestHandler(std::string("/key/") + ((handle_key_press) ? "press" : "release"))
        , _handleKeyPress(handle_key_press)
    {
    }
    
    virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply)
    {
        int keycode(0);
        
        if (getHexArgument(arguments, "code", reply, keycode))
        {
            if (_handleKeyPress)
                getDevice()->getEventQueue()->keyPress(keycode, getLocalTime(arguments, reply));
            else
                getDevice()->getEventQueue()->keyRelease(keycode, getLocalTime(arguments, reply));
        }
        
        return sendOkReply(reply);
    }
    
    virtual void describeTo(std::ostream& out) const
    {
        out << getRequestPath() << ": send KEY_" << (_handleKeyPress ? "DOWN" : "UP") <<", using hex-argument 'code' as keycode" << std::dec;
    }
private:
    bool _handleKeyPress;
};


class MouseMotionRequestHandler : public RestHttpDevice::RequestHandler {
public:
    MouseMotionRequestHandler()
        : RestHttpDevice::RequestHandler("/mouse/motion")
    {
    }
    
    virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply)
    {
        int x(0),y(0);
        if (getIntArgument(arguments, "x", reply, x) && getIntArgument(arguments, "y", reply, y))
        {
            double time_stamp = getTimeStamp(arguments, reply);
            
            if (getDevice()->isNewer(time_stamp))
            {
                //getDevice()->getEventQueue()->mouseMotion(x,y, getLocalTime(time_stamp));
                getDevice()->setTargetMousePosition(x,y);
            }
        }
        
        return sendOkReply(reply);
    }
    
    virtual void describeTo(std::ostream& out) const
    {
        out << getRequestPath() << ": send mouse motion using arguments 'x' and 'y' as coordinates" << std::dec;
    }
private:

};


class MouseButtonRequestHandler : public RestHttpDevice::RequestHandler {
public:
    enum Mode { PRESS, RELEASE, DOUBLE_PRESS};
    
    MouseButtonRequestHandler(Mode mode)
        : RestHttpDevice::RequestHandler("")
        , _mode(mode)
    {
        switch(mode) {
            case PRESS:
                setRequestPath("/mouse/press");
                break;
            case RELEASE:
                setRequestPath("/mouse/release");
                break;
            case DOUBLE_PRESS:
                setRequestPath("/mouse/doublepress");
                break;
        }
    }
    
    virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply)
    {
        int x(0),y(0), button(0);

        if (getIntArgument(arguments, "x", reply, x)
            && getIntArgument(arguments, "y", reply, y)
            && getIntArgument(arguments, "button", reply, button))
        {
            getDevice()->setTargetMousePosition(x,y, true);
            switch (_mode) {
                case PRESS:
                    getDevice()->getEventQueue()->mouseButtonPress(x,y, button, getLocalTime(arguments, reply));
                    break;
                case RELEASE:
                    getDevice()->getEventQueue()->mouseButtonRelease(x,y, button, getLocalTime(arguments, reply));
                    break;
                case DOUBLE_PRESS:
                    getDevice()->getEventQueue()->mouseDoubleButtonPress(x,y, button, getLocalTime(arguments, reply));
                    break;
            }
        }
        
        return sendOkReply(reply);
    }
    
    virtual void describeTo(std::ostream& out) const
    {
        out << getRequestPath() << ": send mouse ";
        switch (_mode) {
            case PRESS:
                out << "press"; break;
            case RELEASE:
                out << "release"; break;
            case DOUBLE_PRESS:
                out << "double press"; break;
        }
        out << " using arguments 'x', 'y' and 'button' as coordinates" << std::dec;
    }
private:
    Mode _mode;
};






class RequestHandlerDispatcherCallback: public http::server::request_handler::Callback {
public:
    RequestHandlerDispatcherCallback(RestHttpDevice* parent)
        : http::server::request_handler::Callback()
        , _parent(parent)
    {
    }
    
    virtual bool operator()(const std::string& request_path, http::server::reply& rep);
    
    virtual std::string applyTemplateVars(const std::string& txt) { return txt; }
private:
    RestHttpDevice* _parent;
};


bool RequestHandlerDispatcherCallback::operator()(const std::string& request_path, http::server::reply& reply)
{
    return _parent->handleRequest(request_path, reply);
}

}

RestHttpDevice::RestHttpDevice(const std::string& listening_address, const std::string& listening_port, const std::string& doc_root)
    : osgGA::Device()
    , OpenThreads::Thread()
    , _server(listening_address, listening_port, osgDB::findDataFile(doc_root), std::max(OpenThreads::GetNumberOfProcessors() - 1, 1))
    , _serverAddress(listening_address)
    , _serverPort(listening_port)
    , _documentRoot(doc_root)
    , _firstEventLocalTimeStamp()
    , _firstEventRemoteTimeStamp(-1)
    , _lastEventRemoteTimeStamp(0)
    , _currentMouseX(0.0f)
    , _currentMouseY(0.0f)
    , _targetMouseX(0.0f)
    , _targetMouseY(0.0f)
    , _targetMouseChanged(false)
{
    setCapabilities(RECEIVE_EVENTS);
    
    OSG_NOTICE << "RestHttpDevice :: listening on " << listening_address << ":" << listening_port << ", document root: " << doc_root << std::endl;
    
    if (osgDB::findDataFile(doc_root).empty())
    {
        OSG_WARN << "RestHttpDevice :: warning, can't locate document-root '" << doc_root << "'for the http-server, starting anyway" << std::endl;
    }
    _server.setCallback(new RestHttp::RequestHandlerDispatcherCallback(this));
    
    addRequestHandler(new RestHttp::KeyCodeRequestHandler(false));
    addRequestHandler(new RestHttp::KeyCodeRequestHandler(true));
    
    addRequestHandler(new RestHttp::SetMouseInputRangeRequestHandler());
    addRequestHandler(new RestHttp::MouseMotionRequestHandler());
    addRequestHandler(new RestHttp::MouseButtonRequestHandler(RestHttp::MouseButtonRequestHandler::PRESS));
    addRequestHandler(new RestHttp::MouseButtonRequestHandler(RestHttp::MouseButtonRequestHandler::RELEASE));
    addRequestHandler(new RestHttp::MouseButtonRequestHandler(RestHttp::MouseButtonRequestHandler::DOUBLE_PRESS));
    
    addRequestHandler(new RestHttp::HomeRequestHandler());
    
    addRequestHandler(new RestHttp::StandardRequestHandler());
    
    // start the thread
    start();
}


void RestHttpDevice::run()
{
    _server.run();
}


RestHttpDevice::~RestHttpDevice()
{
    _server.stop();
    join();
}


void RestHttpDevice::addRequestHandler(RequestHandler* handler)
{
    if (handler)
    {
        _map.insert(std::make_pair(handler->getRequestPath(), handler));
        handler->setDevice(this);
    }
}


void RestHttpDevice::parseArguments(const std::string request_path, RequestHandler::Arguments& arguments)
{
    std::size_t pos = request_path.find('?');
    if (pos == std::string::npos)
        return;
    
    std::vector<std::string> list;
    osgDB::split(request_path.substr(pos+1, std::string::npos), list, '&');
    for(std::vector<std::string>::iterator i = list.begin(); i != list.end(); ++i)
    {
        std::vector<std::string> sub_list;
        osgDB::split(*i, sub_list, '=');
        if (sub_list.size() == 2)
            arguments[sub_list[0]] = sub_list[1];
        else if (sub_list.size() == 1)
            arguments[sub_list[0]] = "";
    }
}


bool RestHttpDevice::handleRequest(const std::string& in_request_path,  http::server::reply& reply)
{
    std::string request_path = in_request_path.substr(0, in_request_path.find('?'));
    request_path += "/";
    RequestHandler::Arguments arguments;
    bool arguments_parsed(false);
    
    std::size_t pos(std::string::npos);
    bool handled(false);
    do {
        pos = request_path.find_last_of('/', pos-1);
        if (pos != std::string::npos)
        {
            std::string mangled_path = request_path.substr(0, pos);
            
            std::pair<RequestHandlerMap::iterator,RequestHandlerMap::iterator> range = _map.equal_range(mangled_path);
            if (!arguments_parsed && (range.first != range.second))
            {
                // parse arguments
                parseArguments(in_request_path, arguments);
                arguments_parsed = true;
            }
            for(RequestHandlerMap::iterator i = range.first; i != range.second; ++i)
            {
                if (i->second->operator()(mangled_path, in_request_path, arguments, reply) && !handled)
                    handled = true;
            }
            
        }
    } while ((pos != std::string::npos) && (pos > 0) && !handled);
    
    return handled;
}


void RestHttpDevice::describeTo(std::ostream& out) const
{
    out << "RestHttpDevice :: Server:        " << _serverAddress << std::endl;
    out << "RestHttpDevice :: Port:          " << _serverPort << std::endl;
    out << "RestHttpDevice :: Document-Root: " << _documentRoot << std::endl;
    out << std::endl;
    
    for(RequestHandlerMap::const_iterator i = _map.begin(); i != _map.end(); ++i)
    {
        const RequestHandler* handler(i->second.get());
        out << "RestHttpDevice :: ";
        handler->describeTo(out);
        out << std::endl;
    }

}

