# -*- coding: utf-8 -*-
"""
Created on Thu Mar 28 11:06:00 2013

@author: pietro
"""
import os
import multiprocessing as mltp
import subprocess as sub

from grass.script.setup import write_gisrc

from grass.pygrass.gis import Mapset, Location, make_mapset
from grass.pygrass.modules import Module

from split import split_region_tiles
from patch import patch_map


_GREG = Module('g.region')


def get_cmd(cmdd):
    """Transforma a cmd dictionary to a list of parameters"""
    cmd = [cmdd['name'], ]
    cmd.extend(("%s=%s" % (k, v) for k, v in cmdd['inputs']))
    cmd.extend(("%s=%s" % (k, v) for k, v in cmdd['outputs']))
    cmd.extend(("%s" % (flg) for flg in cmdd['flags'] if len(flg) == 1))
    cmd.extend(("--%s" % (flg[0]) for flg in cmdd['flags'] if len(flg) > 1))
    return cmd


def cmd_exe((bbox, mapnames, msetname, cmd)):
    """Create a mapset, and execute a cmd inside."""
    mset = Mapset()
    try:
        make_mapset(msetname)
    except:
        pass
    env = os.environ.copy()
    env['GISRC'] = write_gisrc(mset.gisdbase, mset.location, msetname)
    if mapnames:
        inputs = dict(cmd['inputs'])
        # reset the inputs to
        for key in mapnames:
            inputs[key] = mapnames[key]
        cmd['inputs'] = inputs.items()
        # set the region to the tile
        _GREG(env_=env, rast=key)
    else:
        # set the computational region
        _GREG(env_=env, **bbox)
    # run the grass command
    sub.Popen(get_cmd(cmd), env=env).wait()


class GridModule(object):
    """Run GRASS raster commands in a multiproccessing mode.

    Parameters
    -----------

    cmd: raster GRASS command
        Only command staring with r.* are valid.
    width: integer
        Width of the tile, in pixel.
    height: integer
        Height of the tile, in pixel.
    overlap: integer
        Overlap between tiles, in pixel.
    nthreads: number of threads
        Default value is equal to the number of processor available.
    split: boolean
        If True use r.tile to split all the inputs.
    run_: boolean
        If False only instantiate the object.
    args and kargs: cmd parameters
        Give all the parameters to the command.

    Examples
    --------

    ::

        >>> grd = GridModule('r.slope.aspect',
        ...                  width=500, height=500, overlap=2,
        ...                  processes=None, split=True,
        ...                  elevation='elevation',
        ...                  slope='slope', aspect='aspect', overwrite=True)
        >>> grd.run()
    """
    def __init__(self, cmd, width=None, height=None, overlap=0, processes=None,
                 split=False, *args, **kargs):
        kargs['run_'] = False
        self.mset = Mapset()
        self.module = Module(cmd, *args, **kargs)
        self.width = width
        self.height = height
        self.overlap = overlap
        self.processes = processes
        self.bboxes = split_region_tiles(width=width, height=height,
                                         overlap=overlap)
        self.msetstr = cmd.replace('.', '') + "_%03d_%03d"
        self.inlist = None
        if split:
            self.split()

    def clean_location(self):
        """Remove all created mapsets."""
        mapsets = Location().mapsets(self.msetstr.split('_')[0] + '_*')
        for mset in mapsets:
            Mapset(mset).delete()

    def split(self):
        """Split all the raster inputs using r.tile"""
        rtile = Module('r.tile')
        inlist = {}
        #import pdb; pdb.set_trace()
        for inmap in self.module.inputs:
            inm = self.module.inputs[inmap]
            if inm.type == 'raster' and inm.value:
                rtile(input=inm.value, output=inm.value,
                      width=self.width, height=self.height,
                      overlap=self.overlap)
                patt = '%s-*' % inm.value
                inlist[inm.value] = sorted(self.mset.glist(type='rast',
                                                           pattern=patt))
        self.inlist = inlist

    def get_works(self):
        """Return a list of tuble with the parameters for cmd_exe function"""
        works = []
        cmd = self.module.get_dict()
        for row, box_row in enumerate(self.bboxes):
            for col, box in enumerate(box_row):
                inms = None
                if self.inlist:
                    inms = {}
                    cols = len(box_row)
                    for key in self.inlist:
                        indx = row * cols + col
                        inms[key] = "%s@%s" % (self.inlist[key][indx],
                                               self.mset.name)
                # set the computational region, prepare the region parameters
                bbox = dict([(k[0], str(v)) for k, v in box.items()[:-2]])
                works.append((bbox, inms, self.msetstr % (row, col), cmd))
        return works

    def run(self, patch=True, clean=True):
        """Run the GRASS command."""
        self.module.flags.overwrite = True
        pool = mltp.Pool(processes=self.processes)
        result = pool.map_async(cmd_exe, self.get_works())
        result.wait()
        if not result.successful():
            raise RuntimeError

        if patch:
            self.patch()

        if clean:
            self.clean_location()
            self.rm_tiles()

    def patch(self):
        """Patch the final results."""
        # patch all the outputs
        for otmap in self.module.outputs:
            otm = self.module.outputs[otmap]
            if otm.type == 'raster' and otm.value:
                patch_map(otm.value, self.mset.name, self.msetstr,
                          split_region_tiles(width=self.width,
                                             height=self.height),
                          self.module.flags.overwrite)

    def rm_tiles(self):
        """Remove all the tiles."""
        # if split, remove tiles
        if self.inlist:
            grm = Module('g.remove')
            for key in self.inlist:
                grm(rast=self.inlist[key])
