# 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."""

import fnmatch
import os
import re
import textwrap

from gae_ext_runtime import ext_runtime

from googlecloudsdk.api_lib.app.images import config
from googlecloudsdk.core import log

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

GO_APP_YAML = textwrap.dedent("""\
    vm: true
    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 GenerateConfigs(self):
    """Generate all config files for the module.

    Returns:
      (callable()) ext_runtime.Cleaner instance.
    """
    # 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

    # Generate app.yaml.
    cleaner = ext_runtime.Cleaner()
    if not self.params.appinfo:
      app_yaml = os.path.join(self.root, 'app.yaml')
      if not os.path.exists(app_yaml):
        notify('Writing [app.yaml] to [%s].' % self.root)
        runtime = 'custom' if self.params.custom else 'go'
        with open(app_yaml, 'w') as f:
          f.write(GO_APP_YAML.format(runtime=runtime))

    if self.params.custom or self.params.deploy:
      dockerfile = os.path.join(self.root, config.DOCKERFILE)
      if not os.path.exists(dockerfile):
        notify('Writing [%s] to [%s].' % (config.DOCKERFILE, self.root))
        with open(dockerfile, 'w') as f:
          f.write(DOCKERFILE)
        cleaner.Add(dockerfile)

      # Generate .dockerignore TODO(mmuller): eventually this file will just be
      # copied verbatim.
      dockerignore = os.path.join(self.root, '.dockerignore')
      if not os.path.exists(dockerignore):
        notify('Writing [.dockerignore] to [%s].' % self.root)
        with open(dockerignore, 'w') as f:
          f.write(DOCKERIGNORE)
        cleaner.Add(dockerignore)

    if not cleaner.HasFiles():
      notify('All config files already exist, not generating anything.')

    return cleaner


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 open(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)
