#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Moodle Development Kit

Copyright (c) 2013 Frédéric Massart - FMCorz.net

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

http://github.com/FMCorz/mdk
"""

import logging
import os
import shutil
import stat
from contextlib import contextmanager
from pathlib import Path

from .config import Conf
from .exceptions import ConflictInScriptName, ScriptNotFound, UnsupportedScript
from .tools import process

C = Conf()


class Scripts(object):

    _supported = ['php', 'sh']
    _dirs = None
    _list = None

    @classmethod
    def dirs(cls):
        """Return the directories containing scripts, in priority order"""

        if not cls._dirs:
            dirs = ['~/.moodle-sdk']
            if C.get('dirs.moodle') != None:
                dirs.insert(0, C.get('dirs.moodle'))

            dirs.append('/etc/moodle-sdk')

            # Directory within the package.
            # This can point anywhere when the package is installed, or to the folder containing the module when it is not.
            packageDir = os.path.join(os.path.dirname(__file__), 'scripts')
            dirs.append(os.path.split(packageDir)[0])

            # Legacy: directory part of the root git repository, only if we can be sure that the parent directory is still MDK.
            if os.path.isfile(os.path.join(os.path.dirname(__file__), '..', 'mdk.py')):
                dirs.append(os.path.join(os.path.dirname(__file__), '..'))

            i = 0
            for d in dirs:
                dirs[i] = os.path.expanduser(os.path.join(d, 'scripts'))
                i += 1

            cls._dirs = dirs

        return cls._dirs

    @classmethod
    def list(cls):
        """Return a dict where keys are the name of the scripts
           and the value is the directory in which the script is stored"""

        if not cls._list:
            scripts = {}

            # Walk through the directories, in reverse to get the higher
            # priority last.
            dirs = cls.dirs()
            dirs.reverse()
            for d in dirs:

                if not os.path.isdir(d):
                    continue

                # For each file found in the directory.
                l = os.listdir(d)
                for f in l:

                    # Check if supported format.
                    supported = False
                    for ext in cls._supported:
                        if f.endswith('.' + ext):
                            supported = True
                            break

                    if supported:
                        scripts[f] = d

            cls._list = scripts

        return cls._list

    @classmethod
    def find(cls, script):
        """Return the path to a script"""

        lst = cls.list()
        cli = None
        if script in list(lst.keys()):
            cli = os.path.join(lst[script], script)
        else:
            found = 0
            for ext in cls._supported:
                candidate = script + '.' + ext
                if candidate in list(lst.keys()):
                    scriptFile = candidate
                    found += 1

            if found > 1:
                raise ConflictInScriptName('The script name conflicts with other ones')
            elif found == 1:
                cli = os.path.join(lst[scriptFile], scriptFile)

        if not cli:
            raise ScriptNotFound('Script not found')

        return cli

    @classmethod
    def get_script_destination(cls, cli, path):
        """Get the final path where the script will be copied"""

        ext = os.path.splitext(cli)[1]

        i = 0
        while True:
            candidate = os.path.join(path, 'mdkscriptrun{}{}'.format(i if i > 0 else '', ext))
            if not os.path.isfile(candidate):
                break
            i += 1

        return candidate

    @classmethod
    @contextmanager
    def prepare_script_in_path(cls, script, path, container=None):
        """Temporarily copy the script to a certain directory"""
        cli = cls.find(script)
        dest = cls.get_script_destination(cli, path)
        logging.debug('Copying %s to %s' % (cli, dest))
        shutil.copyfile(cli, dest)
        if dest.endswith('.sh'):
            if container:
                container.chmod(Path(dest), stat.S_IRUSR | stat.S_IXUSR)
            else:
                os.chmod(dest, stat.S_IRUSR | stat.S_IXUSR)
        yield dest
        os.remove(dest)

    @classmethod
    def run(cls, script, path, arguments=None, cmdkwargs={}):
        """Executes a script at in a certain directory"""

        # Converts arguments to a string.
        arguments = '' if arguments == None else arguments
        if type(arguments) == list:
            arguments = ' '.join(arguments)
        arguments = ' ' + arguments

        with cls.prepare_script_in_path(script, path) as dest:
            if dest.endswith('.php'):
                cmd = '%s %s %s' % (C.get('php'), dest, arguments)
                result = process(cmd, cwd=path, **cmdkwargs)
            elif dest.endswith('.sh'):
                cmd = '%s %s' % (dest, arguments)
                result = process(cmd, cwd=path, **cmdkwargs)
            else:
                raise UnsupportedScript('Script not supported')

        return result[0]
