#!/usr/bin/python

from __future__ import print_function

import os
import osc.cmdln as cmdln
import osc.conf as conf
import osc.core

try:
    from debian.deb822 import Changes, Dsc
except ImportError:
    from deb822 import Changes, Dsc
import shutil
import atexit
import tempfile

def _get_objects_from_file(filename):
    """
    Return a tuple containing (None or a Changes object), a Dsc object,
    and the filename of the .dsc file.
    """
    changes = None
    dsc = None
    dscfile = None

    try:
        f = open(filename)
    except IOError:
        raise Exception(filename + " couldn't be read")

    if filename.endswith('.changes'):
        changes = Changes(f)
        f.close()

        if 'source' not in changes['architecture'].split():
            raise Exception(filename + ' does not contain source code')

        for rec in changes['files']:
            if rec['name'].endswith('.dsc'):
                dscfile = os.path.join(os.path.dirname(filename), rec['name'])
                f = open(dscfile)
                dsc = Dsc(f)
                f.close()
                break
        else:
            raise Exception(filename + ' does not list a .dsc file')
    elif filename.endswith('.dsc'):
        dsc = Dsc(f)
        dscfile = filename
    else:
        raise Exception(filename + ' is not a .dsc or .changes file')

    f.close()

    return changes, dsc, dscfile

class DPut(object):
    WORKING_DIR = None
    PACKAGE_NAME = None
    PROJECT_NAME = None

    def _create_and_move_to_working_dir(self):
        """
           Create and move the current_path (CWD) to the working_dir
        """
        working_path = self.WORKING_DIR

        # TODO: Should we check for IO exceptions here?
        if not os.path.exists(working_path):
            os.mkdir(working_path)

        os.chdir(working_path)

    def _create_package(self):
        """
           Create a package on the remove server 
        """

        current_path = os.getcwd()
        project_path = os.path.join(self.WORKING_DIR, self.PROJECT_NAME)

        os.chdir(project_path)
        osc.core.createPackageDir(self.PACKAGE_NAME)

        project = osc.core.Project(".", getPackageList=False)
        project.update()
        project.commit()
        os.chdir(current_path)

    def _get_package_object(self):
        """
            Returns a valid package object depending if the package already exists or
            if it needs to be created
        """
        # this one is needed because there's two ways of getting the package object
        # first one fails if the folder already exists, the second one fail if it doesn't

        path = os.path.join(self.WORKING_DIR, self.PROJECT_NAME, self.PACKAGE_NAME)
        if os.path.exists(path) and os.path.isdir(path):
            os.chdir(path)

            package = osc.core.Package(".")
            return package

        elif not os.path.exists(path):
            package = osc.core.Package.init_package(conf.config['apiurl'],
                                                         project=self.PROJECT_NAME,
                                                         package=self.PACKAGE_NAME,
                                                         dir=path,
                                                         meta=True)
            package.update()
            os.chdir(path)

            return package

        else:
            print("Package folder couldn't be created")
            raise Exception("Package folder couldn't be created")

    def _get_remote_file_list(self, package):
        """
            Returns a list of files inside an specific package
        """
        fileList = osc.core.meta_get_filelist(conf.config['apiurl'], self.PROJECT_NAME, self.PACKAGE_NAME)

        return fileList

@cmdln.option('--maintained-in-git', action='store_true',
              help='add MAINTAINED_IN_GIT.txt')
def do_dput(self, subcmd, opts, proj_name, dsc_or_changes_file):
    dput = DPut()

    dput.PROJECT_NAME = proj_name.encode("utf8")
    dput.WORKING_DIR = tempfile.mkdtemp('_oscdput')
    def cleanup(dirname, initdir):
        os.chdir(initdir)
        if os.path.exists(dirname):
            shutil.rmtree(dirname)
    atexit.register(cleanup, dirname=dput.WORKING_DIR, initdir=os.getcwd())



    # get debian .change object before moving current path to the
    # working_dir
    changes, dsc, dsc_file = _get_objects_from_file(dsc_or_changes_file)
    dput.PACKAGE_NAME = dsc.get("Source").encode('utf8') # XXX: needs to be utf8!!!
    dput.PACKAGE_VERSION = dsc.get("Version")

    # Filenames in the .dsc are relative to the directory where it appears.
    # We need to make it absolute before we chdir elsewhere.
    dsc_or_changes_file = os.path.abspath(dsc_or_changes_file)
    dsc_file = os.path.abspath(dsc_file)
    dscdir = os.path.dirname(dsc_file)

    dput._create_and_move_to_working_dir()

    # Get the list of packages
    # TODO: Probably it can be done after checking out the project
    # So we can save one http request
    package_list = osc.core.meta_get_packagelist(conf.config['apiurl'], dput.PROJECT_NAME)

    # Starting the project
    project = osc.core.Project.init_project(conf.config['apiurl'],
                                                 dir=dput.PROJECT_NAME,
                                                 project=dput.PROJECT_NAME)

    # check if the package exists on server, otherwise create one
    if dput.PACKAGE_NAME not in package_list:
        dput._create_package()

    # it also changes the current_dir to the package dir
    package = dput._get_package_object()

    # defining file list, so we can decide which one to delete
    remote_file_list = dput._get_remote_file_list(package)
    local_file_list = [f["name"] for f in dsc.get("Files")] #local lambda list

    # Remove old files, but only those that are part of the Debian package
    superseded = set()
    retained = set()
    downloaded = set()

    for f in remote_file_list:
        if f.endswith('.dsc'):
            u = osc.core.makeurl(conf.config['apiurl'],
                    ['source', dput.PROJECT_NAME, dput.PACKAGE_NAME, f],
                    query={})

            lines = []

            remote_dsc = Dsc(osc.core.streamfile(u, bufsize='line'))

            for entry in remote_dsc.get('Files'):
                superseded.add(entry['name'])

            superseded.add(f)
        elif f.endswith('.changes'):
            superseded.add(f)
        else:
            retained.add(f)

    retained -= superseded

    # The temporary checkout we're using doesn't seem to have the
    # expected result for latest_rev()?
    source_rev = osc.core.get_source_rev(conf.config['apiurl'],
                     dput.PROJECT_NAME, dput.PACKAGE_NAME)

    if 'rev' in source_rev:
        latest_revision = source_rev['rev']

        for f in retained:
            print('retaining non-source file: %r' % f)
            if not os.path.exists(f):
                print('fetching %r from server' % f)
                package.updatefile(f, latest_revision)
                downloaded.add(f)

    # adding local_file_list to the package as links
    for f in local_file_list:
        filepath = os.path.join(dscdir, f)
        os.symlink(filepath, f)
        package.addfile(f)

    f = os.path.basename(dsc_file)
    os.symlink(dsc_file, f)
    package.addfile(f)

    if opts.maintained_in_git and 'MAINTAINED_IN_GIT.txt' not in retained:
        open('MAINTAINED_IN_GIT.txt', 'w+').close()
        package.addfile('MAINTAINED_IN_GIT.txt')

    # a plain .dsc file has very little metadata, so "hello_2.10-1.dsc"
    # is about the best commit message we can provide without unpacking
    # the source package
    msg = f

    for f in downloaded:
        package.addfile(f)

    if changes is not None:
        f = os.path.basename(dsc_or_changes_file)
        os.symlink(dsc_or_changes_file, f)
        package.addfile(f)

        msg = changes.get('changes', msg)

    package.commit(msg=msg)
