# Copyright 2015 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.

import os
import re
from util import build_utils


class _ProguardOutputFilter(object):
  """ProGuard outputs boring stuff to stdout (proguard version, jar path, etc)
  as well as interesting stuff (notes, warnings, etc). If stdout is entirely
  boring, this class suppresses the output.
  """

  IGNORE_RE = re.compile(
      r'(?:Pro.*version|Note:|Reading|Preparing|.*:.*(?:MANIFEST\.MF|\.empty))')

  def __init__(self):
    self._last_line_ignored = False

  def __call__(self, output):
    ret = []
    for line in output.splitlines(True):
      if not line.startswith(' '):
        self._last_line_ignored = bool(self.IGNORE_RE.match(line))
      elif 'You should check if you need to specify' in line:
        self._last_line_ignored = True

      if not self._last_line_ignored:
        ret.append(line)
    return ''.join(ret)


class ProguardCmdBuilder(object):
  def __init__(self, proguard_jar):
    assert os.path.exists(proguard_jar)
    self._proguard_jar_path = proguard_jar
    self._tested_apk_info_path = None
    self._tested_apk_info = None
    self._mapping = None
    self._libraries = None
    self._injars = None
    self._configs = None
    self._outjar = None
    self._cmd = None
    self._verbose = False
    self._disabled_optimizations = []

  def outjar(self, path):
    assert self._cmd is None
    assert self._outjar is None
    self._outjar = path

  def tested_apk_info(self, tested_apk_info_path):
    assert self._cmd is None
    assert self._tested_apk_info is None
    self._tested_apk_info_path = tested_apk_info_path

  def mapping(self, path):
    assert self._cmd is None
    assert self._mapping is None
    assert os.path.exists(path), path
    self._mapping = path

  def libraryjars(self, paths):
    assert self._cmd is None
    assert self._libraries is None
    for p in paths:
      assert os.path.exists(p), p
    self._libraries = paths

  def injars(self, paths):
    assert self._cmd is None
    assert self._injars is None
    for p in paths:
      assert os.path.exists(p), p
    self._injars = paths

  def configs(self, paths):
    assert self._cmd is None
    assert self._configs is None
    for p in paths:
      assert os.path.exists(p), p
    self._configs = paths

  def verbose(self, verbose):
    assert self._cmd is None
    self._verbose = verbose

  def disable_optimizations(self, optimizations):
    assert self._cmd is None
    self._disabled_optimizations += optimizations

  def build(self):
    if self._cmd:
      return self._cmd
    assert self._injars is not None
    assert self._outjar is not None
    assert self._configs is not None
    cmd = [
      'java', '-jar', self._proguard_jar_path,
      '-forceprocessing',
    ]
    if self._tested_apk_info_path:
      tested_apk_info = build_utils.ReadJson(self._tested_apk_info_path)
      self._configs += tested_apk_info['configs']

    if self._mapping:
      cmd += [
        '-applymapping', self._mapping,
      ]

    if self._libraries:
      cmd += [
        '-libraryjars', ':'.join(self._libraries),
      ]

    for optimization in self._disabled_optimizations:
      cmd += [ '-optimizations', '!' + optimization ]

    cmd += [
      '-injars', ':'.join(self._injars)
    ]

    for config_file in self._configs:
      cmd += ['-include', config_file]

    # The output jar must be specified after inputs.
    cmd += [
      '-outjars', self._outjar,
      '-dump', self._outjar + '.dump',
      '-printseeds', self._outjar + '.seeds',
      '-printusage', self._outjar + '.usage',
      '-printmapping', self._outjar + '.mapping',
    ]

    if self._verbose:
      cmd.append('-verbose')

    self._cmd = cmd
    return self._cmd

  def GetInputs(self):
    self.build()
    inputs = [self._proguard_jar_path] + self._configs + self._injars
    if self._mapping:
      inputs.append(self._mapping)
    if self._libraries:
      inputs += self._libraries
    if self._tested_apk_info_path:
      inputs += [self._tested_apk_info_path]
    return inputs


  def CheckOutput(self):
    self.build()
    # Proguard will skip writing these files if they would be empty. Create
    # empty versions of them all now so that they are updated as the build
    # expects.
    open(self._outjar + '.dump', 'w').close()
    open(self._outjar + '.seeds', 'w').close()
    open(self._outjar + '.usage', 'w').close()
    open(self._outjar + '.mapping', 'w').close()
    # Warning: and Error: are sent to stderr, but messages and Note: are sent
    # to stdout.
    stdout_filter = None
    stderr_filter = None
    if not self._verbose:
      stdout_filter = _ProguardOutputFilter()
      stderr_filter = _ProguardOutputFilter()
    build_utils.CheckOutput(self._cmd, print_stdout=True,
                            print_stderr=True,
                            stdout_filter=stdout_filter,
                            stderr_filter=stderr_filter)

    this_info = {
      'inputs': self._injars,
      'configs': self._configs,
      'mapping': self._outjar + '.mapping',
    }

    build_utils.WriteJson(this_info, self._outjar + '.info')

