require "focuslight"
require "focuslight/logger"

require "digest"
require "json"

module Focuslight
  class Graph
    include Focuslight::Logger

    def self.concrete(row)
      if row.has_key?(:mode) && row.has_key?(:type)
        Focuslight::SimpleGraph.new(row)
      else
        Focuslight::ComplexGraph.new(row)
      end
    end

    attr_accessor :id, :service, :section, :graph, :number, :description, :sort
    attr_accessor :meta
    attr_accessor :created_at_time, :updated_at_time

    attr_accessor :c_type, :stack # for complex graph construction

    def initialize(row)
      @row_hash = row

      @id = row[:id]
      @service = row[:service_name]
      @section = row[:section_name]
      @graph = row[:graph_name]
      @number = row[:number].to_i # NOT NULL DEFAULT 0
      @description = row[:description] || ''
      @sort = row[:sort].to_i # NOT NULL DEFAULT 0

      @meta = row[:meta]
      @parsed_meta = JSON.parse(@meta || '{}', :symbolize_names => true)

      @created_at_time = Time.at(row[:created_at].to_i)
      @updated_at_time = Time.at(row[:updated_at].to_i)
    end

    def path
      [@service, @section, @graph]
    end

    def created_at
      @created_at_time.strftime('%Y/%m/%d %T')
    end

    def updated_at
      @updated_at_time.strftime('%Y/%m/%d %T')
    end

    def to_hash
      {
        id: @id, service_name: @service, section_name: @section, graph_name: @graph,
        number: @number, description: @description, sort: @sort,
        created_at: self.created_at(), updated_at: self.updated_at(),
      }
    end

    def self.hash2request(hash)
      hash = hash.dup
      is_complex = hash.delete(:complex)

      hash.delete(:id)
      hash.delete(:created_at)
      hash.delete(:updated_at)

      return hash unless is_complex ##TODO concrete?

      hash.delete(:number)
      hash[:sumup] = (hash[:sumup] ? '1' : '0')

      data_rows = hash.delete(:data)

      first = data_rows.shift
      hash['path-1'.to_sym] = first[:graph_id]
      hash['type-1'.to_sym] = first[:type]

      p2 = 'path-2'.to_sym
      t2 = 'type-2'.to_sym
      s2 = 'stack-2'.to_sym
      hash[p2] = []
      hash[t2] = []
      hash[s2] = []
      data_rows.each do |row|
        hash[p2] << row[:graph_id]
        hash[t2] << row[:type]
        hash[s2] << (row[:stack] ? '1' : '0')
      end

      hash ##TODO concrete?
    end
  end

  class SimpleGraph < Graph
    COLUMNS = %w(service_name section_name graph_name number mode color llimit created_at updated_at)
    PLACEHOLDERS = COLUMNS.map{|c| '?'}

    attr_accessor :mode, :color, :ulimit, :llimit, :type

    attr_reader :md5
    attr_accessor :adjust, :adjustval, :unit

    def initialize(row)
      super

      @mode = row[:mode] || 'gauge' # NOT NULL DEFAULT 'gauge'
      @color = row[:color] || '#00CC00' # NOT NULL DEFAULT '#00CC00'
      @ulimit = row[:ulimit] || 1000000000000000 # NOT NULL DEFAULT 1000000000000000
      @llimit = row[:llimit] || 0
      @type = row[:type] || 'AREA'

      @md5 = Digest::MD5.hexdigest(@id.to_s)

      @adjust = @parsed_meta.fetch(:adjust, '*')
      @adjustval = @parsed_meta.fetch(:adjustval, '1')
      @unit = @parsed_meta.fetch(:unit, '')
    end

    def to_hash
      simple = {
        mode: @mode, color: @color,
        ulimit: @ulimit, llimit: @llimit,
        type: @type,
        adjust: @adjust, adjustval: @adjustval, unit: @unit,
        complex: false,
        md5: @md5, meta: @meta,
      }
      hash = super
      hash.merge(simple)
    end

    def complex?
      false
    end

    def update(args={})
      meta = @parsed_meta.dup
      args.each do |k, v|
        case k.to_sym
        when :number then @number = v
        when :description then @description = v
        when :sort then @sort = v
        when :mode then @mode = v
        when :color then @color = v
        when :ulimit then @ulimit = v
        when :llimit then @llimit = v
        when :type then @type = v
        else
          meta[k.to_sym] = v
        end
      end
      @parsed_meta = self.class.meta_clean(@parsed_meta.merge(meta))
      @meta = @parsed_meta.to_json
    end

    def self.meta_clean(args={})
      args.delete_if do |k,v|
        %w(id service_name section_name graph_name number
           description sort mode color ulimit llimit type).include?(k.to_s)
      end
    end
  end

  class ComplexGraph < Graph
    attr_accessor :sumup, :data_rows
    attr_reader :complex_graph

    def initialize(row)
      super

      uri = [:'type-1', :'path-1'].map{|k| @parsed_meta[k]}.join(':') + ':0' # stack

      data_rows = []

      first_row = {
        type: @parsed_meta[:'type-1'],
        path: @parsed_meta[:'path-1'].to_i,
        stack: false,
        graph_id: @parsed_meta[:'path-1'].to_i,
      }
      data_rows << first_row

      unless @parsed_meta[:'type-2'].is_a?(Array)
        [:'type-2', :'path-2', :'stack-2'].each do |key|
          @parsed_meta[key] = [@parsed_meta[key]].flatten
        end
      end

      @parsed_meta[:'type-2'].each_with_index do |type, i|
        t = @parsed_meta[:'type-2'][i]
        p = @parsed_meta[:'path-2'][i].to_i
        s = @parsed_meta[:'stack-2'][i].is_a?(String) ? !!(@parsed_meta[:'stack-2'][i] =~ /^(1|true)$/i) : !!@parsed_meta[:'stack-2'][i]
        uri += ':' + [t, p, (s ? '1' : '0')].join(':')
        data_rows << {type: t, path: p, stack: s, graph_id: p}
      end

      @sumup = @parsed_meta.fetch(:sumup, '0') != '0' # '0' is false
      @data_rows = data_rows
      @complex_graph = uri
    end

    def to_hash
      complex = {
        sumup: @sumup, data: @data_rows,
        complex: true
      }
      hash = super
      hash.merge(complex)
    end

    def complex?
      true
    end

    def update(args={})
      meta = @parsed_meta.dup
      args.each do |k, v|
        case k.to_sym
        when :number then @number = v
        when :description then @description = v
        when :sort then @sort = v
        else
          meta[k.to_sym] = v
        end
      end
      @parsed_meta = self.class.meta_clean(@parsed_meta.merge(meta))
      @meta = @parsed_meta.to_json
    end

    def self.meta_clean(args={})
      args.delete_if do |k,v|
        %w(id service_name section_name graph_name number description sort).include?(k.to_s)
      end
    end
  end
end
