#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (C) 2015 Canonical Ltd.
# Author: Łukasz 'sil2100' Zemczak <lukasz.zemczak@ubuntu.com>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import json
import os
import sys
import argparse
import fcntl
import logging

sys.path.insert(0, os.path.join(sys.path[0], os.pardir, "lib"))

from operator import itemgetter
from systemimage import config, generators, tools, tree


def main():
    parser = argparse.ArgumentParser(description="image tagger")
    parser.add_argument("channel", metavar="CHANNEL")
    parser.add_argument("device", metavar="DEVICE")
    parser.add_argument("version", metavar="VERSION", type=int)
    parser.add_argument("tag", metavar="TAG")
    parser.add_argument("--verbose", "-v", action="count", default=0)

    args = parser.parse_args()

    # Setup logging
    formatter = logging.Formatter(
        "%(asctime)s %(levelname)s %(message)s")

    levels = {1: logging.ERROR,
              2: logging.WARNING,
              3: logging.INFO,
              4: logging.DEBUG}

    if args.verbose > 0:
        stdoutlogger = logging.StreamHandler(sys.stdout)
        stdoutlogger.setFormatter(formatter)
        logging.root.setLevel(levels[min(4, args.verbose)])
        logging.root.addHandler(stdoutlogger)
    else:
        logging.root.addHandler(logging.NullHandler())

    # Load the configuration
    conf = config.Config()

    # Try to acquire a global lock
    lock_file = os.path.join(conf.state_path, "global.lock")
    lock_fd = open(lock_file, 'w')

    try:
        fcntl.lockf(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
    except IOError:
        print("Something else holds the global lock. exiting.")
        sys.exit(0)

    # Load the tree
    pub = tree.Tree(conf)

    # Do some checks
    channels = pub.list_channels()
    if args.channel not in channels:
        parser.error("Invalid channel: %s" % args.channel)

    if args.device not in channels[args.channel]['devices']:
        parser.error("Invalid device for channel: %s" % args.device)

    if "alias" in channels[args.channel] and \
            channels[args.channel]['alias'] \
            != args.channel:
        parser.error("Channel is an alias.")

    if "redirect" in channels[args.channel]:
        parser.error("Channel is a redirect.")

    target_device = channels[args.channel]['devices'][args.device]
    if "redirect" in target_device:
        parser.error("Target device is a redirect. Use original channel "
                     "%s instead." % target_device['redirect'])

    if "," in args.tag:
        parser.error("Image tag cannot include the character ','.")

    device = pub.get_device(args.channel, args.device)

    device_images = device.list_images()
    if not device_images:
        parser.error("No images in selected channel/device.")

    sorted_images = sorted(device_images, key=itemgetter('version'))
    if args.version != sorted_images[-1]['version']:
        parser.error("You can only tag the latest image.")

    # Extract the build we want to copy
    images = [image for image in device_images
              if image['type'] == "full" and image['version'] == args.version]
    if not images:
        parser.error("Can't find version: %s" % args.version)
    image = images[0]

    # Assign a new version number
    new_version = args.version + 1

    # Generate a list of required deltas
    delta_base = tools.get_required_deltas(
        conf, pub, args.channel, args.device)

    # Create a new empty file list
    new_files = []

    # Extract current version_detail and image files
    version_detail = tools.extract_files_and_version(
        conf, image['files'], args.version, new_files)
    logging.debug("Original version_detail is: %s" % version_detail)

    # Generate new version tarball environment
    environment = {}
    environment['channel_name'] = args.channel
    environment['device'] = device
    environment['device_name'] = args.device
    environment['version'] = new_version
    environment['version_detail'] = [entry
                                     for entry in version_detail.split(",")
                                     if not entry.startswith("version=")]
    environment['new_files'] = new_files

    # Set the tag for the image
    tools.set_tag_on_version_detail(environment['version_detail'], args.tag)
    logging.info("Setting tag for image to '%s'" % args.tag)

    # Generate the new version tarball
    logging.info("Generating new version tarball for '%s' (%s)"
                 % (new_version, ",".join(environment['version_detail'])))
    version_path = generators.generate_file(conf, "version", [], environment)
    if version_path:
        new_files.append(version_path)

    # Add full image
    logging.info("Publishing new image '%s' (%s) with %s files."
                 % (new_version, ",".join(environment['version_detail']),
                    len(new_files)))
    device.create_image(
        "full", new_version, ",".join(environment['version_detail']),
        new_files, version_detail=",".join(environment['version_detail']))

    # Add delta images
    delta_files = []
    for path in new_files:
        filename = path.split("/")[-1]
        if filename.startswith("version-") or filename.startswith("keyring-"):
            delta_files.append(path)

    for delta in delta_base:
        logging.info("Publishing new delta from '%s' (%s)"
                     " to '%s' (%s)" %
                     (delta['version'], delta.get("description", ""),
                      new_version, ",".join(environment['version_detail'])))

        device.create_image(
            "delta", new_version,
            ",".join(environment['version_detail']),
            delta_files,
            base=delta['version'],
            version_detail=",".join(environment['version_detail']))

    # Set the phased percentage value for the new image
    percentage = device.get_phased_percentage(args.version)
    logging.info("Setting phased percentage of the new image to %s%%" %
                 percentage)
    device.set_phased_percentage(new_version, percentage)

    # Expire images
    if args.channel in conf.channels:
        if conf.channels[args.channel].fullcount > 0:
            logging.info("Expiring old images")
            device.expire_images(conf.channels[args.channel].fullcount)

    # Sync all channel aliases
    logging.info("Syncing any existing alias")
    pub.sync_aliases(args.channel)

    # Remove any orphaned file
    logging.info("Removing orphaned files from the pool")
    pub.cleanup_tree()

    # Sync the mirrors
    logging.info("Triggering a mirror sync")
    tools.sync_mirrors(conf)


if __name__ == '__main__':
    main()
