import logging
import shutil
import tempfile
import subprocess
import os
import platform

from webodm import settings

logger = logging.getLogger('app.logger')

class GrassEngine:
    def __init__(self):
        self.grass_binary = shutil.which('grass7') or \
                            shutil.which('grass7.bat') or \
                            shutil.which('grass72') or \
                            shutil.which('grass72.bat') or \
                            shutil.which('grass74') or \
                            shutil.which('grass74.bat') or \
                            shutil.which('grass76') or \
                            shutil.which('grass76.bat') or \
                            shutil.which('grass78') or \
                            shutil.which('grass78.bat') or \
                            shutil.which('grass80') or \
                            shutil.which('grass80.bat')

        if self.grass_binary is None:
            logger.warning("Could not find a GRASS 7 executable. GRASS scripts will not work.")

    def create_context(self, serialized_context = {}):
        if self.grass_binary is None: raise GrassEngineException("GRASS engine is unavailable")
        return GrassContext(self.grass_binary, **serialized_context)


class GrassContext:
    def __init__(self, grass_binary, tmpdir = None, script_opts = {}, location = None, auto_cleanup=True, python_path=None):
        self.grass_binary = grass_binary
        if tmpdir is None:
            tmpdir = os.path.basename(tempfile.mkdtemp('_grass_engine', dir=settings.MEDIA_TMP))
        self.tmpdir = tmpdir
        self.script_opts = script_opts.copy()
        self.location = location
        self.auto_cleanup = auto_cleanup
        self.python_path = python_path

    def get_cwd(self):
        return os.path.join(settings.MEDIA_TMP, self.tmpdir)

    def add_file(self, filename, source, use_as_location=False):
        param = os.path.splitext(filename)[0] # filename without extension

        dst_path = os.path.abspath(os.path.join(self.get_cwd(), filename))
        with open(dst_path, 'w') as f:
            f.write(source)
        self.script_opts[param] = dst_path

        if use_as_location:
            self.set_location(self.script_opts[param])

        return dst_path

    def add_param(self, param, value):
        self.script_opts[param] = value

    def set_location(self, location):
        """
        :param location: either a "epsg:XXXXX" string or a path to a geospatial file defining the location
        """
        if not location.lower().startswith('epsg:'):
            location = os.path.abspath(location)
        self.location = location

    def execute(self, script):
        """
        :param script: path to .grass script
        :return: script output
        """
        if self.location is None: raise GrassEngineException("Location is not set")

        script = os.path.abspath(script)

        # Make sure working directory exists
        if not os.path.exists(self.get_cwd()):
            os.mkdir(self.get_cwd())

        # Create param list
        params = ["{}={}".format(opt,value) for opt,value in self.script_opts.items()]

        # Track success, output
        success = False
        out = ""
        err = ""

        # Setup env
        env = os.environ.copy()
        env["LC_ALL"] = "C.UTF-8"
        
        if self.python_path:
            sep = ";" if platform.system() == "Windows" else ":"
            env["PYTHONPATH"] = "%s%s%s" % (self.python_path, sep, env.get("PYTHONPATH", ""))

        # Execute it
        logger.info("Executing grass script from {}: {} -c {} location --exec python3 {} {}".format(self.get_cwd(), self.grass_binary, self.location, script, " ".join(params)))
        
        command = [self.grass_binary, '-c', self.location, 'location', '--exec', 'python3', script] + params
        if platform.system() == "Windows":
            # communicate() hangs on Windows so we use check_output instead
            try:
                out = subprocess.check_output(command, cwd=self.get_cwd(), env=env).decode('utf-8').strip()
                success = True
            except:
                success = False
                err = out
        else:
            p = subprocess.Popen(command, cwd=self.get_cwd(), env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            
            out, err = p.communicate()

            out = out.decode('utf-8').strip()
            err = err.decode('utf-8').strip()
            success = p.returncode == 0

        if success:
            return out
        else:
            raise GrassEngineException("Could not execute GRASS script {} from {}: {}".format(script, self.get_cwd(), err))

    def serialize(self):
        return {
            'tmpdir': self.tmpdir,
            'script_opts': self.script_opts,
            'location': self.location,
            'auto_cleanup': self.auto_cleanup,
            'python_path': self.python_path,
        }

    def cleanup(self):
        if os.path.exists(self.get_cwd()):
            shutil.rmtree(self.get_cwd())

    def __del__(self):
        if self.auto_cleanup:
            self.cleanup()

class GrassEngineException(Exception):
    pass

def cleanup_grass_context(serialized_context):
    ctx = grass.create_context(serialized_context)
    ctx.cleanup()

grass = GrassEngine()
