#!/usr/bin/env ruby

require 'utils'
include Utils
require 'tins/xt'
include Tins::GO
require 'term/ansicolor'
include Term::ANSIColor
require 'tempfile'

def convert_ruby_to_vim_regexp(pattern)
  regexp = pattern.source.dup
  regexp.gsub!(/([^\\])\?/, '\1\\?')
  pattern.casefold? and regexp << '\c'
  regexp
end

def read_line(path)
  filename, lineno = path.source_location
  File.open(filename) do |file|
    file.lazy.each_with_index.select { |l, n| n + 1 == lineno }.first&.first
  end
end

def replace_line(path, new_line)
  filename, lineno = path.source_location
  Tempfile.open do |output|
    File.open(filename, 'r+') do |file|
      file.each_with_index do |line, n|
        if n + 1 == lineno
          output.write new_line
        else
          output.write line
        end
      end
      file.truncate(0)
      file.rewind
      output.rewind
      output.each do |line|
        file.write line
      end
    end
  end
end

def replace_ask(editor, path, pattern, replace, all)
  line = read_line path
  display_new_line = line.gsub(
    pattern.matcher,
    "#{on_red(line[pattern.matcher])}#{on_green(replace)}"
  )
  loop do
    puts red(path)
    puts display_new_line
    if all
      new_line = line.gsub(pattern.matcher, replace)
      replace_line path, new_line
      break true
    else
      print "Replace? (#{bold(?y)}/n/e/a/q) "
      case answer = STDIN.gets.chomp
      when ?y, '', ?a
        new_line = line.gsub(pattern.matcher, replace)
        replace_line path, new_line
        break answer == ?a
      when ?n
        break false
      when ?q
        throw :quit
      when ?e
        editor.edit(path.strip)
        break false
      else
        next
      end
    end
  end
end

def edit_files(pattern, paths, pick: false, replace: nil)
  editor = Utils::Editor.new
  editor.edit_remote_send("<ESC>/#{convert_ruby_to_vim_regexp(pattern)}<CR>")
  case
  when replace
    editor.wait = true
    all = false
    catch :quit do
      for path in paths
        all |= replace_ask editor, path, pattern, replace, all
      end
    end
  when pick
    if paths.size > 1
      path = complete(prompt: 'Pick? ') do |p|
        paths.grep(/#{p}/)
      end
    else
      path = paths.first
    end
    editor.edit(path.strip)
  else
    editor.wait = true
    for path in paths
      STDERR.puts "Edit #{path}"
      editor.edit(path)
    end
  end
end

def usage
  puts <<~EOT
    Usage: #{File.basename($0)} [OPTS] PATTERN [PATHS]

    PATTERN is a pattern expression which is used to match against the content of
    the files. PATHS are the directory and file paths that are searched.

    Options are

      -n PATTERN  only search files whose names match fuzzy PATTERN
      -N PATTERN  only search files whose names match regexp PATTERN
      -s PATTERN  skip lines that match fuzzy PATTERN
      -S PATTERN  skip lines that match regexp PATTERN
      -A NUMBER   displays NUMBER lines of context after the match
      -B NUMBER   displays NUMBER lines of context before the match
      -C NUMBER   displays NUMBER lines of context around the match
      -f          just list the paths of the files that would be searched
      -F          just consider real files when searching
      -l          just list the paths of the files with matches
      -L          list only the path:linenumber of the files with matches
      -pX         interpret PATTERN argument as X=f fuzzy or X=r for regexp
      -c          disable color output
      -iX         use case insensitive matches with X=y (default) or not with X=n
      -I SUFFIX   only include files with suffix SUFFIX in search
      -e          open the matching files with edit command
      -E          pick one file to edit
      -r REPLACE  replace the searched match with REPLACE
      -g          use git to determine author of the line
      -G AUTHOR   only display lines authored by AUTHOR
      -a CSET     use only character set CSET from PATTERN
      -v          be verbose
      -h          display this help

    Version is #{File.basename($0)} #{Utils::VERSION}.
  EOT
  exit 1
end

args = go 'r:p:I:A:B:C:s:S:n:N:a:i:G:cfFlLeEvgh'
args[?h] and usage
pattern = ARGV.shift or usage
roots = (ARGV.empty? ? [ Dir.pwd ] : ARGV).map { |f| File.expand_path(f) }

Term::ANSIColor.coloring = (STDIN.tty? && ENV['TERM'] !~ /dumb/) && !args[?c]
STDOUT.sync = true
config = Utils::ConfigFile.new
config.configure_from_paths
grepper = Grepper.new(
  :pattern => pattern,
  :args    => args,
  :roots   => roots,
  :config  => config
).search
case
when args[?r] then edit_files grepper.pattern, grepper.paths, replace: args[?r]
when args[?E] then edit_files grepper.pattern, grepper.paths, pick: true
when args[?e] then edit_files grepper.pattern, grepper.paths
when args[?l] then puts grepper.paths
end