#!/usr/bin/env python
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Command processor for GRIT.  This is the script you invoke to run the various
GRIT tools.
"""

import os
import sys
if __name__ == '__main__':
  sys.path[0] = os.path.abspath(os.path.join(sys.path[0], '..'))

import getopt

from grit import util

import grit.extern.FP

# Tool info factories; these import only within each factory to avoid
# importing most of the GRIT code until required.
def ToolFactoryBuild():
  import grit.tool.build
  return grit.tool.build.RcBuilder()

def ToolFactoryBuildInfo():
  import grit.tool.buildinfo
  return grit.tool.buildinfo.DetermineBuildInfo()

def ToolFactoryCount():
  import grit.tool.count
  return grit.tool.count.CountMessage()

def ToolFactoryDiffStructures():
  import grit.tool.diff_structures
  return grit.tool.diff_structures.DiffStructures()

def ToolFactoryMenuTranslationsFromParts():
  import grit.tool.menu_from_parts
  return grit.tool.menu_from_parts.MenuTranslationsFromParts()

def ToolFactoryNewGrd():
  import grit.tool.newgrd
  return grit.tool.newgrd.NewGrd()

def ToolFactoryResizeDialog():
  import grit.tool.resize
  return grit.tool.resize.ResizeDialog()

def ToolFactoryRc2Grd():
  import grit.tool.rc2grd
  return grit.tool.rc2grd.Rc2Grd()

def ToolFactoryTest():
  import grit.tool.test
  return grit.tool.test.TestTool()

def ToolFactoryTranslationToTc():
  import grit.tool.transl2tc
  return grit.tool.transl2tc.TranslationToTc()

def ToolFactoryUnit():
  import grit.tool.unit
  return grit.tool.unit.UnitTestTool()

def ToolFactoryXmb():
  import grit.tool.xmb
  return grit.tool.xmb.OutputXmb()

def ToolAndroid2Grd():
  import grit.tool.android2grd
  return grit.tool.android2grd.Android2Grd()

# Keys for the following map
_FACTORY = 1
_REQUIRES_INPUT = 2
_HIDDEN = 3  # optional key - presence indicates tool is hidden

# Maps tool names to the tool's module.  Done as a list of (key, value) tuples
# instead of a map to preserve ordering.
_TOOLS = [
  ['build', { _FACTORY : ToolFactoryBuild, _REQUIRES_INPUT : True }],
  ['buildinfo', { _FACTORY : ToolFactoryBuildInfo, _REQUIRES_INPUT : True }],
  ['count', { _FACTORY : ToolFactoryCount, _REQUIRES_INPUT : True }],
  ['menufromparts', {
      _FACTORY: ToolFactoryMenuTranslationsFromParts,
      _REQUIRES_INPUT : True, _HIDDEN : True }],
  ['newgrd', { _FACTORY  : ToolFactoryNewGrd, _REQUIRES_INPUT : False }],
  ['rc2grd', { _FACTORY : ToolFactoryRc2Grd, _REQUIRES_INPUT : False }],
  ['resize', {
      _FACTORY : ToolFactoryResizeDialog, _REQUIRES_INPUT : True }],
  ['sdiff', { _FACTORY : ToolFactoryDiffStructures,
              _REQUIRES_INPUT : False }],
  ['test', {
      _FACTORY: ToolFactoryTest, _REQUIRES_INPUT : True,
      _HIDDEN : True }],
  ['transl2tc', { _FACTORY : ToolFactoryTranslationToTc,
                  _REQUIRES_INPUT : False }],
  ['unit', { _FACTORY : ToolFactoryUnit, _REQUIRES_INPUT : False }],
  ['xmb', { _FACTORY : ToolFactoryXmb, _REQUIRES_INPUT : True }],
  ['android2grd', {
      _FACTORY: ToolAndroid2Grd,
      _REQUIRES_INPUT : False }],
]


def PrintUsage():
  tool_list = ''
  for (tool, info) in _TOOLS:
    if not _HIDDEN in info.keys():
      tool_list += '    %-12s %s\n' % (tool, info[_FACTORY]().ShortDescription())

  # TODO(joi) Put these back into the usage when appropriate:
  #
  #  -d    Work disconnected.  This causes GRIT not to attempt connections with
  #        e.g. Perforce.
  #
  #  -c    Use the specified Perforce CLIENT when talking to Perforce.
  print """GRIT - the Google Resource and Internationalization Tool

Usage: grit [GLOBALOPTIONS] TOOL [args to tool]

