# coding=utf-8
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import absolute_import, division, print_function, unicode_literals

import os

from pants.base.build_environment import get_buildroot
from pants.util.contextutil import open_zip, temporary_dir
from pants.util.process_handler import subprocess
from pants_test.pants_run_integration_test import PantsRunIntegrationTest


class BinaryCreateIntegrationTest(PantsRunIntegrationTest):

  def test_autovalue_classfiles(self):
    self.build_and_run(
      pants_args=['binary', 'examples/src/java/org/pantsbuild/example/autovalue'],
      rel_out_path='dist',
      java_args=['-jar', 'autovalue.jar'],
      expected_output='Hello Autovalue!'
    )

  def test_manifest_entries(self):
    self.build_and_run(
      pants_args=['binary',
                  'testprojects/src/java/org/pantsbuild/testproject/manifest:manifest-with-source'],
      rel_out_path='dist',
      java_args=['-cp', 'manifest-with-source.jar', 'org.pantsbuild.testproject.manifest.Manifest'],
      expected_output='Hello World!  Version: 1.2.3'
    )

  def test_manifest_entries_no_source(self):
    self.build_and_run(
      pants_args=['binary',
                  'testprojects/src/java/org/pantsbuild/testproject/manifest:manifest-no-source'],
      rel_out_path='dist',
      java_args=['-cp', 'manifest-no-source.jar', 'org.pantsbuild.testproject.manifest.Manifest'],
      expected_output='Hello World!  Version: 4.5.6',
    )

  def test_manifest_entries_bundle(self):
    # package level manifest entry, in this case, `Implementation-Version`, no longer work
    # because package files are not included in the bundle jar, instead they are referenced
    # through its manifest's Class-Path.
    self.build_and_run(
      pants_args=['bundle',
                  'testprojects/src/java/org/pantsbuild/testproject/manifest:manifest-app'],
      rel_out_path=os.path.join('dist', ('testprojects.src.java.org.pantsbuild.testproject'
                                         '.manifest.manifest-app-bundle')),
      java_args=['-cp', 'manifest-no-source.jar', 'org.pantsbuild.testproject.manifest.Manifest'],
      expected_output='Hello World!  Version: null',
    )

    # If we still want to get package level manifest entries, we need to include packages files
    # in the bundle jar through `--deployjar`. However use that with caution because the monolithic
    # jar may have multiple packages.
    self.build_and_run(
      pants_args=['bundle',
                  'testprojects/src/java/org/pantsbuild/testproject/manifest:manifest-app',
                  '--bundle-jvm-deployjar'],
      rel_out_path=os.path.join('dist', ('testprojects.src.java.org.pantsbuild.testproject'
                                         '.manifest.manifest-app-bundle')),
      java_args=['-cp', 'manifest-no-source.jar', 'org.pantsbuild.testproject.manifest.Manifest'],
      expected_output='Hello World!  Version: 4.5.6',
    )

  def test_agent_dependency(self):
    directory = "testprojects/src/java/org/pantsbuild/testproject/manifest"
    target = "{}:manifest-with-agent".format(directory)
    with self.temporary_workdir() as workdir:
      pants_run = self.run_pants_with_workdir(["binary", target], workdir=workdir)
      self.assert_success(pants_run)
      jar = "dist/manifest-with-agent.jar"
      with open_zip(jar, mode='r') as j:
        with j.open("META-INF/MANIFEST.MF") as jar_entry:
          normalized_lines = (line.decode('utf-8').strip() for line in jar_entry.readlines() if line.strip())
          entries = {tuple(line.split(": ", 2)) for line in normalized_lines}
          self.assertIn(('Agent-Class', 'org.pantsbuild.testproject.manifest.Agent'), entries)

  def test_deploy_excludes(self):
    with temporary_dir() as distdir:
      def build(name):
        jar_filename = os.path.join(distdir, '{}.jar'.format(name))
        command = [
          '--pants-distdir={}'.format(distdir),
          '--no-compile-zinc-capture-classpath',
          'binary',
          'testprojects/src/java/org/pantsbuild/testproject/deployexcludes:{}'.format(name),
        ]
        self.assert_success(self.run_pants(command))
        return jar_filename

      # The excluded binary should not contain any guava classes, and should fail to run.
      jar_filename = build('deployexcludes')
      with open_zip(jar_filename) as jar_file:
        self.assertEqual({'META-INF/',
                           'META-INF/MANIFEST.MF',
                           'org/',
                           'org/pantsbuild/',
                           'org/pantsbuild/testproject/',
                           'org/pantsbuild/testproject/deployexcludes/',
                           'org/pantsbuild/testproject/deployexcludes/DeployExcludesMain.class'},
                          set(jar_file.namelist()))
      self.run_java(java_args=['-jar', jar_filename],
                    expected_returncode=1,
                    expected_output='java.lang.NoClassDefFoundError: '
                                    'com/google/common/collect/ImmutableSortedSet')

      # And the non excluded binary should succeed.
      jar_filename = build('nodeployexcludes')
      self.run_java(java_args=['-jar', jar_filename],
                    expected_output='DeployExcludes Hello World')

  def build_and_run(self, pants_args, rel_out_path, java_args, expected_output):
    self.assert_success(self.run_pants(['clean-all']))
    with self.pants_results(pants_args, {}) as pants_run:
      self.assert_success(pants_run)

      out_path = os.path.join(get_buildroot(), rel_out_path)
      self.run_java(java_args=java_args, expected_output=expected_output, cwd=out_path)

  def run_java(self, java_args, expected_returncode=0, expected_output=None, cwd=None):
    command = ['java'] + java_args
    process = subprocess.Popen(command,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE,
                               cwd=cwd)
    stdout, stderr = process.communicate()
    stdout = stdout.decode('utf-8')
    stderr = stderr.decode('utf-8')

    self.assertEqual(expected_returncode, process.returncode,
                      ('Expected exit code {} from command `{}` but got {}:\n'
                       'stdout:\n{}\n'
                       'stderr:\n{}'
                       .format(expected_returncode,
                               ' '.join(command),
                               process.returncode,
                               stdout,
                               stderr)))
    self.assertIn(expected_output, stdout if expected_returncode == 0 else stderr)
