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

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import os
import unittest

from py_vulcanize import fake_fs
from py_vulcanize import generate
from py_vulcanize import html_generation_controller
from py_vulcanize import html_module
from py_vulcanize import parse_html_deps
from py_vulcanize import project as project_module
from py_vulcanize import resource
from py_vulcanize import resource_loader as resource_loader
import six


class ResourceWithFakeContents(resource.Resource):

  def __init__(self, toplevel_dir, absolute_path, fake_contents):
    """A resource with explicitly provided contents.

    If the resource does not exist, then pass fake_contents=None. This will
    cause accessing the resource contents to raise an exception mimicking the
    behavior of regular resources."""
    super(ResourceWithFakeContents, self).__init__(toplevel_dir, absolute_path)
    self._fake_contents = fake_contents

  @property
  def contents(self):
    if self._fake_contents is None:
      raise Exception('File not found')
    return self._fake_contents


class FakeLoader(object):

  def __init__(self, source_paths, initial_filenames_and_contents=None):
    self._source_paths = source_paths
    self._file_contents = {}
    if initial_filenames_and_contents:
      for k, v in six.iteritems(initial_filenames_and_contents):
        self._file_contents[k] = v

  def FindResourceGivenAbsolutePath(self, absolute_path):
    candidate_paths = []
    for source_path in self._source_paths:
      if absolute_path.startswith(source_path):
        candidate_paths.append(source_path)
    if len(candidate_paths) == 0:
      return None

    # Sort by length. Longest match wins.
    candidate_paths.sort(lambda x, y: len(x) - len(y))
    longest_candidate = candidate_paths[-1]

    return ResourceWithFakeContents(
        longest_candidate, absolute_path,
        self._file_contents.get(absolute_path, None))

  def FindResourceGivenRelativePath(self, relative_path):
    absolute_path = None
    for script_path in self._source_paths:
      absolute_path = os.path.join(script_path, relative_path)
      if absolute_path in self._file_contents:
        return ResourceWithFakeContents(script_path, absolute_path,
                                        self._file_contents[absolute_path])
    return None


class ParseTests(unittest.TestCase):

  def testValidExternalScriptReferenceToRawScript(self):
    parse_results = parse_html_deps.HTMLModuleParserResults("""<!DOCTYPE html>
      <script src="../foo.js">
      """)

    file_contents = {}
    file_contents[os.path.normpath('/tmp/a/foo.js')] = """
'i am just some raw script';
"""

    metadata = html_module.Parse(
        FakeLoader([os.path.normpath('/tmp')], file_contents),
        'a.b.start',
        '/tmp/a/b/',
        is_component=False,
        parser_results=parse_results)
    self.assertEquals([], metadata.dependent_module_names)
    self.assertEquals(
        ['a/foo.js'], metadata.dependent_raw_script_relative_paths)

  def testExternalScriptReferenceToModuleOutsideScriptPath(self):
    parse_results = parse_html_deps.HTMLModuleParserResults("""<!DOCTYPE html>
      <script src="/foo.js">
      """)

    file_contents = {}
    file_contents[os.path.normpath('/foo.js')] = ''

    def DoIt():
      html_module.Parse(FakeLoader([os.path.normpath('/tmp')], file_contents),
                        'a.b.start',
                        '/tmp/a/b/',
                        is_component=False,
                        parser_results=parse_results)
    self.assertRaises(Exception, DoIt)

  def testExternalScriptReferenceToFileThatDoesntExist(self):
    parse_results = parse_html_deps.HTMLModuleParserResults("""<!DOCTYPE html>
      <script src="/foo.js">
      """)

    file_contents = {}

    def DoIt():
      html_module.Parse(FakeLoader([os.path.normpath('/tmp')], file_contents),
                        'a.b.start',
                        '/tmp/a/b/',
                        is_component=False,
                        parser_results=parse_results)
    self.assertRaises(Exception, DoIt)

  def testValidImportOfModule(self):
    parse_results = parse_html_deps.HTMLModuleParserResults("""<!DOCTYPE html>
      <link rel="import" href="../foo.html">
      """)

    file_contents = {}
    file_contents[os.path.normpath('/tmp/a/foo.html')] = """
"""

    metadata = html_module.Parse(
        FakeLoader([os.path.normpath('/tmp')], file_contents),
        'a.b.start',
        '/tmp/a/b/',
        is_component=False,
        parser_results=parse_results)
    self.assertEquals(['a.foo'], metadata.dependent_module_names)

  def testStyleSheetImport(self):
    parse_results = parse_html_deps.HTMLModuleParserResults("""<!DOCTYPE html>
      <link rel="stylesheet" href="../foo.css">
      """)

    file_contents = {}
    file_contents[os.path.normpath('/tmp/a/foo.css')] = """
"""
    metadata = html_module.Parse(
        FakeLoader([os.path.normpath('/tmp')], file_contents),
        'a.b.start',
        '/tmp/a/b/',
        is_component=False,
        parser_results=parse_results)
    self.assertEquals([], metadata.dependent_module_names)
    self.assertEquals(['a.foo'], metadata.style_sheet_names)

  def testUsingAbsoluteHref(self):
    parse_results = parse_html_deps.HTMLModuleParserResults("""<!DOCTYPE html>
      <script src="/foo.js">
      """)

    file_contents = {}
    file_contents[os.path.normpath('/src/foo.js')] = ''

    metadata = html_module.Parse(
        FakeLoader([os.path.normpath("/tmp"), os.path.normpath("/src")],
                   file_contents),
        "a.b.start",
        "/tmp/a/b/",
        is_component=False,
        parser_results=parse_results)
    self.assertEquals(['foo.js'], metadata.dependent_raw_script_relative_paths)


