# encoding: utf-8
MetricFu.lib_require       { 'utility'                 }
MetricFu.metrics_require   { 'saikuro/scratch_file'    }
MetricFu.metrics_require   { 'saikuro/parsing_element' }
module MetricFu

  class SaikuroGenerator < MetricFu::Generator

    def self.metric
      :saikuro
    end

    def emit
      options_string = options.inject("") do |options, option|
        option[0] == :input_directory ? options : options + "--#{option.join(' ')} "
      end

      options[:input_directory].each do |input_dir|
        options_string += "--input_directory #{input_dir} "
      end

      @output = run!(options_string)
    end

    def analyze
      @files = sort_files(assemble_files)
      @classes = sort_classes(assemble_classes(@files))
      @meths = sort_methods(assemble_methods(@files))
    end

    def to_h
      @saikuro_data = {
        files:    files_with_relative_paths(@files),
        classes:  @classes.map { |c| c.to_h },
        methods:  @meths.map   { |m| m.to_h },
       }
      clear_scratch_files!
      {:saikuro => @saikuro_data}
    end

    def per_file_info(out)
      @saikuro_data[:files].each do |file_data|
        filename = file_data[:filename]
        next if erb_file?(filename) || file_not_exists?(filename)
        next unless line_numbers = line_numbers_from_file(filename)

        build_output_from_line_numbers(out, line_numbers, file_data)
      end
    end

    def build_output_from_line_numbers(out, line_numbers, file_data)
      filename = file_data[:filename]
      output = out[filename]
      method_data_for_file_data(file_data) do |method_data|
         line = line_numbers.start_line_for_method(method_data[:name]).to_s
         result = {
           :type => :saikuro,
           :description => "Complexity #{method_data[:complexity]}"
         }
         output[line] <<  result
      end
      out
    end

    def line_numbers_from_file(filename)
      MetricFu::LineNumbers.new(File.read(filename))
    rescue StandardError => e
      raise e unless e.message =~ /you shouldn't be able to get here/
      mf_log "ruby_parser blew up while trying to parse #{file_path}. You won't have method level Saikuro information for this file."
    end

    def method_data_for_file_data(file_data, &block)
      return unless block_given?
      file_data[:classes].each do |class_data|
        class_data[:methods].each do |method_data|
          yield method_data
        end
      end
    end

    private

    def erb_file?(filename)
      File.extname(filename) == '.erb'
    end

    def file_not_exists?(filename)
      !File.exists?(filename)
    end

    def sort_methods(methods)
      methods.sort_by {|method| method.complexity.to_i}.reverse
    end

    def assemble_methods(files)
      methods = []
      files.each do |file|
        file.elements.each do |element|
          element.defs.each do |defn|
            defn.name = "#{element.name}##{defn.name}"
            methods << defn
          end
        end
      end
      methods
    end

    def sort_classes(classes)
      classes.sort_by {|k| k.complexity.to_i}.reverse
    end

    def assemble_classes(files)
      files.map {|f| f.elements}.flatten
    end

    def sort_files(files)
      files.sort_by do |file|
        file.elements.
             max {|a,b| a.complexity.to_i <=> b.complexity.to_i}.
             complexity.to_i
      end.reverse
    end

    def assemble_files
      SaikuroScratchFile.assemble_files( Dir.glob("#{metric_directory}/**/*.html") )
    end

    def files_with_relative_paths(files)
      files.map do |file|
        file_hash = file.to_h
        file_hash[:filename] = file_relative_path(file)
        file_hash
      end.to_a
    end

    def file_relative_path(file)
      filepath = file.filepath
      path = filepath.gsub(/^#{metric_directory}\//, '')
      "#{path}/#{file.filename}"
    end

    def clear_scratch_files!
      MetricFu::Utility.rm_rf(metric_directory)
    end

  end

end