#!/usr/bin/ruby
# encoding: UTF-8

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

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
  line = 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
  File.secure_write filename do |output|
    File.open(filename) do |file|
      file.each_with_index do |line, n|
        if n + 1 == lineno
          output.write new_line
        else
          output.write line
        end
      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) "
      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 ?e
        editor.edit(path.strip)
        break false
      when ?a
        break true
      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
    for path in paths
      all |= replace_ask editor, path, pattern, replace, all
    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
  -b          also search binary files
  -g          use git to determine author of the line
  -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:cfFlLeEvbgh'
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