class HTMLModuleTests(unittest.TestCase):

  def testBasicModuleGeneration(self):
    file_contents = {}
    file_contents[os.path.normpath('/tmp/a/b/start.html')] = """
<!DOCTYPE html>
<link rel="import" href="/widget.html">
<link rel="stylesheet" href="../common.css">
<script src="/raw_script.js"></script>
<script src="/excluded_script.js"></script>
<dom-module id="start">
  <template>
  </template>
  <script>
    'use strict';
    console.log('inline script for start.html got written');
  </script>
</dom-module>
"""
    file_contents[os.path.normpath('/py_vulcanize/py_vulcanize.html')] = """<!DOCTYPE html>
"""
    file_contents[os.path.normpath('/components/widget.html')] = """
<!DOCTYPE html>
<link rel="import" href="/py_vulcanize.html">
<widget name="widget.html"></widget>
<script>
'use strict';
console.log('inline script for widget.html');
</script>
"""
    file_contents[os.path.normpath('/tmp/a/common.css')] = """
/* /tmp/a/common.css was written */
"""
    file_contents[os.path.normpath('/raw/raw_script.js')] = """
console.log('/raw/raw_script.js was written');
"""
    file_contents[os.path.normpath(
        '/raw/components/polymer/polymer.min.js')] = """
"""

    with fake_fs.FakeFS(file_contents):
      project = project_module.Project(
          [os.path.normpath('/py_vulcanize/'),
           os.path.normpath('/tmp/'),
           os.path.normpath('/components/'),
           os.path.normpath('/raw/')])
      loader = resource_loader.ResourceLoader(project)
      a_b_start_module = loader.LoadModule(
          module_name='a.b.start', excluded_scripts=['\/excluded_script.js'])
      load_sequence = project.CalcLoadSequenceForModules([a_b_start_module])

      # Check load sequence names.
      load_sequence_names = [x.name for x in load_sequence]
      self.assertEquals(['py_vulcanize',
                         'widget',
                         'a.b.start'], load_sequence_names)

      # Check module_deps on a_b_start_module
      def HasDependentModule(module, name):
        return [x for x in module.dependent_modules
                if x.name == name]
      assert HasDependentModule(a_b_start_module, 'widget')

      # Check JS generation.
      js = generate.GenerateJS(load_sequence)
      assert 'inline script for start.html' in js
      assert 'inline script for widget.html' in js
      assert '/raw/raw_script.js' in js
      assert 'excluded_script.js' not in js

      # Check HTML generation.
      html = generate.GenerateStandaloneHTMLAsString(
          load_sequence, title='', flattened_js_url='/blah.js')
      assert '<dom-module id="start">' in html
      assert 'inline script for widget.html' not in html
      assert 'common.css' in html

  def testPolymerConversion(self):
    file_contents = {}
    file_contents[os.path.normpath('/tmp/a/b/my_component.html')] = """
<!DOCTYPE html>
<dom-module id="my-component">
  <template>
  </template>
  <script>
    'use strict';
    Polymer ( {
      is: "my-component"
    });
  </script>
</dom-module>
"""
    with fake_fs.FakeFS(file_contents):
      project = project_module.Project([
          os.path.normpath('/py_vulcanize/'), os.path.normpath('/tmp/')])
      loader = resource_loader.ResourceLoader(project)
      my_component = loader.LoadModule(module_name='a.b.my_component')

      f = six.StringIO()
      my_component.AppendJSContentsToFile(
          f,
          use_include_tags_for_scripts=False,
          dir_for_include_tag_root=None)
      js = f.getvalue().rstrip()
      expected_js = """
    'use strict';
    Polymer ( {
      is: "my-component"
    });
""".rstrip()
      self.assertEquals(expected_js, js)

  def testInlineStylesheetURLs(self):
    file_contents = {}
    file_contents[os.path.normpath('/tmp/a/b/my_component.html')] = """
<!DOCTYPE html>
<style>
.some-rule {
    background-image: url('../something.jpg');
}
</style>
"""
    file_contents[os.path.normpath('/tmp/a/something.jpg')] = 'jpgdata'
    with fake_fs.FakeFS(file_contents):
      project = project_module.Project([
          os.path.normpath('/py_vulcanize/'), os.path.normpath('/tmp/')])
      loader = resource_loader.ResourceLoader(project)
      my_component = loader.LoadModule(module_name='a.b.my_component')

      computed_deps = []
      my_component.AppendDirectlyDependentFilenamesTo(computed_deps)
      self.assertEquals(set(computed_deps),
                        set([os.path.normpath('/tmp/a/b/my_component.html'),
                             os.path.normpath('/tmp/a/something.jpg')]))

      f = six.StringIO()
      ctl = html_generation_controller.HTMLGenerationController()
      my_component.AppendHTMLContentsToFile(f, ctl)
      html = f.getvalue().rstrip()
      # FIXME: This is apparently not used.
      expected_html = """
.some-rule {
    background-image: url(data:image/jpg;base64,anBnZGF0YQ==);
}
""".rstrip()
