#!/usr/bin/python

from __future__ import print_function

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

from debian.deb822 import Changes, Dsc
from tempfile import TemporaryDirectory
from pathlib import Path

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:
        with open(filename) as f:
            if filename.endswith('.changes'):
                changes = Changes(f)

                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 = Path(filename).with_name(rec['name'])
                        with open(dscfile) as fdsc:
                            dsc = Dsc(fdsc)
                        return changes, dsc, dscfile
                else:
                    raise Exception(filename + ' does not list a .dsc file')
            elif filename.endswith('.dsc'):
                dsc = Dsc(f)
                dscfile = filename
                return changes, dsc, dscfile
            else:
                raise Exception(filename + ' is not a .dsc or .changes file')
    except IOError:
        raise Exception(filename + " couldn't be read")

class DPut(object):
    def __init__(self, project_name=None, package_name=None, working_dir=None):
        self.project_name = project_name
        self.package_name = package_name
        self.working_dir = Path(working_dir)

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

        current_path = os.getcwd()
        project_path = 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 = self.working_dir / self.project_name / self.package_name
        if path.is_dir():
            os.chdir(path)
            return osc.core.Package('.')

        elif not path.exists():
            path.mkdir()
            os.chdir(path)
            package = osc.core.Package.init_package(conf.config['apiurl'],
                                                         project=self.project_name,
                                                         package=self.package_name,
                                                         dir='.',
                                                         meta=True)
            package.update()
            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
        """
        return osc.core.meta_get_filelist(conf.config['apiurl'], self.project_name, self.package_name)

@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):

  with TemporaryDirectory('_oscdput') as working_dir:
    # get debian .change object before moving current path to the
    # working_dir
    changes, dsc, dsc_file = get_objects_from_file(dsc_or_changes_file)
    package_name = dsc.get("Source")
    dput = DPut(proj_name, package_name, working_dir)

    # 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)

    # 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'], proj_name)

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

    # check if the package exists on server, otherwise create one
    if 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', proj_name, 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'],
                     proj_name, package_name)

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

        for f in retained:
            print('retaining non-source file: %r' % f)
            # this will undo branch links since _link needs meta=0 when fetching
            if not os.path.exists(f) and f != '_link':
                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)
