# encoding: UTF-8
#
# Copyright (c) 2010-2017 GoodData Corporation. All rights reserved.
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

require_relative '../../goodzilla/goodzilla'
require_relative '../../mixins/mixins'
require_relative '../metadata'
require_relative 'metadata'

module GoodData
  # Metric representation
  class Metric < MdObject
    include Mixin::Lockable
    extend GoodData::Mixin::ContentPropertyReader
    extend GoodData::Mixin::ContentPropertyWriter

    content_property_reader :folders, :expression, :format
    content_property_writer :folders, :expression, :format

    class << self
      # Method intended to get all objects of that type in a specified project
      #
      # @param options [Hash] the options hash
      # @option options [Boolean] :full if passed true the subclass can decide
      # to pull in full objects. This is desirable from the usability POV
      # but unfortunately has negative impact on performance so it is not
      # the default.
      # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
      def all(options = { :client => GoodData.connection, :project => GoodData.project })
        query('metric', Metric, options)
      end

      def xcreate(metric, options = { :client => GoodData.connection, :project => GoodData.project })
        create(metric, { extended_notation: true }.merge(options))
      end

      def create(metric, options = { :client => GoodData.connection, :project => GoodData.project })
        client, project = GoodData.get_client_and_project(options)

        if metric.is_a?(String)
          expression = metric || options[:expression]
          extended_notation = options[:extended_notation] || false
          title = options[:title]
          summary = options[:summary]
          format = options[:format]
        else
          metric ||= options
          title = metric[:title] || options[:title]
          summary = metric[:summary] || options[:summary]
          expression = metric[:expression] || options[:expression] || fail('Metric has to have its expression defined')
          format = metric[:format] || options[:format]
          extended_notation = metric[:extended_notation] || options[:extended_notation] || false
        end

        expression = if extended_notation
                       dict = {
                         :facts => project.facts.reduce({}) do |memo, item|
                           memo[item.title] = item.uri
                           memo
                         end,
                         :attributes => project.attributes.reduce({}) do |memo, item|
                           memo[item.title] = item.uri
                           memo
                         end,
                         :metrics => project.metrics.reduce({}) do |memo, item|
                           memo[item.title] = item.uri
                           memo
                         end
                       }
                       interpolated_metric = GoodData::SmallGoodZilla.interpolate_metric(expression, dict, options)
                       interpolated_metric
                     else
                       expression
                     end

        metric = {
          'metric' => {
            'content' => {
              'format' => format || '#,##0',
              'expression' => expression
            },
            'meta' => {
              'tags' => '',
              'summary' => summary,
              'title' => title
            }
          }
        }
        # TODO: add test for explicitly provided identifier
        metric['metric']['meta']['identifier'] = options[:identifier] if options[:identifier]

        client.create(Metric, metric, :project => project)
      end

      def execute(expression, options = { :client => GoodData.connection })
        # client = options[:client]
        # fail ArgumentError, 'No :client specified' if client.nil?

        options = expression if expression.is_a?(Hash)

        m = if expression.is_a?(String)
              tmp = {
                :title => 'Temporary metric to be deleted',
                :expression => expression
              }.merge(options)

              GoodData::Metric.create(tmp, options)
            else
              tmp = {
                :title => 'Temporary metric to be deleted'
              }.merge(expression)
              GoodData::Metric.create(tmp, options)
            end
        m.execute
      end

      def xexecute(expression, opts = { :client => GoodData.connection, :project => GoodData.project })
        GoodData.get_client_and_project(opts)

        execute(expression, opts.merge(:extended_notation => true))
      end
    end

    def execute
      opts = {
        :client => client,
        :project => project
      }
      res = GoodData::ReportDefinition.execute(opts.merge(:left => self))
      res.data[0][0] if res && !res.empty?
    end

    def validate
      fail 'Metric needs to have title' if title.nil?
      true
    end

    def metric?
      true
    end

    # Checks that the expression contains certain metadata object.
    # The difference between this and used_by using is in the fact that this
    # is not a transitive closure. it searches only inside the expression
    #
    # @param [GoodData::MdObject] item Object that is going to be looked up
    # @return [Boolean]
    def contain?(item)
      uri = item.respond_to?(:uri) ? item.uri : item
      expression[uri] != nil
    end

    # Checks that the expression contains certain element of an attribute. The value is looked up through given label.
    # @param [GoodData::Label] label Label though which the value is looked up
    # @param [String] value Value that will be looked up through the label.
    # @return [Boolean]
    def contain_value?(label, value)
      uri = label.find_value_uri(value)
      contain?(uri)
    end

    # Method used for replacing values in their state according to mapping.
    # Can be used to replace any values but it is typically used to replace
    # the URIs. Returns a new object of the same type.
    #
    # @param [Array<Array>]Mapping specifying what should be exchanged for what. As mapping should be used output of GoodData::Helpers.prepare_mapping.
    # @return [GoodData::Metric]
    def replace(mapping)
      x = GoodData::MdObject.replace_quoted(self, mapping)
      x = GoodData::MdObject.replace_bracketed(x, mapping)
      vals = GoodData::MdObject.find_replaceable_values(x, mapping)
      GoodData::MdObject.replace_bracketed(x, vals)
    end

    # Method used for replacing attribute element values. Looks up certain value of a label in the MAQL expression and exchanges it for a different value of the same label.
    # @param [GoodData::Label] label Label through which the value and for_value are resolved
    # @param [String] value value that is going to be replaced
    # @param [String] for_value value that is going to be the new one
    # @return [GoodData::Metric]
    def replace_value(label, value, for_label, for_value = nil)
      label = label.respond_to?(:primary_label) ? label.primary_label : label
      if for_value
        for_label = for_label.respond_to?(:primary_label) ? for_label.primary_label : for_label
        value_uri = label.find_value_uri(value)
        for_value_uri = for_label.find_value_uri(for_value)
        self.expression = expression.gsub(value_uri, for_value_uri)
        self.expression = expression.gsub(label.attribute.uri, for_label.attribute.uri)
      else
        for_value = for_label
        value_uri = label.find_value_uri(value)
        for_value_uri = label.find_value_uri(for_value)
        self.expression = expression.gsub(value_uri, for_value_uri)
      end
      self
    end

    # Looks up the readable values of the objects used inside of MAQL epxpressions. Labels and elements titles are based on the primary label.
    # @return [String] Ther resulting MAQL like expression
    def pretty_expression
      SmallGoodZilla.pretty_print(expression, client: client, project: project)
    end
  end
end