Global options:

  -i INPUT  Specifies the INPUT file to use (a .grd file).  If this is not
            specified, GRIT will look for the environment variable GRIT_INPUT.
            If it is not present either, GRIT will try to find an input file
            named 'resource.grd' in the current working directory.

  -h MODULE Causes GRIT to use MODULE.UnsignedFingerPrint instead of
            grit.extern.FP.UnsignedFingerprint.  MODULE must be
            available somewhere in the PYTHONPATH search path.

  -v        Print more verbose runtime information.

  -x        Print extremely verbose runtime information.  Implies -v

  -p FNAME  Specifies that GRIT should profile its execution and output the
            results to the file FNAME.

Tools:

  TOOL can be one of the following:
%s
  For more information on how to use a particular tool, and the specific
  arguments you can send to that tool, execute 'grit help TOOL'
""" % (tool_list)


class Options(object):
  """Option storage and parsing."""

  def __init__(self):
    self.disconnected = False
    self.client = ''
    self.hash = None
    self.input = None
    self.verbose = False
    self.extra_verbose = False
    self.output_stream = sys.stdout
    self.profile_dest = None
    self.psyco = False

  def ReadOptions(self, args):
    """Reads options from the start of args and returns the remainder."""
    (opts, args) = getopt.getopt(args, 'g:qdvxc:i:p:h:', ('psyco',))
    for (key, val) in opts:
      if key == '-d': self.disconnected = True
      elif key == '-c': self.client = val
      elif key == '-h': self.hash = val
      elif key == '-i': self.input = val
      elif key == '-v':
        self.verbose = True
        util.verbose = True
      elif key == '-x':
        self.verbose = True
        util.verbose = True
        self.extra_verbose = True
        util.extra_verbose = True
      elif key == '-p': self.profile_dest = val
      elif key == '--psyco': self.psyco = True

    if not self.input:
      if 'GRIT_INPUT' in os.environ:
        self.input = os.environ['GRIT_INPUT']
      else:
        self.input = 'resource.grd'

    return args

  def __repr__(self):
    return '(disconnected: %d, verbose: %d, client: %s, input: %s)' % (
        self.disconnected, self.verbose, self.client, self.input)


def _GetToolInfo(tool):
  """Returns the info map for the tool named 'tool' or None if there is no
  such tool."""
  matches = [t for t in _TOOLS if t[0] == tool]
  if not matches:
    return None
  else:
    return matches[0][1]


def Main(args):
  """Parses arguments and does the appropriate thing."""
  util.ChangeStdoutEncoding()

  if sys.version_info < (2, 6):
    print "GRIT requires Python 2.6 or later."
    return 2
  elif not args or (len(args) == 1 and args[0] == 'help'):
    PrintUsage()
    return 0
  elif len(args) == 2 and args[0] == 'help':
    tool = args[1].lower()
    if not _GetToolInfo(tool):
      print "No such tool.  Try running 'grit help' for a list of tools."
      return 2

    print ("Help for 'grit %s' (for general help, run 'grit help'):\n"
           % (tool))
    print _GetToolInfo(tool)[_FACTORY]().__doc__
    return 0
  else:
    options = Options()
    args = options.ReadOptions(args)  # args may be shorter after this
    if not args:
      print "No tool provided.  Try running 'grit help' for a list of tools."
      return 2
    tool = args[0]
    if not _GetToolInfo(tool):
      print "No such tool.  Try running 'grit help' for a list of tools."
      return 2

    try:
      if _GetToolInfo(tool)[_REQUIRES_INPUT]:
        os.stat(options.input)
    except OSError:
      print ('Input file %s not found.\n'
             'To specify a different input file:\n'
             '  1. Use the GRIT_INPUT environment variable.\n'
             '  2. Use the -i command-line option.  This overrides '
             'GRIT_INPUT.\n'
             '  3. Specify neither GRIT_INPUT or -i and GRIT will try to load '
             "'resource.grd'\n"
             '     from the current directory.' % options.input)
      return 2

    if options.psyco:
      # Psyco is a specializing JIT for Python.  Early tests indicate that it
      # could speed up GRIT (at the expense of more memory) for large GRIT
      # compilations.  See http://psyco.sourceforge.net/
      import psyco
      psyco.profile()

    if options.hash:
      grit.extern.FP.UseUnsignedFingerPrintFromModule(options.hash)

    toolobject = _GetToolInfo(tool)[_FACTORY]()
    if options.profile_dest:
      import hotshot
      prof = hotshot.Profile(options.profile_dest)
      prof.runcall(toolobject.Run, options, args[1:])
    else:
      toolobject.Run(options, args[1:])


if __name__ == '__main__':
  sys.exit(Main(sys.argv[1:]))
