import os
import shutil
import posixpath

from parallels.core import local_command_exec
from parallels.core.runners.entities import ExecutionResults
from parallels.core.runners.unix.base import UnixRunner
from parallels.core.utils.common import mkdir_p, open_no_inherit
from parallels.core.utils.file_utils import iter_full_file_paths, iter_full_dir_paths, \
    get_dir_tree as file_utils_get_dir_tree


class LocalUnixRunner(UnixRunner):
    """Execute commands on local server"""

    def __init__(self):
        super(LocalUnixRunner, self).__init__(host_description='the local server', hostname=None)

    def _execute_command_raw(self, command, arguments, execution_options):
        """Execute command and return results, but do not log, do not check for exit code, and so on.

        :type command: str | unicode 
        :type arguments: dict[str | unicode, str | unicode] 
        :type execution_options: parallels.core.runners.entities.ExecutionOptions 
        :rtype: parallels.core.runners.entities.ExecutionResults 
        """
        if execution_options.redirect_output_file is not None:
            raise NotImplementedError()

        if execution_options.controlled_interruption is not None:
            stop_mark = execution_options.controlled_interruption.stop_mark
            stop_poll_interval = execution_options.controlled_interruption.poll_interval
        else:
            stop_mark = None
            stop_poll_interval = None

        command_str = self._format_command(command, arguments)
        command = ['/bin/sh', '-c', command_str]

        exit_code, stdout, stderr = local_command_exec(
            command, execution_options.stdin_content, None, None,
            execution_options.environment_variables, execution_options.working_dir,
            umask=execution_options.umask, stop_mark=stop_mark, stop_poll_interval=stop_poll_interval
        )
        return ExecutionResults(exit_code, stdout, stderr)

    def mkdir(self, dirname):
        mkdir_p(dirname)

    def create_file(self, filename):
        with open(filename, 'wb'):
            pass

    def append_file_content(self, filename, content):
        with open(filename, 'ab') as fp:
            fp.write(content)

    def upload_file_content(self, filename, content):
        with open(filename, 'wb') as fp:
            fp.write(content)

    def upload_file(self, local_filename, remote_filename):
        if local_filename != remote_filename:
            shutil.copy2(local_filename, remote_filename)

    def get_file(self, remote_filename, local_filename):
        if local_filename != remote_filename:
            shutil.copy2(remote_filename, local_filename)

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

        :type remote_filename: str | unicode
        :rtype: str | unicode
        """
        with open_no_inherit(remote_filename, "rb") as fp:
            return fp.read()

    def get_file_size(self, remote_filename):
        """Get size of specified file (in bytes)

        :type remote_filename: str | unicode
        :rtype: int
        """
        return os.path.getsize(remote_filename)

    def move(self, src_path, dst_path):
        shutil.move(src_path, dst_path)

    def get_files_list(self, path):
        return os.listdir(path)

    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'
                ]
            }
        ]

        Symbolic links are ignored.

        :type path: str | unicode
        :rtype: list[str | {str, list}]
        """
        return file_utils_get_dir_tree(path)

    def iter_files_list_nested(self, path):
        """Iterate over all (including files in nested directories) file names inside a path

        :type path: str | unicode
        :rtype: list[str | unicode]
        """
        return iter_full_file_paths(path)

    def iter_dirs_list_nested(self, path):
        return iter_full_dir_paths(path)

    def is_dir(self, path):
        """Returns True, False or None if file does not exist
        :type path: str | unicode
        :rtype: bool | None
        """
        if os.path.exists(path):
            return os.path.isdir(path)
        return None

    def remove_file(self, filename):
        if self.file_exists(filename):
            os.remove(filename)

    def remove_directory(self, directory, is_remove_root=True):
        for root, dirs, files in os.walk(directory):
            for name in files:
                self.remove_file(posixpath.join(directory, name))
            for name in dirs:
                self.remove_directory(posixpath.join(directory, name))
            if is_remove_root:
                os.rmdir(root)

    def check(self, server_description):
        # No need to check local execution, it should never fail
        pass

    def file_exists(self, filename):
        return os.path.exists(filename)

    def chmod(self, filename, mode):
        """Change file mode

        :type filename: str | unicode
        :type mode: int
        :rtype: None
        """
        os.chmod(filename, mode)
