# encoding: UTF-8
#
# Copyright (c) 2010-2015 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 'pathname'
require 'hashie'

require_relative 'global_helpers_params'

module GoodData
  module Helpers
    class DeepMergeableHash < Hash
      include Hashie::Extensions::DeepMerge
    end

    class << self
      def error(msg)
        STDERR.puts(msg)
        exit 1
      end

      # FIXME: Windows incompatible
      def find_goodfile(pwd = `pwd`.strip!, options = {})
        root = Pathname(options[:root] || '/')
        pwd = Pathname(pwd).expand_path
        loop do
          gf = pwd + 'Goodfile'
          return gf if gf.exist?
          pwd = pwd.parent
          break unless root == pwd
        end
        nil
      end

      def get_path(an_object, path = [])
        return an_object if path.empty?
        path.reduce(an_object) do |a, e|
          a && a.key?(e) ? a[e] : nil
        end
      end

      def last_uri_part(uri)
        uri.split('/').last
      end

      def home_directory
        running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
      end

      def hash_dfs(thing, &block)
        if !thing.is_a?(Hash) && !thing.is_a?(Array) # rubocop:disable Style/GuardClause
        elsif thing.is_a?(Array)
          thing.each do |child|
            hash_dfs(child, &block)
          end
        else
          thing.each do |key, val|
            yield(thing, key)
            hash_dfs(val, &block)
          end
        end
      end

      def titleize(str)
        titleized = str.gsub(/[\.|_](.)/, &:upcase)
        titleized = titleized.tr('_', ' ')
        titleized[0] = titleized[0].upcase
        titleized
      end

      def join(master, slave, on, on2, options = {})
        full_outer = options[:full_outer]
        inner = options[:inner]

        lookup = create_lookup(slave, on2)
        marked_lookup = {}
        results = master.reduce([]) do |a, line|
          matching_values = lookup[line.values_at(*on)] || []
          marked_lookup[line.values_at(*on)] = 1
          if matching_values.empty? && !inner
            a << line.to_hash
          else
            matching_values.each do |matching_value|
              a << matching_value.to_hash.merge(line.to_hash)
            end
          end
          a
        end

        if full_outer
          (lookup.keys - marked_lookup.keys).each do |key|
            puts lookup[key]
            results << lookup[key].first.to_hash
          end
        end
        results
      end

      def running_on_windows?
        RUBY_PLATFORM =~ /mswin32|mingw32/
      end

      def running_on_a_mac?
        RUBY_PLATFORM =~ /-darwin\d/
      end

      def underline(x)
        '=' * x.size
      end

      def interpolate_error_messages(errors)
        errors.map { |e| interpolate_error_message(e) }
      end

      def interpolate_error_message(error)
        message = error['error']['message']
        params = error['error']['parameters']
        sprintf(message, *params)
      end

      def transform_keys!(an_object)
        return enum_for(:transform_keys!) unless block_given?
        an_object.keys.each do |key|
          an_object[yield(key)] = an_object.delete(key)
        end
        an_object
      end

      def symbolize_keys!(an_object)
        transform_keys!(an_object) do |key|
          begin
            key.to_sym
          rescue
            key
          end
        end
      end

      def symbolize_keys(an_object)
        transform_keys(an_object) do |key|
          begin
            key.to_sym
          rescue
            key
          end
        end
      end

      def transform_keys(an_object)
        return enum_for(:transform_keys) unless block_given?
        result = an_object.class.new
        an_object.each_key do |key|
          result[yield(key)] = an_object[key]
        end
        result
      end

      def deep_symbolize_keys(an_object)
        deep_transform_keys(an_object) do |key|
          begin
            key.to_sym
          rescue
            key
          end
        end
      end

      def stringify_keys(an_object)
        transform_keys(an_object, &:to_s)
      end

      def deep_stringify_keys(an_object)
        deep_transform_keys(an_object, &:to_s)
      end

      def deep_transform_keys(an_object, &block)
        _deep_transform_keys_in_object(an_object, &block)
      end

      def _deep_transform_keys_in_object(object, &block)
        case object
        when Hash
          object.each_with_object({}) do |(key, value), result|
            result[yield(key)] = _deep_transform_keys_in_object(value, &block)
          end
        when Array
          object.map { |e| _deep_transform_keys_in_object(e, &block) }
        else
          object
        end
      end

      def deep_dup(an_object)
        case an_object
        when Array
          an_object.map { |it| GoodData::Helpers.deep_dup(it) }
        when Hash
          an_object.each_with_object(an_object.dup) do |(key, value), hash|
            hash[GoodData::Helpers.deep_dup(key)] = GoodData::Helpers.deep_dup(value)
          end
        when Object
          an_object.duplicable? ? an_object.dup : an_object
        end
      end

      def undot(params)
        # for each key-value config given
        params.map do |k, v|
          # dot notation to hash
          k.split('__').reverse.reduce(v) do |memo, obj|
            GoodData::Helper.DeepMergeableHash[{ obj => memo }]
          end
        end
      end

      # Creates a matrix with zeroes in all places. It is implemented as an Array of Arrays. First rows then columns.
      #
      # @param [Integer] m Number of rows
      # @param [Integer] n Number of cols
      # @param [Integer] val Alternatively can fill in positions with different values than zeroes. Defualt is zero.
      # @return [Array<Array>] Returns a matrix of zeroes
      def zeroes(m, n, val = 0)
        m.times.map { n.times.map { val } }
      end
    end
  end
end
