# 
#   Copyright 2017 RIFT.IO Inc
#
#   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.
#
#   Author(s): Nandan Sinha
#

import os
import uuid
import shutil 
import enum

import gi
gi.require_version('RwVnfdYang', '1.0')
gi.require_version('RwNsdYang', '1.0')
from gi.repository import (
    RwYang, 
    NsdYang,
    VnfdYang,
    RwVnfdYang,
    RwNsdYang,
    RwPkgMgmtYang
)

class PackageCopyError(Exception): 
    pass

class CopyStatus(enum.Enum):
    UNINITIATED = 0
    STARTED = 1
    IN_PROGRESS = 2
    COMPLETED = 3
    FAILED = 4
    CANCELLED = 5

TaskStatus = RwPkgMgmtYang.TaskStatus

class CopyMeta:
    STATUS_MAP = {
        CopyStatus.STARTED: TaskStatus.QUEUED.value_nick.upper(),
        CopyStatus.UNINITIATED: TaskStatus.QUEUED.value_nick.upper(),
        CopyStatus.IN_PROGRESS: TaskStatus.IN_PROGRESS.value_nick.upper(),
        CopyStatus.COMPLETED: TaskStatus.COMPLETED.value_nick.upper(),
        CopyStatus.FAILED: TaskStatus.FAILED.value_nick.upper(),
        CopyStatus.CANCELLED: TaskStatus.CANCELLED.value_nick.upper()
        }

    def __init__(self, transaction_id):
        self.transaction_id = transaction_id
        self.state = CopyStatus.UNINITIATED

    def set_state(self, state):
        self.state = state

    def as_dict(self): 
        return self.__dict__

    def to_yang(self):
        job = RwPkgMgmtYang.CopyJob.from_dict({
            "transaction_id": self.transaction_id, 
            "status": CopyMeta.STATUS_MAP[self.state]
            })
        return job

class PackageFileCopier:
    DESCRIPTOR_MAP = {
            "vnfd": (RwVnfdYang.YangData_Vnfd_VnfdCatalog_Vnfd, 'vnfd rw-vnfd'), 
            "nsd" : (RwNsdYang.YangData_Nsd_NsdCatalog_Nsd, 'nsd rw-nsd') 
            }

    @classmethod
    def from_rpc_input(cls, rpc_input, proxy, log=None): 
        return cls(
                rpc_input.package_id,
                rpc_input.package_type, 
                rpc_input.package_name,
                proxy = proxy,
                log=log)

    def __init__(self, 
            pkg_id, 
            pkg_type, 
            pkg_name, 
            proxy, 
            log):
        self.src_package_id = pkg_id
        self.package_type = pkg_type.lower()
        self.dest_package_name = pkg_name
        self.dest_package_id = str(uuid.uuid4())
        self.transaction_id = str(uuid.uuid4())
        self.proxy = proxy
        self.log = log
        self.meta = CopyMeta(self.transaction_id)
        self.src_package = None
        self.dest_desc_msg = None

    # Start of delegate calls
    def call_delegate(self, event):
        if not self.delegate:
            return
        
        # Send out the descriptor message to be posted on success
        # Otherwise send out the CopyJob yang conversion from meta object.
        if event == "on_download_succeeded":
            getattr(self.delegate, event)(self.dest_desc_msg)
        else:
            getattr(self.delegate, event)(self.meta.to_yang())

    def _copy_tree(self):
        """
        Locate directory tree of the source descriptor folder. 
        Copy directory tree to destination descriptor folder.  

        """
        store = self.proxy._get_store(self.package_type)
        src_path = store._get_package_dir(self.src_package_id)
        self.src_package = store.get_package(self.src_package_id) 
        src_desc_name = self.src_package.descriptor_name
        src_copy_path = os.path.join(src_path, src_desc_name)

        self.dest_copy_path = os.path.join(store.DEFAULT_ROOT_DIR, 
                self.dest_package_id, 
                self.dest_package_name)
        self.log.debug("Copying contents from {src} to {dest}".
                format(src=src_copy_path, dest=self.dest_copy_path))

        shutil.copytree(src_copy_path, self.dest_copy_path)

    def _create_descriptor_file(self):
        """ Update descriptor file for the newly copied descriptor catalog.
        Use the existing descriptor file to create a descriptor proto gi object,
        change some identifiers, and create a new descriptor yaml file from it.

        """
        src_desc_file = self.src_package.descriptor_file
        src_desc_contents = self.src_package.descriptor_msg.as_dict()
        src_desc_contents.update(
                id =self.dest_package_id, 
                name = self.dest_package_name,
                short_name = self.dest_package_name
                )

        desc_cls, modules = PackageFileCopier.DESCRIPTOR_MAP[self.package_type]
        self.dest_desc_msg = desc_cls.from_dict(src_desc_contents)
        dest_desc_path = os.path.join(self.dest_copy_path, 
                "{pkg_name}_{pkg_type}.yaml".format(pkg_name=self.dest_package_name, pkg_type=self.package_type))
        model = RwYang.Model.create_libncx()
        for module in modules.split():
            model.load_module(module) 

        with open(dest_desc_path, "w") as fh:
            fh.write(self.dest_desc_msg.to_yaml(model))

        copied_desc_file = os.path.join(self.dest_copy_path, os.path.basename(src_desc_file))
        if os.path.exists(copied_desc_file):
            self.log.debug("Deleting copied yaml from old source %s" % (copied_desc_file))
            os.remove(copied_desc_file)

    def copy(self):
        try:
            if self.package_type not in PackageFileCopier.DESCRIPTOR_MAP: 
                raise PackageCopyError("Package type {} not currently supported for copy operations".format(self.package_type))

            self._copy_tree()
            self._create_descriptor_file()
            self.copy_succeeded()

        except Exception as e: 
            self.log.exception(str(e))
            self.copy_failed()

        self.copy_finished()

    def copy_failed(self):
        self.meta.set_state(CopyStatus.FAILED)
        self.call_delegate("on_download_failed")

    def copy_progress(self): 
        self.meta.set_state(CopyStatus.IN_PROGRESS)
        self.call_delegate("on_download_progress")

    def copy_succeeded(self):
        self.meta.set_state(CopyStatus.COMPLETED)
        self.call_delegate("on_download_succeeded")

    def copy_finished(self): 
        self.call_delegate("on_download_finished") 

