#!/usr/bin/env python
# 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.

import argparse
import logging
import os
import subprocess

from telemetry.core import command_line
from telemetry.page import cloud_storage


BUCKET_CHOICES = {
    'public': cloud_storage.PUBLIC_BUCKET,
    #'partner': cloud_storage.PARTNER_BUCKET,
    'google-only': cloud_storage.INTERNAL_BUCKET,
}


def _GetPaths(path):
  root, ext = os.path.splitext(path)
  if ext == '.sha1':
    file_path = root
    hash_path = path
  else:
    file_path = path
    hash_path = path + '.sha1'
  return file_path, hash_path


def _FindFilesInCloudStorage(files):
  bucket_contents = {}
  for easy_bucket_name, bucket in BUCKET_CHOICES.iteritems():
    try:
      bucket_contents[easy_bucket_name] = cloud_storage.List(bucket)
    except (cloud_storage.PermissionError, cloud_storage.CredentialsError):
      pass

  file_buckets = {}
  for path in files:
    file_path, hash_path = _GetPaths(path)

    if file_path in file_buckets:
      continue
    if not os.path.exists(hash_path):
      continue

    with open(hash_path, 'rb') as f:
      file_hash = f.read(1024).rstrip()

    buckets = []
    for bucket in BUCKET_CHOICES:
      if bucket not in bucket_contents:
        continue
      if file_hash in bucket_contents[bucket]:
        buckets.append(bucket)

    file_buckets[file_path] = buckets

  return file_buckets


class Ls(command_line.ArgparseCommand):
  """List which bucket each file is in."""

  def AddCommandLineOptions(self, parser):
    parser.add_argument('-r', '--recursive', action='store_true')
    parser.add_argument('paths', nargs='+')

  def ProcessCommandLine(self, parser, args):
    for path in args.paths:
      if not os.path.exists(path):
        parser.error('File not found: %s' % path)

  def Run(self, args):
    def GetFilesInPath(paths, recursive):
      """If path is a dir, yields all files in path, otherwise just yields path.
      
      If recursive is true, walks subdirectories recursively."""
      for path in paths:
        if not os.path.isdir(path):
          yield path
          return

        if recursive:
          for root, _, filenames in os.walk(path):
            for filename in filenames:
              yield os.path.join(root, filename)
        else:
          for filename in os.listdir(path):
            yield os.path.join(path, filename)

    files = _FindFilesInCloudStorage(GetFilesInPath(args.paths, args.recursive))

    if not files:
      print 'No files in Cloud Storage.'
      return

    for file_path, buckets in sorted(files.iteritems()):
      if buckets:
        print '%-11s  %s' % (','.join(buckets), file_path)
      else:
        print '%-11s  %s' % ('not found', file_path)


class Mv(command_line.ArgparseCommand):
  """Move files to the given bucket."""

  def AddCommandLineOptions(self, parser):
    parser.add_argument('files', nargs='+')
    parser.add_argument('bucket', choices=BUCKET_CHOICES)

  def ProcessCommandLine(self, parser, args):
    args.bucket = BUCKET_CHOICES[args.bucket]

    for path in args.paths:
      _, hash_path = _GetPaths(path)
      if not os.path.exists(hash_path):
        parser.error('File not found: %s' % hash_path)

  def Run(self, args):
    files = _FindFilesInCloudStorage(args.files)

    for file_path, buckets in sorted(files.iteritems()):
      if not buckets:
        raise IOError('%s not found in Cloud Storage.' % file_path)

    for file_path, buckets in sorted(files.iteritems()):
      hash_path = file_path + '.sha1'
      with open(hash_path, 'rb') as f:
        file_hash = f.read(1024).rstrip()

      cloud_storage.Move(buckets.pop(), args.bucket, file_hash)

      for bucket in buckets:
        if bucket == args.bucket:
          continue
        cloud_storage.Delete(bucket, file_hash)


class Rm(command_line.ArgparseCommand):
  """Remove files from Cloud Storage."""

  def AddCommandLineOptions(self, parser):
    parser.add_argument('files', nargs='+')

  def ProcessCommandLine(self, parser, args):
    for path in args.paths:
      _, hash_path = _GetPaths(path)
      if not os.path.exists(hash_path):
        parser.error('File not found: %s' % hash_path)

  def Run(self, args):
    files = _FindFilesInCloudStorage(args.files)
    for file_path, buckets in sorted(files.iteritems()):
      hash_path = file_path + '.sha1'
      with open(hash_path, 'rb') as f:
        file_hash = f.read(1024).rstrip()

      for bucket in buckets:
        cloud_storage.Delete(bucket, file_hash)


class Upload(command_line.ArgparseCommand):
  """Upload files to Cloud Storage."""

  def AddCommandLineOptions(self, parser):
    parser.add_argument('files', nargs='+')
    parser.add_argument('bucket', choices=BUCKET_CHOICES)

  def ProcessCommandLine(self, parser, args):
    args.bucket = BUCKET_CHOICES[args.bucket]

    for path in args.paths:
      if not os.path.exists(path):
        parser.error('File not found: %s' % path)

  def Run(self, args):
    for file_path in args.files:
      file_hash = cloud_storage.GetHash(file_path)

      # Create or update the hash file.
      hash_path = file_path + '.sha1'
      with open(hash_path, 'wb') as f:
        f.write(file_hash)
        f.flush()

      # Add the data to Cloud Storage.
      cloud_storage.Insert(args.bucket, file_hash, file_path)

      # Add the hash file to the branch, for convenience. :)
      subprocess.call(['git', 'add', hash_path])


COMMANDS = (Ls, Mv, Rm, Upload)


def main():
  logging.getLogger().setLevel(logging.INFO)

  parser = argparse.ArgumentParser()
  subparsers = parser.add_subparsers()

  for command in COMMANDS:
    command = command()
    subparser = subparsers.add_parser(command.name, help=command.description)
    subparser.set_defaults(command=command)
    command.AddCommandLineOptions(subparser)

  args = parser.parse_args()
  args.command.ProcessCommandLine(parser, args)
  args.command.Run(args)


if __name__ == '__main__':
  main()
