module ClarkKent
	module Reportable
    def self.included(base)
      base.extend ClassMethods
    end

	  module ClassMethods

	    def chain_up(query, params)
	      params.each do |key,val|
	        arel_method_name = self.arel_method_for(key)
	        if arel_method_name.present? and self.respond_to? arel_method_name and val.present?
	          query = self.send(arel_method_name, query, key, val)
	        end
	      end
	      query = query.distinct if params[:distinct]
	      query
	    end

	    def required_date_params
	    	self::REPORT_FILTER_OPTIONS.select{|rfo| rfo.in_required_date_group}.map{|rfo| rfo.filter_params}.flatten.map(&:to_sym)
	    end

	    def validate_params(params,report)
				binding.pry
	    	if required_date_params.any?
	    		missing_params = required_date_params - params.select{|k,v| v.present? }.symbolize_keys.keys
	    		# a bit clunky, it only requires any 2 date filters. It would be better to require at least one pair of before/after filters
	    		if missing_params.length > (required_date_params.length - 2)
			    	raise ClarkKent::ReportFilterError.new("At least one date range is required.")
			    end
			  end
	    end

		  def report(params,report,count = false)
		  	@selects = []
		  	@includes = []
		  	@joins = []
		  	@extra_scopes = []
		  	@extra_filters = []
		  	@groups = []
		  	if 'ClarkKent::ReportEmail' == report.class.name
		  		@report_email = report
		  		report = @report_email.report
		  	end
		  	if count == false
					report.select_clauses.each do |select_clause|
						@selects.push select_clause
				  end
				end
		    report.arel_includes.each do |arel_include|
		    	@includes.push arel_include
		    end
		    report.arel_joins.each do |arel_join|
		    	@joins.push arel_join
		    end
		    report.extra_scopes.each do |extra_scope|
		    	@extra_scopes.push extra_scope
		    end
		    report.extra_filters.each do |extra_filter|
		    	@extra_filters.push extra_filter
		    end
		    report.groups.each do |grouper|
		    	@groups.push grouper
		    end
		    query = self.all
		    if @report_email and @report_email.is_a? ClarkKent::ReportEmail
					params = @report_email.report_filter_params.symbolize_keys!.merge(params.symbolize_keys)
		    else
					params = report.report_filter_params.symbolize_keys!.merge(params.symbolize_keys)
				end
		  	validate_params(params, report)
		    params.each do |param_type,param_value|
		      if param_value.present?
	          arel_method_name = self.arel_method_for(param_type)
		        if arel_method_name.present?
		          query = self.send(arel_method_name, query, param_type, param_value)
		          if report_column_options = column_options_for(param_type)
			          if(report_column_options.joins.present?) && (@joins.exclude? report_column_options.joins)
			          	@joins.push report_column_options.joins
			          end
								if(report_column_options.includes.present?) && (@includes.exclude? report_column_options.includes)
									@includes.push report_column_options.includes
			          end
			          if (count == false) && (report_column_options.custom_select.present?) && (@selects.exclude? report_column_options.custom_select)
			          	@selects.push report_column_options.custom_select
			          end
			        end
		        end
		      end
		    end
		    if @selects.any?
		    	query = query.select("DISTINCT " + self.column_names.map{|cn| self.table_name + '.' + cn}.join(', '))
		    	@selects.uniq.each do |selectable|
			    	query = query.select(selectable)
			    end
			  end
			  if self.respond_to? :clark_kent_required_filters
			  	query = self.send(:clark_kent_required_filters, query)
			  end
	    	@includes.uniq.each do |includeable|
		    	query = query.includes(includeable)
		    end if @includes.any?
	    	@extra_scopes.uniq.each do |extra_scope|
		    	query = query.send(extra_scope)
		    end if @extra_scopes.any?
	    	@extra_filters.uniq.each do |extra_filter|
		    	query = query.where(extra_filter)
		    end if @extra_filters.any?
	    	@joins.uniq.each do |joinable|
		    	query = query.joins(joinable).uniq
		    end if @joins.any?
	    	@groups.uniq.each do |grouper|
		    	query = query.group(grouper)
		    end if @groups.any?
		    if count == true
		    	return query.count
				else
					return query
				end

		  end

		  def column_options_for(column_name)
				self::REPORT_COLUMN_OPTIONS.detect{|co| column_name == co.name}
		  end

		  def arel_method_for(param_type)
		    method_name = self::AREL_METHODS[param_type.to_s]
		    method_name ||= "#{param_type}_arel" if self.respond_to? "#{param_type}_arel"
		    method_name
		  end

		  def simple_equality_arel(query, field_name, match_value)
		    query.
		    where(field_name.to_sym => match_value)
		  end

		  def before_date_arel(query, field_name, match_value)
		  	query.
		  	where("#{self.table_name}.#{field_name.to_s.sub(/_until/,'')} <= :date_limit", date_limit: match_value)
		  end

		  def after_date_arel(query, field_name, match_value)
		  	query.
		  	where("#{self.table_name}.#{field_name.to_s.sub(/_from/,'')} >= :date_limit", date_limit: match_value)
		  end

		  def above_number_arel(query, field_name, match_value)
		  	query.
		  	where("#{self.table_name}.#{field_name.to_s.sub(/_above/,'')} >= :lower_limit", lower_limit: match_value)
		  end

		  def below_number_arel(query, field_name, match_value)
		  	query.
		  	where("#{self.table_name}.#{field_name.to_s.sub(/_below/,'')} <= :upper_limit", upper_limit: match_value)
		  end

		  def order_arel(query, field_name, match_value)
		  	if match_value.is_a? ClarkKent::ReportSort
			  	order_column = match_value.order_column
			  	order_direction = match_value.order_direction
			  else
			  	order_column, order_direction = match_value.split('-')
			  end
				column_info = column_options_for(order_column.to_sym)
				if column_info.respond_to?(:order_sql) && column_info.order_sql.present?
				  order_sql = column_info.order_sql
			  	order_sql = "#{order_sql} #{order_direction}"
			  	query = query.order(order_sql)
			  	if column_info.respond_to? :includes
			  		order_includes = column_info.includes
		  			query = query.includes(order_includes).references(order_includes)
		  		end
		  		query
			  else
			  	query
			  end
		  end

		end
	end
end