# -*- encoding: utf-8 -*-
require 'growthforecast-client'

module MultiForecast
  class Error < StandardError; end
  class NotFound < Error; end
  class AlreadyExists < Error; end

  class Client
    include ::MultiForecast::ConversionRule
    attr_accessor :clients
    attr_accessor :debug_dev
    attr_accessor :short_metrics

    # @param [Hash] opts
    #   [Hash] mapping: Mapping rules from `path` to GrowthForecast's `base_uri`.
    def initialize(opts = {})
      @mapping = opts['mapping'] || { '' => 'http://localhost:5125' }
      @short_metrics = opts['short_metrics'] || true

      @clients = {}
      @base_uris = {}
      @mapping.each do |path, base_uri|
        if base_uri.kind_of?(Hash)
          base_uri = uri['in_uri']
          out_uri  = uri['out_uri']
        end
        @clients[path] = GrowthForecast::Client.new(base_uri)
        @base_uris[path] = out_uri || base_uri
      end
    end

    # set the `debug_dev` attribute of HTTPClient
    # @param [IO] debug_dev such as STDOUT
    def debug_dev=(debug_dev)
      @debug_dev = debug_dev
      @clients.each {|c| c.debug_dev = debug_dev }
    end

    def clients(base_path = nil)
      base_path.nil? ? @clients.values : @clients.values_at(*ids(base_path)).compact
    end

    def client(path)
      @last_client = @clients[id(path)]
    end

    def last_client
      @last_client
    end

    def last_response
      @last_client.last_response
    end

    # GET the JSON API
    # @param [String] path
    # @return [Hash] response body
    def get_json(path)
      client(path).get_json(path)
    end

    # POST the JSON API
    # @param [String] path
    # @param [Hash] data 
    # @return [Hash] response body
    def post_json(path, data = {})
      client(path).post_json(path, data)
    end

    # POST the non-JSON API
    # @param [String] path
    # @param [Hash] data 
    # @return [String] response body
    def post_query(path, data = {})
      client(path).post_query(path, data)
    end

    # Get the list of graphs, /json/list/graph
    # @return [Hash] list of graphs
    # @example
    # [
    #   {"base_uri"=>"xxxxx",
    #    "service_name"=>"mbclient",
    #    "section_name"=>"mbclient",
    #    "graph_name"=>"test%2Fhostname%2F%3C2sec_count",
    #    "path"=>"test/hostname/<2sec_count",
    #    "id"=>4},
    #   {"base_uri"=>"xxxxx",
    #    "service_name"=>"mbclient",
    #    "section_name"=>"mbclient",
    #    "graph_name"=>"test%2Fhostname%2F%3C1sec_count",
    #    "path"=>"test/hostname/<1sec_count",
    #    "id"=>3},
    # ]
    def list_graph(base_path = nil)
      clients(base_path).inject([]) do |ret, client|
        graphs = []
        client.list_graph.each do |graph|
          graph['base_uri'] = client.base_uri
          graph['path']  = path(graph['service_name'], graph['section_name'], graph['graph_name'])
          graphs << graph if base_path.nil? or graph['path'].index(base_path) == 0
        end
        ret = ret + graphs
      end
    end

    # Get the propety of a graph, GET /api/:path
    # @param [String] path
    # @return [Hash] the graph property
    # @example
    #{
    #  "base_uri" => "xxxxxx",
    #  "path" => "test/hostname/<4sec_count",
    #  "service_name"=>"mbclient",
    #  "section_name"=>"mbclient",
    #  "graph_name"=>"test%2Fhostname%2F%3C4sec_count",
    #  "number"=>1,
    #  "llimit"=>-1000000000,
    #  "mode"=>"gauge",
    #  "stype"=>"AREA",
    #  "adjustval"=>"1",
    #  "meta"=>"",
    #  "gmode"=>"gauge",
    #  "color"=>"#cc6633",
    #  "created_at"=>"2013/02/02 00:41:11",
    #  "ulimit"=>1000000000,
    #  "id"=>21,
    #  "description"=>"",
    #  "sulimit"=>100000,
    #  "unit"=>"",
    #  "sort"=>0,
    #  "updated_at"=>"2013/02/02 02:32:10",
    #  "adjust"=>"*",
    #  "type"=>"AREA",
    #  "sllimit"=>-100000,
    #  "md5"=>"3c59dc048e8850243be8079a5c74d079"}
    def get_graph(path)
      client(path).get_graph(service_name(path), section_name(path), graph_name(path)).tap do |graph|
        graph['base_uri'] = client(path).base_uri
        graph['path']  = path
      end
    end

    # Post parameters to a graph, POST /api/:path
    # @param [String] path
    # @param [Hash] params The POST parameters. See #get_graph
    def post_graph(path, params)
      client(path).post_graph(service_name(path), section_name(path), graph_name(path), params)
    end

    # Delete a graph, POST /delete/:path
    # @param [String] path
    def delete_graph(path)
      client(path).delete_graph(service_name(path), section_name(path), graph_name(path))
    end

    # Update the property of a graph, /json/edit/graph/:id
    # @param [String] path
    # @param [Hash]   params
    #   All of parameters given by #get_graph are available except `number` and `mode`.
    # @return [Hash]  error response
    # @example
    # {"error"=>0} #=> Success
    # {"error"=>1} #=> Error
    def edit_graph(path, params)
      client(path).edit_graph(service_name(path), section_name(path), graph_name(path), params)
    end

    # Get the list of complex graphs, /json/list/complex
    # @return [Hash] list of complex graphs
    # @example
    # [
    #   {"base_uri"=>"xxxxx",
    #    "path"=>"test/hostname/<2sec_count",
    #    "service_name"=>"mbclient",
    #    "section_name"=>"mbclient",
    #    "graph_name"=>"test%2Fhostname%2F%3C2sec_count",
    #    "id"=>4},
    #   {"base_uri"=>"xxxxx",
    #    "path"=>"test/hostname/<1sec_count",
    #    "service_name"=>"mbclient",
    #    "section_name"=>"mbclient",
    #    "graph_name"=>"test%2Fhostname%2F%3C1sec_count",
    #    "id"=>3},
    # ]
    def list_complex(base_path = nil)
      clients(base_path).inject([]) do |ret, client|
        graphs = []
        client.list_complex.each do |graph|
          graph['base_uri'] = client.base_uri
          graph['path']  = path(graph['service_name'], graph['section_name'], graph['graph_name'])
          graphs << graph if base_path.nil? or graph['path'].index(base_path) == 0
        end
        ret = ret + graphs
      end
    end

    # Create a complex graph
    #
    # @param [Array] from_graphs Array of graph properties whose keys are
    #   ["path", "gmode", "stack", "type"]
    # @param [Hash] to_complex Property of Complex Graph, whose keys are like
    #   ["path", "description", "sort"]
    def create_complex(from_graphs, to_complex)
      from_graphs = from_graphs.dup
      to_complex = to_complex.dup

      from_graphs.each do |from_graph|
        from_graph['service_name'] = service_name(from_graph['path'])
        from_graph['section_name'] = section_name(from_graph['path'])
        from_graph['graph_name']   = graph_name(from_graph['path'])
        from_graph.delete('path')
        from_graph.delete('base_uri')
      end

      to_complex['service_name'] = service_name(to_complex['path'])
      to_complex['section_name'] = section_name(to_complex['path'])
      to_complex['graph_name']   = graph_name(to_complex['path'])
      path = to_complex.delete('path')

      # NOTE: FROM_GRAPHS AND TO _COMPLEX MUST BE THE SAME GF SERVER
      client(path).create_complex(from_graphs, to_complex)
    end

    # Get a complex graph
    #
    # @param [String] path
    # @return [Hash] the graph property
    # @example
    # {"number"=>0,
    #  "complex"=>true,
    #  "created_at"=>"2013/05/20 15:08:28",
    #  "service_name"=>"app name",
    #  "section_name"=>"host name",
    #  "id"=>18,
    #  "graph_name"=>"complex graph test",
    #  "data"=>
    #   [{"gmode"=>"gauge", "stack"=>false, "type"=>"AREA", "graph_id"=>218},
    #    {"gmode"=>"gauge", "stack"=>true, "type"=>"AREA", "graph_id"=>217}],
    #  "sumup"=>false,
    #  "description"=>"complex graph test",
    #  "sort"=>10,
    #  "updated_at"=>"2013/05/20 15:08:28"}
    def get_complex(path)
      client(path).get_complex(service_name(path), section_name(path), graph_name(path)).tap do |graph|
        graph['base_uri'] = client(path).base_uri
        graph['path']  = path
      end
    end

    # Delete a complex graph
    #
    # @param [String] path
    # @return [Hash]  error response
    # @example
    # {"error"=>0} #=> Success
    # {"error"=>1} #=> Error
    def delete_complex(path)
      client(path).delete_complex(service_name(path), section_name(path), graph_name(path))
    end

    # Get graph image uri
    #
    # @param [String] path
    # @param [Hash] params for the query string
    #   t      [String] the time unit such as 'h' (an hour), '4h' (4 hours), '8h', 'n' (half day), 'd' (a day), '3d', 'w', (a week), 'm' (a month), 'y' (a year).
    #                   Also, 'sh' 's4h' 's8h', 'sn', 'sd', 's3d' for graphs generated by short period GF worker.
    #                   Also, this parameter is overrided with 'c' or 'sc' when `from` parameter is set.
    #   from   [String|Time] the time period to show 'from'. String describing a time, or a Time object
    #   to     [String|Time] the time period to show 'to'.   String describing a time, or a Time Object
    #   width  [String] the widh of image to show
    #   height [String] the height of image to show
    # @return [Hash]  error response
    # @example
    def get_graph_uri(path, params = {})
      params = preprocess_time_params(params) if params
      "#{@base_uris[id(path)]}/graph/#{uri_escape(service_name(path))}/#{uri_escape(section_name(path))}/#{uri_escape(graph_name(path))}?#{query_string(params)}"
    end

    # Get complex graph image uri
    #
    # @param [String] path
    # @param [Hash] params for the query string
    #   t      [String] the time unit such as 'h' (an hour), '4h' (4 hours), '8h', 'n' (half day), 'd' (a day), '3d', 'w', (a week), 'm' (a month), 'y' (a year).
    #                   Also, 'sh' 's4h' 's8h', 'sn', 'sd', 's3d' for graphs generated by short period GF worker.
    #                   Also, this parameter is overrided with 'c' or 'sc' when `from` parameter is set.
    #   from   [String|Time] the time period to show 'from'. String describing a time, or a Time object
    #   to     [String|Time] the time period to show 'to'.   String describing a time, or a Time Object
    #   width  [String] the widh of image to show
    #   height [String] the height of image to show
    # @return [Hash]  error response
    # @example
    def get_complex_uri(path, params = {})
      params = preprocess_time_params(params) if params
      "#{@base_uris[id(path)]}/complex/graph/#{uri_escape(service_name(path))}/#{uri_escape(section_name(path))}/#{uri_escape(graph_name(path))}?#{query_string(params)}"
    end

    # process the time params (from and to)
    def preprocess_time_params(params)
      params = params.dup
      params['from'] = Time.parse(params['from']) if params['from'].kind_of?(String)
      params['to']   = Time.parse(params['to']) if params['to'].kind_of?(String)
      if params['from'] and params['to']
        # if from is more future than 3 days ago, use 'sc' (short period time worker)
        params['t']    = (@short_metrics && params['from'] > Time.now - 60 * 60 * 24 * 3) ? 'sc' : 'c'
        params['from'] = params['from'].strftime("%F %T %z") # format is determined
        params['to']   = params['to'].strftime("%F %T %z")
      end
      params
    end

    private

    # build URI query string
    #
    # @param [Hash] param
    # @return [String] query string
    # @example
    def query_string(params)
      return '' if params.nil?
      params.keys.collect{|key| "#{URI.escape(key.to_s)}=#{URI.escape(params[key].to_s)}" }.join('&')
    end
  end
end