import posixpath

from parallels.core import get_logger, messages
from parallels.core.registry import Registry
from parallels.core.runners.base import BaseRunner
from parallels.core.runners.entities import FileListItem
from parallels.core.utils.file_utils import path_to_binary_string
from parallels.core.utils.ftp import ItemInfo

logger = get_logger(__name__)


class FTPRunner(BaseRunner):
    """
    :type agent: parallels.core.runners.web.utils.agent.WebRPCAgent
    """

    def __init__(self, ftp):
        """
        :type ftp: parallels.core.utils.ftp.Ftp
        """
        super(FTPRunner, self).__init__(None, ftp.host)
        self._ftp = ftp

    def get_dir_tree(self, path):
        """Retrieve tree structure of the directory with given path, like the following:

        [
            'file_1',
            'file_2',
            {
                'dir_1': [
                    'file_3'
                ]
            }
        ]

        :type path: str | unicode
        :rtype: list[str | {str, list}]
        """
        tree_root_items = []
        binary_path = path_to_binary_string(path)

        items_to_walk = [
            (
                binary_path,
                tree_root_items
            )
        ]

        while len(items_to_walk) > 0:
            item_path, child_items = items_to_walk.pop()

            try:
                ftp_item_infos = self._ftp.list_file_and_directory_paths(item_path)
            except Exception as e:
                logger.exception()
                logger.fwarn(messages.FAILED_TO_LIST_DIRECTORY_BY_FTP, reason=e, directory=path)
                ftp_item_infos = []

            for ftp_item_info in ftp_item_infos:
                if ftp_item_info.item_type == ItemInfo.TYPE_FILE:
                    child_items.append(ftp_item_info.name)
                elif ftp_item_info.item_type == ItemInfo.TYPE_DIR:
                    child_dir_child_items = []
                    child_dir_item = {ftp_item_info.name: child_dir_child_items}
                    child_items.append(child_dir_item)
                    items_to_walk.append(
                        (
                            posixpath.join(item_path, ftp_item_info.name),
                            child_dir_child_items
                        )
                    )

        return tree_root_items

    def get_files_list_extended(self, path):
        """Retrieve list of item in path, with with information whether each item is file or directory

        :type path: str | unicode 
        :rtype: list[parallels.core.runners.entities.FileListItem]
        """
        ftp_item_infos = self._ftp.list_file_and_directory_paths(path)
        return [FileListItem(item.name, item.is_dir) for item in ftp_item_infos]

    def get_directories_list(self, path):
        """
        :type path: str | unicode
        :rtype: list[str | unicode]
        """
        return self._ftp.list_directories(path)

    def upload_file_content(self, filename, content):
        """Put data into the file.

        :type filename: str | unicode
        :type content: str
        :rtype: None
        """
        return self._ftp.upload_file_content(content, filename)

    def upload_file(self, local_filename, remote_filename):
        """
        :type local_filename: str | unicode
        :type remote_filename: str | unicode
        :rtype: None
        """
        self._ftp.upload_local_file(local_filename, remote_filename)

    def get_file(self, remote_filename, local_filename):
        """
        :type remote_filename: str | unicode
        :type local_filename: str | unicode
        :rtype: None
        """
        local_server = Registry.get_instance().get_context().migrator_server
        return self._ftp.download_file(remote_filename, local_server, local_filename)

    def get_file_contents(self, remote_filename):
        """Get contents of the file.

        :type remote_filename: str | unicode
        :rtype: str | unicode
        """
        return self._ftp.download_file_contents(remote_filename)

    def remove_file(self, filename):
        """Remove file. If file does not exist - silently do nothing.

        :type filename: str | unicode
        """
        self._ftp.remove_file(filename)

    def remove_directory(self, directory, is_remove_root=True):
        """
        :type directory: str | unicode
        :type is_remove_root: bool
        :rtype: None
        """
        if not is_remove_root:
            raise NotImplementedError()
        self._ftp.remove_directory(directory)

    def mkdir(self, dirname):
        """
        :type dirname: str | unicode
        :rtype: None
        """
        self._ftp.mkdir(dirname)