# -*- coding: utf-8 -*- #
# Copyright 2015 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Fingerprinting code for the Go runtime."""

from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

import fnmatch
import os
import re
import textwrap

from gae_ext_runtime import ext_runtime

from googlecloudsdk.api_lib.app.images import config as images_config
from googlecloudsdk.core import log
from googlecloudsdk.core.util import files


NAME ='go'
ALLOWED_RUNTIME_NAMES = ('go', 'custom')
GO_RUNTIME_NAME = 'go'

GO_APP_YAML = textwrap.dedent("""\
    env: flex
    runtime: {runtime}
    api_version: go1
    """)
DOCKERIGNORE = textwrap.dedent("""\
    .dockerignore
    Dockerfile
    .git
    .hg
    .svn
    """)
DOCKERFILE = textwrap.dedent("""\
    # Dockerfile extending the generic Go image with application files for a
    # single application.
    FROM gcr.io/google_appengine/golang

    COPY . /go/src/app
    RUN go-wrapper install -tags appenginevm
    """)


class GoConfigurator(ext_runtime.Configurator):
  """Generates configuration for a Go app."""

  def __init__(self, path, params):
    """Constructor.

    Args:
      path: (str) Root path of the source tree.
      params: (ext_runtime.Params) Parameters passed through to the
        fingerprinters.
    """

    self.root = path
    self.params = params

  def GetAllConfigFiles(self):

    all_config_files = []

    # Generate app.yaml.
    if not self.params.appinfo:
      app_yaml_path = os.path.join(self.root, 'app.yaml')
      if not os.path.exists(app_yaml_path):
        runtime = 'custom' if self.params.custom else 'go'
        app_yaml_contents = GO_APP_YAML.format(runtime=runtime)
        app_yaml = ext_runtime.GeneratedFile('app.yaml', app_yaml_contents)
        all_config_files.append(app_yaml)

    if self.params.custom or self.params.deploy:
      dockerfile_path = os.path.join(self.root, images_config.DOCKERFILE)
      if not os.path.exists(dockerfile_path):
        dockerfile = ext_runtime.GeneratedFile(images_config.DOCKERFILE,
                                               DOCKERFILE)
        all_config_files.append(dockerfile)

      # Generate .dockerignore
      dockerignore_path = os.path.join(self.root, '.dockerignore')
      if not os.path.exists(dockerignore_path):
        dockerignore = ext_runtime.GeneratedFile('.dockerignore', DOCKERIGNORE)
        all_config_files.append(dockerignore)
    return all_config_files

  def GenerateConfigs(self):
    """Generate config files for the module.

    Returns:
      (bool) True if files were created
    """
    # Write "Writing file" messages to the user or to log depending on whether
    # we're in "deploy."
    if self.params.deploy:
      notify = log.info
    else:
      notify = log.status.Print
    cfg_files = self.GetAllConfigFiles()
    created = False
    for cfg_file in cfg_files:
      if cfg_file.WriteTo(self.root, notify):
        created = True
    if not created:
      notify('All config files already exist, not generating anything.')

    return created

  def GenerateConfigData(self):
    """Generate config files for the module.

    Returns:
      list(ext_runtime.GeneratedFile) list of generated files.
    """
    # Write "Writing file" messages to the user or to log depending on whether
    # we're in "deploy."
    if self.params.deploy:
      notify = log.info
    else:
      notify = log.status.Print
    cfg_files = self.GetAllConfigFiles()
    for cfg_file in cfg_files:
      if cfg_file.filename == 'app.yaml':
        cfg_file.WriteTo(self.root, notify)
    final_cfg_files = []
    for f in cfg_files:
      if f.filename != 'app.yaml' and not os.path.exists(
          os.path.join(self.root, f.filename)):
        final_cfg_files.append(f)
    return final_cfg_files


def _GoFiles(path):
  """Return list of '*.go' files under directory 'path'.

  Note that os.walk by default performs a top-down search, so files higher in
  the directory tree appear before others.

  Args:
    path: (str) Application path.

  Returns:
    ([str, ...]) List of full pathnames for all '*.go' files under 'path' dir.
  """
  go_files = []
  for root, _, filenames in os.walk(path):
    for filename in fnmatch.filter(filenames, '*.go'):
      go_files.append(os.path.join(root, filename))
  return go_files


def _FindMain(filename):
  """Check filename for 'package main' and 'func main'.

  Args:
    filename: (str) File name to check.

  Returns:
    (bool) True if main is found in filename.
  """
  with files.FileReader(filename) as f:
    found_package = False
    found_func = False
    for line in f:
      if re.match('^package main', line):
        found_package = True
      elif re.match('^func main', line):
        found_func = True
      if found_package and found_func:
        return True
  return False


def Fingerprint(path, params):
  """Check for a Go app.

  Args:
    path: (str) Application path.
    params: (ext_runtime.Params) Parameters passed through to the
      fingerprinters.

  Returns:
    (GoConfigurator or None) Returns a module if the path contains a
    Go app.
  """
  log.info('Checking for Go.')

  # Test #1 - are there any '*.go' files at or below 'path'?
  go_files = _GoFiles(path)
  if not go_files:
    return None

  # Test #2 - check that one of these files has "package main" and "func main".
  main_found = False
  for f in go_files:
    if _FindMain(f):
      log.info('Found Go main in %s', f)
      main_found = True
      break
  if not main_found:
    return None

  return GoConfigurator(path, params)
