/*
 *  JsonRpc-Cpp - JSON-RPC implementation.
 *  Copyright (C) 2008-2011 Sebastien Vincent <sebastien.vincent@cppextrem.com>
 *
 *  This program 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * \file jsonrpc_handler.cpp
 * \brief JSON-RPC server processor engine.
 * \author Sebastien Vincent
 */

#include "jsonrpc_handler.h"

namespace Json
{
  namespace Rpc
  {
    CallbackMethod::~CallbackMethod()
    {
    }

    Handler::Handler()
    {
      /* add a RPC method that list the actual RPC methods contained in
       * the Handler
       */
      Json::Value root;

      root["description"] = "List the RPC methods available";
      root["parameters"] = Json::Value::null;
      root["returns"] =
        "Object that contains description of all methods registered";

      AddMethod(new RpcMethod<Handler>(*this, &Handler::SystemDescribe,
            std::string("system.describe"), root));
    }

    Handler::~Handler()
    {
      /* delete all objects from the list */
      for(std::list<CallbackMethod*>::const_iterator it = m_methods.begin() ; it != m_methods.end() ; ++it)
      {
        delete (*it);
      }
      m_methods.clear();
    }

    void Handler::AddMethod(CallbackMethod* method)
    {
      m_methods.push_back(method);
    }

    void Handler::DeleteMethod(const std::string& name)
    {
      /* do not delete system defined method */
      if(name == "system.describe")
      {
        return;
      }

      for(std::list<CallbackMethod*>::iterator it = m_methods.begin() ; it != m_methods.end() ; ++it)
      {
        if((*it)->GetName() == name)
        {
          delete (*it);
          m_methods.erase(it);
          break;
        }
      }
    }

    bool Handler::SystemDescribe(const Json::Value& msg, Json::Value& response)
    {
      Json::Value methods;
      response["jsonrpc"] = "2.0";
      response["id"] = msg["id"];

      for(std::list<CallbackMethod*>::iterator it = m_methods.begin() ; it != m_methods.end() ; ++it)
      {
        methods[(*it)->GetName()] = (*it)->GetDescription();
      }

      response["result"] = methods;
      return true;
    }

    std::string Handler::GetString(Json::Value value)
    {
      return m_writer.write(value);
    }

    bool Handler::Check(const Json::Value& root, Json::Value& error)
    {
      Json::Value err;

      /* check the JSON-RPC version => 2.0 */
      if(!root.isObject() || !root.isMember("jsonrpc") ||
          root["jsonrpc"] != "2.0")
      {
        error["id"] = Json::Value::null;
        error["jsonrpc"] = "2.0";

        err["code"] = INVALID_REQUEST;
        err["message"] = "Invalid JSON-RPC request.";
        error["error"] = err;
        return false;
      }

      if(root.isMember("id") && (root["id"].isArray() || root["id"].isObject()))
      {
        error["id"] = Json::Value::null;
        error["jsonrpc"] = "2.0";

        err["code"] = INVALID_REQUEST;
        err["message"] = "Invalid JSON-RPC request.";
        error["error"] = err;
        return false;
      }

      /* extract "method" attribute */
      if(!root.isMember("method") || !root["method"].isString())
      {
        error["id"] = Json::Value::null;
        error["jsonrpc"] = "2.0";

        err["code"] = INVALID_REQUEST;
        err["message"] = "Invalid JSON-RPC request.";
        error["error"] = err;
        return false;
      }

      return true;
    }

    bool Handler::Process(const Json::Value& root, Json::Value& response)
    {
      Json::Value error;
      std::string method;

      if(!Check(root, error))
      {
        response = error;
        return false;
      }

      method = root["method"].asString();

      if(method != "")
      {
        CallbackMethod* rpc = Lookup(method);
        if(rpc)
        {
          return rpc->Call(root, response);
        }
      }

      /* forge an error response */
      response["id"] = root.isMember("id") ? root["id"] : Json::Value::null;
      response["jsonrpc"] = "2.0";

      error["code"] = METHOD_NOT_FOUND;
      error["message"] = "Method not found.";
      response["error"] = error;

      return false;
    }

    bool Handler::Process(const std::string& msg, Json::Value& response)
    {
      Json::Value root;
      Json::Value error;
      bool parsing = false;

      /* parsing */
      parsing = m_reader.parse(msg, root);

      if(!parsing)
      {
        /* request or batched call is not in JSON format */
        response["id"] = Json::Value::null;
        response["jsonrpc"] = "2.0";

        error["code"] = PARSING_ERROR;
        error["message"] = "Parse error.";
        response["error"] = error;
        return false;
      }

      if(root.isArray())
      {
        /* batched call */
        Json::Value::ArrayIndex i = 0;
        Json::Value::ArrayIndex j = 0;

        for(i = 0 ; i < root.size() ; i++)
        {
          Json::Value ret;
          Process(root[i], ret);

          if(ret != Json::Value::null)
          {
            /* it is not a notification, add to array of responses */
            response[j] = ret;
            j++;
          }
        }
        return true;
      }
      else
      {
        return Process(root, response);
      }
    }

    bool Handler::Process(const char* msg, Json::Value& response)
    {
      std::string str(msg);

      return Process(str, response);
    }

    CallbackMethod* Handler::Lookup(const std::string& name) const
    {
      for(std::list<CallbackMethod*>::const_iterator it = m_methods.begin() ; it != m_methods.end() ; ++it)
      {
        if((*it)->GetName() == name)
        {
          return (*it);
        }
      }

      return 0;
    }
  } /* namespace Rpc */
} /* namespace Json */
