#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'json'

module RPC
  class InvalidRequestError < StandardError; end
  class UndefinedMethodError < StandardError; end

  class Request
    attr_reader :method, :params, :id

    def initialize(method, *args)
      options = args.last.is_a?(Hash) ? args.pop : {}
      @method = method
      @params = args
      @id = options[:id]
    end

    def to_json
      JSON.dump({:method => @method, :params => @params, :id => @id})
    end

    def self.parse(str)
      json = JSON.parse(str) rescue {}
      method, params, id = json.values_at('method', 'params', 'id')
      raise InvalidRequestError if method.nil?
      self.new(method, *(params || []), :id => id)
    end
  end

  class Response
    attr_reader :result, :error, :id

    def initialize(result, *args)
      options = args.last.is_a?(Hash) ? args.pop : {}
      @result = result
      @error = nil
      @result, @error = @error, @result if options[:error]
      @id = options[:id]
    end

    def to_json
      JSON.dump({:result => @result, :error => @error, :id => @id})
    end
  end

  module Receiver
    def self.included(base)
      base.send :include, InstanceMethods
      base.send :extend, ClassMethods
    end

    module ClassMethods
      def attr_rpc(*methods)
        methods.each do |method|
          rpc_methods << method.to_s
        end
        rpc_methods.uniq!
      end
      
      def rpc_methods
        @rpc_methods ||= []
      end
    end

    module InstanceMethods
      def process_request(data)
        begin
          request = Request.parse(data)
          raise UndefinedMethodError unless self.class.rpc_methods.include?(request.method)
          id = request.id
          result = self.send(request.method, *request.params)
          error = false
        rescue => e
          id = nil
          result = e.to_s
          error = true
        ensure
          return Response.new(result, :id => id, :error => error)
        end
      end
    end
  end
end

class Server
  include RPC::Receiver
  attr_rpc :mul

  def mul(a, b)
    a.to_i * b.to_i
  end
end

server = Server.new
req = RPC::Request.new('mul', 3, 4, :id => Time.now.to_i)
puts req.to_json
res = server.process_request(req.to_json)
puts res.to_json