#!/usr/bin/python

# For Python 2.5
from __future__ import with_statement

import optparse
import os
import sys
BASE_PATH = os.path.dirname(__file__)
sys.path.insert(0, os.path.join(BASE_PATH, '..'))
import misspellings_lib as misspellings


class Suggestions(object):
  """Class to query user on which correction should be used."""
  def __init__(self):
    self.last_suggestions = {}

  def _get_a_line(self, filename, lineno):
    # Perhaps caching this would be nice, but assuming not an insane
    # number of misspellings.
    return open(filename, 'r').readlines()[lineno - 1].rstrip()

  def get_suggestion(self, filename, lineno, word, suggestions):
    if word not in self.last_suggestions:
      self.last_suggestions[word] = suggestions[0]
    line = self._get_a_line(filename, lineno)
    sys.stdout.write('> %s\nReplace "%s" with one of %s\nChoose [%s]:'
                     % (line, word, ','.join(suggestions),
                        self.last_suggestions[word]))
    suggestion = sys.stdin.readline().strip()
    if not suggestion:
      suggestion = self.last_suggestions[word]
    else:
      self.last_suggestions[word] = suggestion
    return suggestion


def print_file_context(fn, target_line_num, context=5):
  line_num = 1
  start_line = target_line_num - context
  end_line = target_line_num + context
  with open(fn, 'r') as f:
    for line in f:
      if (line_num > start_line) and (line_num < end_line):
        if line_num == target_line_num:
          sys.stdout.write('+%5d %s' % (line_num, line))
        else:
          sys.stdout.write(' %5d %s' % (line_num, line))
      line_num += 1


def cmp_rev_line(a, b):
  file_cmp = cmp(a[0], b[0])
  if file_cmp == 0:
    return cmp(b[1], a[1])
  return file_cmp


def parse_file_list(filename):
  f = sys.stdin
  try:
    if filename != '-':
      f = open(filename, 'r')
    return [line.strip() for line in f]
  except IOError:
    raise
  finally:
    if f != sys.stdin:
      f.close()


def esc_sed(s):
  return s.replace('"', '\\"').replace('/', '\\/')


def esc_file(s):
  return s.replace('"', '\\"')


# Output routines.
def output_normal(ms):
  errors, results = ms.check()
  for res in results:
    print('%s[%d]: %s -> %s' % (res[0], res[1], res[2], ','.join(
      ['"%s"' % w for w in ms.suggestions(res[2])])))
  for err in errors:
    print('ERROR: %s' % err)


def output_sed_script(ms, parser, opts):
  errors, results = ms.check()
  if os.path.exists(opts.script_output):
    # Emit an error is the file already exists in case the user
    # forgets to give the file - but does give source files.
    parser.error('The sed script file "%s" must not exist.'
                 % opts.script_output)
  sed_script = open(opts.script_output, 'w')
  sg = Suggestions()
  for res in results:
    suggestions = ms.suggestions(res[2])
    if len(suggestions) == 1:
      suggestion = suggestions[0]
    else:
      suggestion = sg.get_suggestion(res[0], res[1], res[2], suggestions)
    if suggestion != res[2]:
      sed_script.write('sed "%(lc)ds/%(wrd)s/%(rep)s/" "%(f)s" > "%(f)s,"\n'
                       'mv "%(f)s," "%(f)s"\n'
                       % {'f': esc_file(res[0]), 'lc': res[1],
                          'wrd': esc_sed(res[2]), 'rep': esc_sed(suggestion)})
  for err in errors:
    print('ERROR: %s' % err)
  sed_script.close()


def main():
  parser = optparse.OptionParser(
      usage='Usage: misspellings [-f file_list] [files]',
      description='Checks files for common spelling mistakes.',
      epilog='  files: Zero or more files to check.',
      add_help_option=False)
  parser.add_option('-v', '--version', action='store_true',
                    help='Show the version number and exit.')
  parser.add_option('-h', '--help', action='help',
                    help='Show this help message and exit.')
  parser.add_option('-f', dest='file_list', metavar='file',
                    help='File containing list of files to check.')
  parser.add_option('-m', dest='ms_file', metavar='file',
                    help='File containing list of misspelled words &'
                         ' corrections.')
  parser.add_option('-d', dest='dump_ms', action='store_true',
                    help='Dump the list of misspelled words.')
  parser.add_option('-i', dest='interactive', action='store_true',
                    help='Interactively fix the file.')
  parser.add_option('-s', dest='script_output', metavar='file',
                    help='Create a shell script to interactively correct'
                         ' the files - script saved to the given file.')
  opts, files = parser.parse_args()

  if opts.version:
    print('Version 1.4')
    return 0
  elif opts.file_list:
    try:
      files += parse_file_list(opts.file_list)
    except IOError:
      print('ERROR: %s' % sys.exc_info()[1])
      return 1

  ms = misspellings.Misspellings(files=files,
                                 misspelling_file=opts.ms_file)
  if opts.dump_ms:
    for w, c in ms.dumpMisspellingList():
      print('%s %s' % (w, c))
  if not opts.interactive:
    if not opts.script_output:
      output_normal(ms)
    else:
      output_sed_script(ms, parser, opts)
  else:
    print('Interactive mode not implemented yet.')
    results.sort(cmp=cmp_rev_line)
    for res in results:
      print_file_context(res[0], res[1])
      print('%s -> "%s"' % (res[2], ','.join(
        ['"%s"' % w for w in ms.suggestions(res[2])])))

if __name__ == '__main__':
  sys.exit(main())
