"""
@package gmodeler.model

@brief wxGUI Graphical Modeler (base classes & read/write)

Classes:
 - model::Model
 - model::ModelObject
 - model::ModelAction
 - model::ModelData
 - model::ModelRelation
 - model::ModelItem
 - model::ModelLoop
 - model::ModelCondition
 - model::ModelComment
 - model::ProcessModelFile
 - model::WriteModelFile
 - model::WritePythonFile
 - model::ModelParamDialog

(C) 2010-2014 by the GRASS Development Team

This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.

@author Martin Landa <landa.martin gmail.com>
"""

import os
import getpass
import copy
import re
import mimetypes
import time
try:
    import xml.etree.ElementTree as etree
except ImportError:
    import elementtree.ElementTree as etree # Python <= 2.4

import xml.sax.saxutils as saxutils

import wx
from wx.lib import ogl

from core                import globalvar
from core                import utils
from core.utils import _
from core.gcmd           import GMessage, GException, GError, RunCommand, EncodeString, GWarning, GetDefaultEncoding
from core.settings       import UserSettings
from gui_core.forms      import GUI, CmdPanel
from gui_core.widgets    import GNotebook
from gmodeler.giface import GraphicalModelerGrassInterface

from grass.script import core as grass
from grass.script import task as gtask

class Model(object):
    """Class representing the model"""
    def __init__(self, canvas = None):
        self.items   = list() # list of ordered items (action/loop/condition)
        
        # model properties
        self.properties = { 'name'        : _("model"),
                            'description' : _("Script generated by wxGUI Graphical Modeler."),
                            'author'      : getpass.getuser() }
        # model variables
        self.variables = dict()
        self.variablesParams = dict()
        
        self.canvas  = canvas
        
    def GetCanvas(self):
        """Get canvas or None"""
        return self.canvas

    def GetItems(self, objType = None):
        """Get list of model items

        :param objType: Object type to filter model objects
        """
        if not objType:
            return self.items
        
        result = list()
        for item in self.items:
            if isinstance(item, objType):
                result.append(item)
        
        return result
    
    def GetItem(self, aId, objType=None):
        """Get item of given id

        :param aId: item id
        
        :return: Model* instance
        :return: None if no item found
        """
        ilist = self.GetItems(objType)
        for item in ilist:
            if item.GetId() == aId:
                return item

        return None

    def GetItemIndex(self, item):
        """Return list index of given item"""
        return self.items.index(item)
    
    def GetNumItems(self, actionOnly = False):
        """Get number of items"""
        if actionOnly:
            return len(self.GetItems(objType = ModelAction))
        
        return len(self.GetItems())

    def ReorderItems(self, idxList):
        items = list()
        for oldIdx, newIdx in idxList.iteritems():
            item = self.items.pop(oldIdx)
            items.append(item)
            self.items.insert(newIdx, item)
            nextItem = self.items[newIdx+1]
            items.append(nextItem)
            x = item.GetX()
            y = item.GetY()
            item.SetX(nextItem.GetX())
            item.SetY(nextItem.GetY())
            nextItem.SetX(x)
            nextItem.SetY(y)

        dc = wx.ClientDC(self.canvas)
        for item in items:
            item.MoveLinks(dc)
            for mo in item.GetBlock():
                if isinstance(mo, ModelLoop):
                    self.canvas.parent.DefineLoop(mo)
                elif isinstance(mo, ModelCondition):
                    self.canvas.parent.DefineCondition(mo)
            
    def Normalize(self):
        # check for inconsistecies
        for idx in range(1, len(self.items)):
            if not self.items[idx].GetBlock() and \
                    isinstance(self.items[idx-1], ModelLoop):
                # swap action not-in-block with previously defined
                # loop
                itemPrev = self.items[idx-1]
                self.items[idx-1] = self.items[idx]
                self.items[idx] = itemPrev
        
        # update ids
        iId = 1
        for item in self.items:
            item.SetId(iId)
            item.SetLabel()
            iId += 1
        
    def GetNextId(self):
        """Get next id (data ignored)

        :return: next id to be used (default: 1)
        """
        if len(self.items) < 1:
            return 1
        
        currId = self.items[-1].GetId()
        if currId > 0:
            return currId + 1
        
        return 1

    def GetProperties(self):
        """Get model properties"""
        return self.properties

    def GetVariables(self, params = False):
        """Get model variables"""
        if params:
            return self.variablesParams
        
        return self.variables
    
    def SetVariables(self, data):
        """Set model variables"""
        self.variables = data
    
    def Reset(self):
        """Reset model"""
        self.items = list()

    def RemoveItem(self, item):
        """Remove item from model
        
        :return: list of related items to remove/update
        """
        relList = list()
        upList = list()
        
        self.items.remove(item)
        
        if isinstance(item, ModelAction):
            for rel in item.GetRelations():
                relList.append(rel)
                data = rel.GetData()
                if len(data.GetRelations()) < 2:
                    relList.append(data)
                else:
                    upList.append(data)
            
        elif isinstance(item, ModelData):
            for rel in item.GetRelations():
                relList.append(rel)
                if rel.GetFrom() == self:
                    relList.append(rel.GetTo())
                else:
                    relList.append(rel.GetFrom())
        
        elif isinstance(item, ModelLoop):
            for rel in item.GetRelations():
                relList.append(rel)
            for action in self.GetItems():
                action.UnSetBlock(item)
        
        return relList, upList
    
    def FindAction(self, aId):
        """Find action by id"""
        alist = self.GetItems(objType = ModelAction)
        for action in alist:
            if action.GetId() == aId:
                return action
        
        return None

    def GetMaps(self, prompt):
        """Get list of maps of selected type

        :param prompt: to filter maps"""
        maps = list()
        for data in self.GetData():
            if prompt == data.GetPrompt():
                mapName = data.GetValue()
                if not mapName or mapName[0] is '%':
                    continue # skip variables
                maps.append(mapName)
        
        return maps

    def GetData(self):
        """Get list of data items"""
        result = list()
        dataItems = self.GetItems(objType = ModelData)
        
        for action in self.GetItems(objType = ModelAction):
            for rel in action.GetRelations():
                dataItem = rel.GetData()
                if dataItem not in result:
                    result.append(dataItem)
                if dataItem in dataItems:
                    dataItems.remove(dataItem)
        
        # standalone data
        if dataItems:
            result += dataItems
        
        return result

    def FindData(self, value, prompt):
        """Find data item in the model

        :param value: value
        :param prompt: prompt

        :return: ModelData instance
        :return: None if not found
        """
        for data in self.GetData():
            if data.GetValue() == value and \
                    data.GetPrompt() == prompt:
                return data
        
        return None
                
    def LoadModel(self, filename):
        """Load model definition stored in GRASS Model XML file (gxm)
        
        .. todo::
             Validate against DTD
        
        Raise exception on error.
        """
        dtdFilename = os.path.join(globalvar.WXGUIDIR, "xml", "grass-gxm.dtd")
        
        # parse workspace file
        try:
            gxmXml = ProcessModelFile(etree.parse(filename))
        except StandardError as e:
            raise GException(unicode(e))
        
        if self.canvas:
            win = self.canvas.parent
            if gxmXml.pos:
                win.SetPosition(gxmXml.pos)
            if gxmXml.size:
                win.SetSize(gxmXml.size)
        
        # load properties
        self.properties = gxmXml.properties
        self.variables  = gxmXml.variables
        
        # load actions
        for action in gxmXml.actions:
            actionItem = ModelAction(parent = self, 
                                     x = action['pos'][0],
                                     y = action['pos'][1],
                                     width = action['size'][0],
                                     height = action['size'][1],
                                     task = action['task'],
                                     id = action['id'],
                                     label = action['label'],
                                     comment = action['comment'])
            
            if action['disabled']:
                actionItem.Enable(False)
            
            self.AddItem(actionItem, pos = actionItem.GetId()-1)
            
            actionItem.SetValid(actionItem.GetTask().get_options())
            actionItem.GetLog() # substitute variables (-> valid/invalid)

        # load data & relations
        for data in gxmXml.data:
            dataItem = ModelData(parent = self, 
                                 x = data['pos'][0],
                                 y = data['pos'][1],
                                 width = data['size'][0],
                                 height = data['size'][1],
                                 prompt = data['prompt'],
                                 value = data['value'])
            dataItem.SetIntermediate(data['intermediate'])
            
            for rel in data['rels']:
                actionItem = self.FindAction(rel['id'])
                
                if rel['dir'] == 'from':
                    relation = ModelRelation(parent = self, fromShape = dataItem,
                                             toShape = actionItem, param = rel['name'])
                else:
                    relation = ModelRelation(parent = self, fromShape = actionItem,
                                             toShape = dataItem, param = rel['name'])
                relation.SetControlPoints(rel['points'])
                actionItem.AddRelation(relation)
                dataItem.AddRelation(relation)

            if self.canvas:
                dataItem.Update()
           
        # load loops
        for loop in gxmXml.loops:
            loopItem = ModelLoop(parent = self, 
                                 x = loop['pos'][0],
                                 y = loop['pos'][1],
                                 width = loop['size'][0],
                                 height = loop['size'][1],
                                 label = loop['text'],
                                 id = loop['id'])
            self.AddItem(loopItem, pos = loopItem.GetId()-1)
        
        # load conditions
        for condition in gxmXml.conditions:
            conditionItem = ModelCondition(parent = self, 
                                           x = condition['pos'][0],
                                           y = condition['pos'][1],
                                           width = condition['size'][0],
                                           height = condition['size'][1],
                                           label = condition['text'],
                                           id = condition['id'])
            self.AddItem(conditionItem, pos = conditionItem.GetId()-1)

        # define loops & if/else items
        for loop in gxmXml.loops:
            loopItem = self.GetItem(loop['id'], objType=ModelLoop)
            loopItem.SetItems(loop['items'])
            for idx in loop['items']:
                action = self.GetItem(idx, objType=ModelAction)
                action.SetBlock(loopItem)
        
        for condition in gxmXml.conditions:
            conditionItem = self.GetItem(condition['id'])
            for b in condition['items'].keys():
                alist = list()
                for aId in condition['items'][b]:
                    action = self.GetItem(aId)
                    alist.append(action)
                conditionItem.SetItems(alist, branch = b)
            
            items = conditionItem.GetItems()
            for b in items.keys():
                for action in items[b]:
                    action.SetBlock(conditionItem)

        # load comments
        for comment in gxmXml.comments:
            commentItem = ModelComment(parent = self, 
                                       x = comment['pos'][0],
                                       y = comment['pos'][1],
                                       width = comment['size'][0],
                                       height = comment['size'][1],
                                       id = comment['id'],
                                       label = comment['text'])
            
            self.AddItem(commentItem, pos = commentItem.GetId()-1)
        
    def AddItem(self, newItem, pos = -1):
        """Add item to the list"""
        if pos != -1:
            self.items.insert(pos, newItem)
        else:
            self.items.append(newItem)
        # i = 1
        # for item in self.items:
        #     item.SetId(i)
        #     i += 1
        
    def IsValid(self):
        """Return True if model is valid"""
        if self.Validate():
            return False
        
        return True
    
    def Validate(self):
        """Validate model, return None if model is valid otherwise
        error string"""
        errList = list()

        variables = self.GetVariables().keys()
        pattern = re.compile(r'(.*)(%.+\s?)(.*)')
        for action in self.GetItems(objType = ModelAction):
            cmd = action.GetLog(string = False)
            
            task = GUI(show = None).ParseCommand(cmd = cmd)
            errList += map(lambda x: cmd[0] + ': ' + x, task.get_cmd_error())
            
            # check also variables
            for opt in cmd[1:]:
                if '=' not in opt:
                    continue
                key, value = opt.split('=', 1)
                sval = pattern.search(value)
                if sval:
                    var = sval.group(2).strip()[1:] # strip '%' from beginning
                    found = False
                    for v in variables:
                        if var.startswith(v):
                            found = True
                            break
                    if not found:
                        report = True
                        for item in filter(lambda x: isinstance(x, ModelLoop), action.GetBlock()):
                            if var in item.GetLabel():
                                report = False
                                break
                        if report:
                            errList.append(cmd[0] + ": " + _("undefined variable '%s'") % var)
            ### TODO: check variables in file only optionally
            ### errList += self._substituteFile(action, checkOnly = True)
        
        return errList

    def _substituteFile(self, item, params = None, checkOnly = False):
        """Subsitute variables in command file inputs

        :param bool checkOnly: tuble - True to check variable, don't touch files
        
        :return: list of undefined variables
        """
        errList = list()
        
        self.fileInput = dict()
        
        # collect ascii inputs
        for p in item.GetParams()['params']:
            if p.get('element', '') == 'file' and \
                    p.get('prompt', '') == 'input' and \
                    p.get('age', '') == 'old':
                filename = p.get('value', p.get('default', ''))
                if filename and \
                        mimetypes.guess_type(filename)[0] == 'text/plain':
                    self.fileInput[filename] = None
        
        for finput in self.fileInput:
            # read lines
            fd = open(finput, "r")
            try:
                data = self.fileInput[finput] = fd.read()
            finally:
                fd.close()
            
            # substitute variables
            write = False
            variables = self.GetVariables()
            for variable in variables:
                pattern = re.compile('%' + variable)
                value = ''
                if params and 'variables' in params:
                    for p in params['variables']['params']:
                        if variable == p.get('name', ''):
                            if p.get('type', 'string') == 'string':
                                value = p.get('value', '')
                            else:
                                value = str(p.get('value', ''))
                            break
                
                if not value:
                    value = variables[variable].get('value', '')
                
                data = pattern.sub(value, data)
                if not checkOnly:
                    write = True
            
            pattern = re.compile(r'(.*)(%.+\s?)(.*)')
            sval = pattern.search(data)
            if sval:
                var = sval.group(2).strip()[1:] # ignore '%'
                cmd = item.GetLog(string = False)[0]
                errList.append(cmd + ": " + _("undefined variable '%s'") % var)
            
            if not checkOnly:
                if write:
                    fd = open(finput, "w")
                    try:
                        fd.write(data)
                    finally:
                        fd.close()
                else:
                    self.fileInput[finput] = None
        
        return errList
    
    def OnPrepare(self, item, params):
        self._substituteFile(item, params, checkOnly = False)

    def RunAction(self, item, params, log, onDone, onPrepare = None, statusbar = None):
        """Run given action

        :param item: action item
        :param params: parameters dict
        :param log: logging window
        :param onDone: on-done method
        :param onPrepare: on-prepare method
        :param statusbar: wx.StatusBar instance or None
        """
        name = item.GetLabel()
        if name in params:
            paramsOrig = item.GetParams(dcopy = True)
            item.MergeParams(params[name])
        
        if statusbar:
            statusbar.SetStatusText(_('Running model...'), 0)
            
        data = { 'item' : item,
                 'params' : copy.deepcopy(params) }
        log.RunCmd(command = item.GetLog(string = False, substitute = params),
                   onDone = onDone, onPrepare = self.OnPrepare, userData = data)
        
        if name in params:
            item.SetParams(paramsOrig)

    def Run(self, log, onDone, parent = None):
        """Run model

        :param log: logging window (see gconsole.GConsole)
        :param onDone: on-done method
        :param parent: window for messages or None
        """
        if self.GetNumItems() < 1:
            GMessage(parent = parent,
                     message = _('Model is empty. Nothing to run.'))
            return
        
        statusbar = None
        if isinstance(parent, wx.Frame):
            statusbar = parent.GetStatusBar()
        
        # validation
        if statusbar:
            statusbar.SetStatusText(_('Validating model...'), 0)
        errList = self.Validate()
        if statusbar:
            statusbar.SetStatusText('', 0)
        if errList:
            dlg = wx.MessageDialog(parent = parent,
                                   message = _('Model is not valid. Do you want to '
                                               'run the model anyway?\n\n%s') % '\n'.join(errList),
                                   caption = _("Run model?"),
                                   style = wx.YES_NO | wx.NO_DEFAULT |
                                   wx.ICON_QUESTION | wx.CENTRE)
            ret = dlg.ShowModal()
            dlg.Destroy()
            if ret != wx.ID_YES:
                return
        
        # parametrization
        params = self.Parameterize()
        delInterData = False
        if params:
            dlg = ModelParamDialog(parent = parent,
                                   model = self,
                                   params = params)
            dlg.CenterOnParent()
            
            ret = dlg.ShowModal()
            if ret != wx.ID_OK:
                dlg.Destroy()
                return
            
            err = dlg.GetErrors()
            delInterData = dlg.DeleteIntermediateData()
            dlg.Destroy()
            if err:
                GError(parent = parent, message = unicode('\n'.join(err)))
                return
            
            err = list()
            for key, item in params.iteritems():
                for p in item['params']:
                    if p.get('value', '') == '':
                        err.append((key, p.get('name', ''), p.get('description', '')))
            if err:
                GError(parent = parent,
                       message = _("Variables below not defined:") + \
                           "\n\n" + unicode('\n'.join(map(lambda x: "%s: %s (%s)" % (x[0], x[1], x[2]), err))))
                return
        
        log.cmdThread.SetId(-1)
        for item in self.GetItems():
            if not item.IsEnabled():
                continue
            if isinstance(item, ModelAction):
                if item.GetBlockId():
                    continue
                self.RunAction(item, params, log, onDone)
            elif isinstance(item, ModelLoop):
                cond = item.GetLabel()
                
                # substitute variables in condition
                variables = self.GetVariables()
                for variable in variables:
                    pattern = re.compile('%' + variable)
                    if pattern.search(cond):
                        value = ''
                        if params and 'variables' in params:
                            for p in params['variables']['params']:
                                if variable == p.get('name', ''):
                                    value = p.get('value', '')
                                    break
                        
                        if not value:
                            value = variables[variable].get('value', '')
                        
                        if not value:
                            continue
                        
                        vtype = variables[variable].get('type', 'string')
                        if vtype == 'string':
                            value = '"' + value + '"'
                        cond = pattern.sub(value, cond)
                
                # split condition
                ### TODO: this part needs some better solution
                condVar, condText = map(lambda x: x.strip(), re.split('\s* in \s*', cond))
                pattern = re.compile('%' + condVar)
                ### for vars()[condVar] in eval(condText): ?
                vlist = list()
                if condText[0] == '`' and condText[-1] == '`':
                    # run command
                    cmd, dcmd = utils.CmdToTuple(condText[1:-1].split(' '))
                    ret = RunCommand(cmd,
                                     read = True,
                                     **dcmd)
                    if ret:
                        vlist = ret.splitlines()
                else:
                    vlist = eval(condText)
                
                if 'variables' not in params:
                    params['variables'] = { 'params' : [] }
                varDict = { 'name' : condVar, 'value' : '' }
                params['variables']['params'].append(varDict)
                                
                for var in vlist:
                    for action in item.GetItems(self.GetItems()):
                        if not action.IsEnabled():
                            continue
                        
                        varDict['value'] = var
                                                
                        self.RunAction(item = action, params = params,
                                       log = log, onDone = onDone)
                params['variables']['params'].remove(varDict)
        
        if delInterData:
            self.DeleteIntermediateData(log)
        
        # discard values
        if params:
            for item in params.itervalues():
                for p in item['params']:
                    p['value'] = ''
        
    def DeleteIntermediateData(self, log):
        """Detele intermediate data"""
        rast, vect, rast3d, msg = self.GetIntermediateData()
        
        if rast:
            log.RunCmd(['g.remove', '-f', 'type=raster', 'name=%s' %','.join(rast)])
        if rast3d:
            log.RunCmd(['g.remove', '-f', 'type=raster_3d', 'name=%s' %','.join(rast3d)])
        if vect:
            log.RunCmd(['g.remove', '-f', 'type=vector', 'name=%s' %','.join(vect)])
        
    def GetIntermediateData(self):
        """Get info about intermediate data"""
        rast = list()
        rast3d = list()
        vect = list()
        for data in self.GetData():
            if not data.IsIntermediate():
                continue
            name = data.GetValue()
            prompt = data.GetPrompt()
            if prompt == 'raster':
                rast.append(name)
            elif prompt == 'vector':
                vect.append(name)
            elif prompt == 'raster_3d':
                rast3d.append(name)
        
        msg = ''
        if rast:
            msg += '\n\n%s: ' % _('Raster maps')
            msg += ', '.join(rast)
        if rast3d:
            msg += '\n\n%s: ' % _('3D raster maps')
            msg += ', '.join(rast3d)
        if vect:
            msg += '\n\n%s: ' % _('Vector maps')
            msg += ', '.join(vect)
        
        return rast, vect, rast3d, msg

    def Update(self):
        """Update model"""
        for item in self.items:
            item.Update()
        
    def IsParameterized(self):
        """Return True if model is parameterized"""
        if self.Parameterize():
            return True
        
        return False
    
    def Parameterize(self):
        """Return parameterized options"""
        result = dict()
        idx = 0
        if self.variables:
            params = list()
            result["variables"] = { 'flags'  : list(),
                                    'params' : params,
                                    'idx'    : idx }
            for name, values in self.variables.iteritems():
                gtype = values.get('type', 'string')
                if gtype in ('raster', 'vector', 'mapset', 'file', 'region', 'dir'):
                    gisprompt = True
                    prompt = gtype
                    if gtype == 'raster':
                        element = 'cell'
                    else:
                        element = gtype
                    ptype = 'string'
                else:
                    gisprompt = False
                    prompt = None
                    element = None
                    ptype = gtype
                params.append({ 'gisprompt' : gisprompt,
                                'multiple'  : False,
                                'description' : values.get('description', ''),
                                'guidependency' : '',
                                'default' : '',
                                'age' : None,
                                'required' : True,
                                'value' : values.get('value', ''),
                                'label' : '',
                                'guisection' : '',
                                'key_desc' : '',
                                'values' : list(),
                                'parameterized' : False,
                                'values_desc' : list(),
                                'prompt' : prompt,
                                'element' : element,
                                'type' : ptype,
                                'name' : name })
            
            idx += 1
        
        for action in self.GetItems(objType = ModelAction):
            if not action.IsEnabled():
                continue
            name   = action.GetLabel()
            params = action.GetParams()
            for f in params['flags']:
                if f.get('parameterized', False):
                    if name not in result:
                        result[name] = { 'flags' : list(),
                                         'params': list(),
                                         'idx'   : idx }
                    result[name]['flags'].append(f)
            for p in params['params']:
                if p.get('parameterized', False):
                    if name not in result:
                        result[name] = { 'flags' : list(),
                                         'params': list(),
                                         'idx'   : idx }
                    result[name]['params'].append(p)
            if name in result:
                idx += 1
        
        self.variablesParams = result # record parameters
        
        return result

class ModelObject(object):
    def __init__(self, id = -1, label = ''):
        self.id   = id     # internal id, should be not changed
        self.label = ''
        self.rels = list() # list of ModelRelations
        
        self.isEnabled = True
        self.inBlock   = list() # list of related loops/conditions
        
    def __del__(self):
        pass

    def GetLabel(self):
        """Get label"""
        return self.label

    def SetLabel(self, label=''):
        """Set label"""
        self.label = label
        
    def GetId(self):
        """Get id"""
        return self.id

    def SetId(self, newId):
        """Set id"""
        if self.inBlock:
            for loop in self.inBlock:
                # update block item
                loop.UpdateItem(self.id, newId)
        
        self.id = newId


    def AddRelation(self, rel):
        """Record new relation
        """
        self.rels.append(rel)

    def GetRelations(self, fdir = None):
        """Get list of relations
        
        :param bool fdir: True for 'from'
        """
        if fdir is None:
            return self.rels
        
        result = list()
        for rel in self.rels:
            if fdir == 'from':
                if rel.GetFrom() == self:
                    result.append(rel)
            else:
                if rel.GetTo() == self:
                    result.append(rel)
        
        return result
    
    def IsEnabled(self):
        """Get True if action is enabled, otherwise False"""
        return self.isEnabled
    
    def Enable(self, enabled = True):
        """Enable/disable action"""
        self.isEnabled = enabled
        self.Update()

    def Update(self):
        pass

    def SetBlock(self, item):
        """Add object to the block (loop/condition)

        :param item: reference to ModelLoop or ModelCondition which
                     defines loops/condition
        """
        if item not in self.inBlock:
            self.inBlock.append(item)
        
    def UnSetBlock(self, item):
        """Remove object from the block (loop/consition)

        :param item: reference to ModelLoop or ModelCondition which
                     defines loops/codition
        """
        if item in self.inBlock:
            self.inBlock.remove(item)
        
    def GetBlock(self):
        """Get list of related ModelObject(s) which defines block
        (loop/condition)

        :return: list of ModelObjects
        """
        return self.inBlock
    
    def GetBlockId(self):
        """Get list of related ids which defines block

        :return: list of ids
        """
        ret = list()
        for mo in self.inBlock:
            ret.append(mo.GetId())
        
        return ret
    
class ModelAction(ModelObject, ogl.DividedShape):
    """Action class (GRASS module)"""
    def __init__(self, parent, x, y, id = -1, cmd = None, task = None,
                 width = None, height = None, label = None, comment = ''):
        ModelObject.__init__(self, id, label)
        
        self.parent  = parent
        self.task    = task
        self.comment = comment
        
        if not width:
            width = UserSettings.Get(group='modeler', key='action', subkey=('size', 'width'))
        if not height:
            height = UserSettings.Get(group='modeler', key='action', subkey=('size', 'height'))
        
        if cmd:
            self.task = GUI(show = None).ParseCommand(cmd = cmd)
        else:
            if task:
                self.task = task
            else:
                self.task = None
        
        self.propWin = None
        
        self.data = list()   # list of connected data items
        
        self.isValid = False
        self.isParameterized = False
        
        if self.parent.GetCanvas():
            ogl.DividedShape.__init__(self, width, height)
        
            self.regionLabel = ogl.ShapeRegion()
            self.regionLabel.SetFormatMode(ogl.FORMAT_CENTRE_HORIZ | ogl.FORMAT_CENTRE_VERT)
            self.AddRegion(self.regionLabel)
            
            self.regionComment = None
            
            self.SetCanvas(self.parent)
            self.SetX(x)
            self.SetY(y)
            self._setPen()
            self._setBrush()
            self.SetLabel(label)
            if comment:
                self.SetComment(comment)
            
            self.SetRegionSizes()
            self.ReformatRegions()

        if self.task:
            self.SetValid(self.task.get_options())
        
    def _setBrush(self, running = False):
        """Set brush"""
        if running:
            color = UserSettings.Get(group='modeler', key='action',
                                     subkey=('color', 'running'))
        elif not self.isEnabled:
            color = UserSettings.Get(group='modeler', key='disabled',
                                     subkey='color')
        elif self.isValid:
            color = UserSettings.Get(group='modeler', key='action',
                                     subkey=('color', 'valid'))
        else:
            color = UserSettings.Get(group='modeler', key='action',
                                     subkey=('color', 'invalid'))
        
        wxColor = wx.Colour(color[0], color[1], color[2])
        self.SetBrush(wx.Brush(wxColor))
        
    def _setPen(self):
        """Set pen"""
        if self.isParameterized:
            width = int(UserSettings.Get(group='modeler', key='action',
                                         subkey=('width', 'parameterized')))
        else:
            width = int(UserSettings.Get(group='modeler', key='action',
                                         subkey=('width', 'default')))
        if self.isEnabled:
            style = wx.SOLID
        else:
            style = wx.DOT
        
        pen = wx.Pen(wx.BLACK, width, style)
        self.SetPen(pen)

    def ReformatRegions(self):
        rnum = 0
        canvas = self.parent.GetCanvas()
        
        dc = wx.ClientDC(canvas)  # used for measuring

        for region in self.GetRegions():
            text = region.GetText()
            self.FormatText(dc, text, rnum)
            rnum += 1

    def OnSizingEndDragLeft(self, pt, x, y, keys, attch):
        ogl.DividedShape.OnSizingEndDragLeft(self, pt, x, y, keys, attch)
        self.SetRegionSizes()
        self.ReformatRegions()
        self.GetCanvas().Refresh()

    def SetLabel(self, label=None):
        """Set label

        :param label: if None use command string instead
        """
        if label:
            self.label = label 
        elif self.label:
            label = self.label
        else:
            try:
                label = self.task.get_cmd(ignoreErrors = True)[0]
            except:
                label = _("unknown")
        
        idx = self.GetId()
        self.regionLabel.SetText('(%d) %s' % (idx, label))
        self.SetRegionSizes()
        self.ReformatRegions()

    def SetComment(self, comment):
        """Set comment"""
        if self.regionComment is None:
            self.regionComment = ogl.ShapeRegion()
            self.regionComment.SetFormatMode(ogl.FORMAT_CENTRE_HORIZ)
            font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
            font.SetStyle(wx.ITALIC)
            self.regionComment.SetFont(font)
            self.AddRegion(self.regionComment)
            self.regionLabel.SetProportions(0.0, 0.4)
            
        self.comment = comment
        self.regionComment.SetText(comment)
        self.SetRegionSizes()
        self.ReformatRegions()

    def GetComment(self):
        """Get comment"""
        return self.comment

    def SetProperties(self, params, propwin):
        """Record properties dialog"""
        self.task.params = params['params']
        self.task.flags  = params['flags']
        self.propWin = propwin

    def GetPropDialog(self):
        """Get properties dialog"""
        return self.propWin

    def GetLog(self, string = True, substitute = None):
        """Get logging info

        :param string: True to get cmd as a string otherwise a list
        :param substitute: dictionary of parameter to substitute or None
        """
        cmd = self.task.get_cmd(ignoreErrors = True, ignoreRequired = True,
                                ignoreDefault = False)
        
        # substitute variables
        if substitute:
            variables = []
            if 'variables' in substitute:
                for p in substitute['variables']['params']:
                    variables.append(p.get('name', ''))
            else:
                variables = self.parent.GetVariables()

            # order variables by length
            for variable in sorted(variables, key=len, reverse=True):
                pattern= re.compile('%' + variable)
                value = ''
                if substitute and 'variables' in substitute:
                    for p in substitute['variables']['params']:
                        if variable == p.get('name', ''):
                            if p.get('type', 'string') == 'string':
                                value = p.get('value', '')
                            else:
                                value = str(p.get('value', ''))
                            break
                    
                if not value:
                    value = variables[variable].get('value', '')
                
                if not value:
                    continue
                
                for idx in range(len(cmd)):
                    if pattern.search(cmd[idx]):
                        cmd[idx] = pattern.sub(value, cmd[idx])
                        break
                    idx += 1
        
        if string:
            if cmd is None:
                return ''
            else:
                return ' '.join(cmd)
        
        return cmd
    
    def GetLabel(self):
        """Get name"""
        if self.label:
            return self.label
        
        cmd = self.task.get_cmd(ignoreErrors = True)
        if cmd and len(cmd) > 0:
            return cmd[0]
        
        return _('unknown')

    def GetParams(self, dcopy = False):
        """Get dictionary of parameters"""
        if dcopy:
            return copy.deepcopy(self.task.get_options())
        
        return self.task.get_options()

    def GetTask(self):
        """Get grassTask instance"""
        return self.task
    
    def SetParams(self, params):
        """Set dictionary of parameters"""
        self.task.params = params['params']
        self.task.flags  = params['flags']
        
    def MergeParams(self, params):
        """Merge dictionary of parameters"""
        if 'flags' in params:
            for f in params['flags']:
                self.task.set_flag(f['name'],
                                   f.get('value', False))
        if 'params' in params:
            for p in params['params']:
                self.task.set_param(p['name'],
                                    p.get('value', ''))
        
    def SetValid(self, options):
        """Set validity for action
        
        :param options: dictionary with flags and params (gtask)
        """
        self.isValid = True

        for f in options['flags']:
            if f.get('parameterized', False):
                self.isParameterized = True
                break

        for p in options['params']:
            if self.isValid and p.get('required', False) and \
               p.get('value', '') == '' and \
               p.get('default', '') == '':
                self.isValid = False
            if not self.isParameterized and p.get('parameterized', False):
                self.isParameterized = True
        
        if self.parent.GetCanvas():
            self._setBrush()
            self._setPen()
        
    def IsValid(self):
        """Check validity (all required parameters set)"""
        return self.isValid
    
    def IsParameterized(self):
        """Check if action is parameterized"""
        return self.isParameterized

    def GetParameterizedParams(self):
        """Return parameterized flags and options"""
        param = { 'flags': [], 'params' : [] }
        
        options = self.GetParams()
        
        for f in options['flags']:
            if f.get('parameterized', False):
                param['flags'].append(f)
        
        for p in options['params']:
            if p.get('parameterized', False):
                param['params'].append(p)
        
        return param
        
    def FindData(self, name):
        """Find data item by name"""
        for rel in self.GetRelations():
            data = rel.GetData()
            if name == rel.GetLabel() and name in data.GetLabel():
                return data
        
        return None

    def Update(self, running = False):
        """Update action"""
        if running:
            self._setBrush(running = True)
        else:
            self._setBrush()
        self._setPen()

    def OnDraw(self, dc):
        """Draw action in canvas"""
        self._setBrush()
        self._setPen()
        ogl.RectangleShape.Recentre(self, dc) # re-center text
        ogl.RectangleShape.OnDraw(self, dc)

class ModelData(ModelObject, ogl.EllipseShape):
    def __init__(self, parent, x, y, value = '', prompt = '', width = None, height = None):
        """Data item class
        
        :param parent: window parent
        :param x, y: position of the shape
        :param fname, tname: list of parameter names from / to
        :param value: value
        :param prompt: type of GIS element
        :param width, height: dimension of the shape
        """
        ModelObject.__init__(self)
        
        self.parent  = parent
        self.value   = value
        self.prompt  = prompt
        self.intermediate = False
        self.propWin = None
        if not width:
            width = UserSettings.Get(group='modeler', key='data', subkey=('size', 'width'))
        if not height:
            height = UserSettings.Get(group='modeler', key='data', subkey=('size', 'height'))
        
        if self.parent.GetCanvas():
            ogl.EllipseShape.__init__(self, width, height)
            
            self.SetCanvas(self.parent)
            self.SetX(x)
            self.SetY(y)
            self._setPen()
            self._setBrush()
            self.SetLabel()
            
    def IsIntermediate(self):
        """Checks if data item is intermediate"""
        return self.intermediate
    
    def SetIntermediate(self, im):
        """Set intermediate flag"""
        self.intermediate = im
  
    def OnDraw(self, dc):
        self._setPen()
        
        ogl.EllipseShape.OnDraw(self, dc)
        
    def GetLog(self, string = True):
        """Get logging info"""
        name = list()
        for rel in self.GetRelations():
            name.append(rel.GetLabel())
        if name:
            return '/'.join(name) + '=' + self.value + ' (' + self.prompt + ')'
        else:
            return self.value + ' (' + self.prompt + ')'

    def GetLabel(self):
        """Get list of names"""
        name = list()
        for rel in self.GetRelations():
            name.append(rel.GetLabel())
        
        return name
    
    def GetPrompt(self):
        """Get prompt"""
        return self.prompt

    def SetPrompt(self, prompt):
        """Set prompt
        
        :param prompt:
        """
        self.prompt = prompt
        
    def GetValue(self):
        """Get value"""
        return self.value

    def SetValue(self, value):
        """Set value

        :param value:
        """
        self.value = value
        self.SetLabel()
        for direction in ('from', 'to'):
            for rel in self.GetRelations(direction):
                if direction == 'from':
                    action = rel.GetTo()
                else:
                    action = rel.GetFrom()

                task = GUI(show = None).ParseCommand(cmd = action.GetLog(string = False))
                task.set_param(rel.GetLabel(), self.value)
                action.MergeParams(task.get_options())
        
    def GetPropDialog(self):
        """Get properties dialog"""
        return self.propWin

    def SetPropDialog(self, win):
        """Get properties dialog"""
        self.propWin = win

    def _setBrush(self):
        """Set brush"""
        if self.prompt == 'raster':
            color = UserSettings.Get(group = 'modeler', key = 'data',
                                     subkey = ('color', 'raster'))
        elif self.prompt == 'raster3d':
            color = UserSettings.Get(group = 'modeler', key = 'data',
                                     subkey = ('color', 'raster3d'))
        elif self.prompt == 'vector':
            color = UserSettings.Get(group = 'modeler', key = 'data',
                                     subkey = ('color', 'vector'))
        else:
            color = UserSettings.Get(group = 'modeler', key = 'action',
                                     subkey = ('color', 'invalid'))
        wxColor = wx.Colour(color[0], color[1], color[2])
        self.SetBrush(wx.Brush(wxColor))
        
    def _setPen(self):
        """Set pen"""
        isParameterized = False
        for rel in self.GetRelations('from'):
            if rel.GetTo().IsParameterized():
                isParameterized = True
                break
        if not isParameterized:
            for rel in self.GetRelations('to'):
                if rel.GetFrom().IsParameterized():
                    isParameterized = True
                    break

        if isParameterized:
            width = int(UserSettings.Get(group = 'modeler', key = 'action',
                                         subkey = ('width', 'parameterized')))
        else:
            width = int(UserSettings.Get(group = 'modeler', key = 'action',
                                         subkey = ('width', 'default')))
        if self.intermediate:
            style = wx.DOT
        else:
            style = wx.SOLID
        
        pen = wx.Pen(wx.BLACK, width, style)
        self.SetPen(pen)
        
    def SetLabel(self):
        """Update text"""
        self.ClearText()
        name = []
        for rel in self.GetRelations():
            name.append(rel.GetLabel())
        self.AddText('/'.join(name))
        if self.value:
            self.AddText(self.value)
        else:
            self.AddText(_('<not defined>'))
        
    def Update(self):
        """Update action"""
        self._setBrush()
        self._setPen()
        self.SetLabel()

class ModelRelation(ogl.LineShape):
    """Data - action relation"""
    def __init__(self, parent, fromShape, toShape, param = ''):
        self.fromShape = fromShape
        self.toShape   = toShape
        self.param     = param
        self.parent    = parent
        
        self._points    = None
        
        if self.parent.GetCanvas():        
            ogl.LineShape.__init__(self)
    
    def __del__(self):
        if self in self.fromShape.rels:
            self.fromShape.rels.remove(self)
        if self in self.toShape.rels:
            self.toShape.rels.remove(self)

    def GetFrom(self):
        """Get id of 'from' shape"""
        return self.fromShape
    
    def GetTo(self):
        """Get id of 'to' shape"""
        return self.toShape
    
    def GetData(self):
        """Get related ModelData instance

        :return: ModelData instance
        :return: None if not found
        """
        if isinstance(self.fromShape, ModelData):
            return self.fromShape
        elif isinstance(self.toShape, ModelData):
            return self.toShape
        
        return None
    
    def GetLabel(self):
        """Get parameter name"""
        return self.param
    
    def ResetShapes(self):
        """Reset related objects"""
        self.fromShape.ResetControlPoints()
        self.toShape.ResetControlPoints()
        self.ResetControlPoints()
        
    def SetControlPoints(self, points):
        """Set control points"""
        self._points = points
        
    def GetControlPoints(self):
        """Get list of control points"""
        return self._points
    
    def _setPen(self):
        """Set pen"""
        pen = wx.Pen(wx.BLACK, 1, wx.SOLID)
        self.SetPen(pen)
        
    def OnDraw(self, dc):
        """Draw relation"""
        self._setPen()
        ogl.LineShape.OnDraw(self, dc)
    
    def SetName(self, param):
        self.param = param

class ModelItem(ModelObject):
    def __init__(self, parent, x, y, id = -1, width = None, height = None, label = '', items = []):
        """Abstract class for loops and conditions"""
        ModelObject.__init__(self, id, label)
        self.parent  = parent

    def _setPen(self):
        """Set pen"""
        if self.isEnabled:
            style = wx.SOLID
        else:
            style = wx.DOT
        
        pen = wx.Pen(wx.BLACK, 1, style)
        self.SetPen(pen)

    def SetId(self, id):
        """Set loop id"""
        self.id = id

    def SetLabel(self, label=''):
        """Set loop text (condition)"""
        if label:
            self.label = label
        self.ClearText()
        self.AddText('(' + str(self.id) + ') ' + self.label)

    def GetLog(self):
        """Get log info"""
        if self.label:
            return _("Condition: ") + self.label
        else:
            return _("Condition: not defined")

    def AddRelation(self, rel):
        """Record relation"""
        self.rels.append(rel)
        
    def Clear(self):
        """Clear object, remove rels"""
        self.rels = list()
   
class ModelLoop(ModelItem, ogl.RectangleShape):
    def __init__(self, parent, x, y, id=-1, idx=-1, width = None, height = None, label = '', items = []):
        """Defines a loop"""
        ModelItem.__init__(self, parent, x, y, id, width, height, label, items)
        self.itemIds = list() # unordered        

        if not width:
            width = UserSettings.Get(group='modeler', key='loop', subkey=('size', 'width'))
        if not height:
            height = UserSettings.Get(group='modeler', key='loop', subkey=('size', 'height'))
        
        if self.parent.GetCanvas():
            ogl.RectangleShape.__init__(self, width, height)
            
            self.SetCanvas(self.parent)
            self.SetX(x)
            self.SetY(y)
            self._setPen()
            self._setBrush()
            self.SetCornerRadius(100)
            self.SetLabel(label)

    def _setBrush(self):
        """Set brush"""
        if not self.isEnabled:
            color = UserSettings.Get(group='modeler', key='disabled',
                                     subkey='color')
        else:
            color = UserSettings.Get(group='modeler', key='loop',
                                     subkey=('color', 'valid'))
        
        wxColor = wx.Colour(color[0], color[1], color[2])
        self.SetBrush(wx.Brush(wxColor))

    def Enable(self, enabled = True):
        """Enable/disable action"""
        for idx in self.itemIds:
            item = self.parent.FindAction(idx)
            if item:
                item.Enable(enabled)
        
        ModelObject.Enable(self, enabled)
        self.Update()

    def Update(self):
        self._setPen()
        self._setBrush()

    def GetItems(self, items):
        """Get sorted items by id"""
        result = list()
        for item in items:
            if item.GetId() in self.itemIds:
                result.append(item)
        
        return result

    def SetItems(self, items):
        """Set items (id)"""
        self.itemIds = items

    def UpdateItem(self, oldId, newId):
        """Update item in the list"""
        idx = self.itemIds.index(oldId)
        if idx != -1:
            self.itemIds[idx] = newId
        
    def OnDraw(self, dc):
        """Draw loop in canvas"""
        self._setBrush()
        ogl.RectangleShape.Recentre(self, dc) # re-center text
        ogl.RectangleShape.OnDraw(self, dc)

class ModelCondition(ModelItem, ogl.PolygonShape):
    def __init__(self, parent, x, y, id = -1, width = None, height = None, label = '',
                 items = { 'if' : [], 'else' : [] }):
        """Defines a if-else condition"""
        ModelItem.__init__(self, parent, x, y, id, width, height, label, items)
        self.itemIds = {'if' : [], 'else': []}
        
        if not width:
            self.width = UserSettings.Get(group='modeler', key='if-else', subkey=('size', 'width'))
        else:
            self.width = width
        if not height:
            self.height = UserSettings.Get(group='modeler', key='if-else', subkey=('size', 'height'))
        else:
            self.height = height
        
        if self.parent.GetCanvas():
            ogl.PolygonShape.__init__(self)
            
            points = [(0, - self.height / 2),
                      (self.width / 2, 0),
                      (0, self.height / 2),
                      (- self.width / 2, 0)]
            self.Create(points)
            
            self.SetCanvas(self.parent)
            self.SetX(x)
            self.SetY(y)
            self.SetPen(wx.BLACK_PEN)
            if label:
                self.AddText('(' + str(self.id) + ') ' + label)
            else:
                self.AddText('(' + str(self.id) + ')')

    def GetLabel(self):
        """Get name"""
        return _("if-else")

    def GetWidth(self):
        """Get object width"""
        return self.width

    def GetHeight(self):
        """Get object height"""
        return self.height

    def GetItems(self, items):
        """Get sorted items by id"""
        result = {'if' : [], 'else': []}
        for item in items:
            if item.GetId() in self.itemIds['if']:
                result['if'].append(item)
            elif item.GetId() in self.itemIds['else']:
                result['else'].append(item)
        
        return result

    def SetItems(self, items, branch = 'if'):
        """Set items (id)

        :param items: list of items
        :param branch: 'if' / 'else'
        """
        if branch in ['if', 'else']:
            self.itemIds[branch] = items

class ModelComment(ModelObject, ogl.RectangleShape):
    def __init__(self, parent, x, y, id = -1, width = None, height = None, label = ''):
        """Defines a model comment"""
        ModelObject.__init__(self, id, label)

        if not width:
            width = UserSettings.Get(group='modeler', key='comment', subkey=('size', 'width'))
        if not height:
            height = UserSettings.Get(group='modeler', key='comment', subkey=('size', 'height'))
        
        if parent.GetCanvas():
            ogl.RectangleShape.__init__(self, width, height)
            self.SetCanvas(parent)
            self.SetX(x)
            self.SetY(y)
            font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
            font.SetStyle(wx.ITALIC)
            self.SetFont(font)
            self._setPen()
            self._setBrush()
            self.SetLabel(label)

    def _setBrush(self, running = False):
        """Set brush"""
        color = UserSettings.Get(group='modeler', key='comment',
                                     subkey='color')
        wxColor = wx.Colour(color[0], color[1], color[2])
        self.SetBrush(wx.Brush(wxColor))

    def _setPen(self):
        """Set pen"""
        pen = wx.Pen(wx.BLACK, 1, wx.DOT)
        self.SetPen(pen)

    def SetLabel(self, label=None):
        """Set label

        :param label: if None use command string instead
        """
        if label:
            self.label = label 
        elif self.label:
            label = self.label
        else:
            label = ''
        idx = self.GetId()
        
        self.ClearText()
        self.AddText('(%d) %s' % (idx, label))

    def GetComment(self):
        return self.GetLabel()

    def SetComment(self, comment):
        self.SetLabel(comment)
        self.GetCanvas().Refresh()

class ProcessModelFile:
    """Process GRASS model file (gxm)"""
    def __init__(self, tree):
        """A ElementTree handler for the GXM XML file, as defined in
        grass-gxm.dtd.
        """
        self.tree = tree
        self.root = self.tree.getroot()
        # check if input is a valid GXM file
        if self.root is None or self.root.tag != 'gxm':
            if self.root is not None:
                tagName = self.root.tag
            else:
                tabName = _("empty")
            raise GException(_("Details: unsupported tag name '{}'.").format(tagName))
        
        # list of actions, data
        self.properties = dict()
        self.variables  = dict() 
        self.actions = list()
        self.data    = list()
        self.loops   = list()
        self.conditions = list()
        self.comments = list()

        self._processWindow()
        self._processProperties()
        self._processVariables()
        self._processItems()
        self._processData()
        
    def _filterValue(self, value):
        """Filter value
        
        :param value:
        """
        value = value.replace('&lt;', '<')
        value = value.replace('&gt;', '>')
        
        return value
        
    def _getNodeText(self, node, tag, default = ''):
        """Get node text"""
        p = node.find(tag)
        if p is not None:
            if p.text:
                return utils.normalize_whitespace(p.text)
            else:
                return ''
        
        return default
    
    def _processWindow(self):
        """Process window properties"""
        node = self.root.find('window')
        if node is None:
            self.pos = self.size = None
            return
        
        self.pos, self.size = self._getDim(node)
        
    def _processProperties(self):
        """Process model properties"""
        node = self.root.find('properties')
        if node is None:
            return
        for key in ('name', 'description', 'author'):
            self._processProperty(node, key)
        
        for f in node.findall('flag'):
            name = f.get('name', '')
            if name == 'overwrite':
                self.properties['overwrite'] = True
        
    def _processProperty(self, pnode, name):
        """Process given property"""
        node = pnode.find(name)
        if node is not None:
            self.properties[name] = node.text
        else:
            self.properties[name] = ''

    def _processVariables(self):
        """Process model variables"""
        vnode = self.root.find('variables')
        if vnode is None:
            return
        for node in vnode.findall('variable'):
            name = node.get('name', '')
            if not name:
                continue # should not happen
            self.variables[name] = { 'type' : node.get('type', 'string') }
            for key in ('description', 'value'):
                self._processVariable(node, name, key)
        
    def _processVariable(self, pnode, name, key):
        """Process given variable"""
        node = pnode.find(key)
        if node is not None:
            if node.text:
                self.variables[name][key] = node.text

    def _processItems(self):
        """Process model items (actions, loops, conditions)"""
        self._processActions()
        self._processLoops()
        self._processConditions()
        self._processComments()

    def _processActions(self):
        """Process model file"""
        for action in self.root.findall('action'):
            pos, size = self._getDim(action)
            disabled  = False
            
            task = action.find('task')
            if task is not None:
                if task.find('disabled') is not None:
                    disabled = True
                task = self._processTask(task)
            else:
                task = None
            
            aId = int(action.get('id', -1))
            label = action.get('name')
            comment = action.find('comment')
            if comment is not None:
                commentString = comment.text
            else:
                commentString = ''
            
            self.actions.append({ 'pos'      : pos,
                                  'size'     : size,
                                  'task'     : task,
                                  'id'       : aId,
                                  'disabled' : disabled,
                                  'label'    : label,
                                  'comment'  : commentString})

    def _getDim(self, node):
        """Get position and size of shape"""
        pos = size = None
        posAttr = node.get('pos', None)
        if posAttr:
            posVal = map(int, posAttr.split(','))
            try:
                pos = (posVal[0], posVal[1])
            except:
                pos = None
        
        sizeAttr = node.get('size', None)
        if sizeAttr:
            sizeVal = map(int, sizeAttr.split(','))
            try:
                size = (sizeVal[0], sizeVal[1])
            except:
                size = None
        
        return pos, size        
    
    def _processData(self):
        """Process model file"""
        for data in self.root.findall('data'):
            pos, size = self._getDim(data)
            param = data.find('data-parameter')
            prompt = value = None
            if param is not None:
                prompt = param.get('prompt', None)
                value = self._filterValue(self._getNodeText(param, 'value'))
            
            if data.find('intermediate') is None:
                intermediate = False
            else:
                intermediate = True
            
            rels = list()
            for rel in data.findall('relation'):
                defrel = { 'id'  : int(rel.get('id', -1)),
                           'dir' : rel.get('dir', 'to'),
                           'name' : rel.get('name', '') }
                points = list()
                for point in rel.findall('point'):
                    x = self._filterValue(self._getNodeText(point, 'x'))
                    y = self._filterValue(self._getNodeText(point, 'y'))
                    points.append((float(x), float(y)))
                defrel['points'] = points
                rels.append(defrel)
            
            self.data.append({ 'pos' : pos,
                               'size': size,
                               'prompt' : prompt,
                               'value' : value,
                               'intermediate' : intermediate,
                               'rels' : rels })
        
    def _processTask(self, node):
        """Process task

        :return: grassTask instance
        :return: None on error
        """
        cmd = list()
        parameterized = list()
        
        name = node.get('name', None)
        if not name:
            return None
        
        cmd.append(name)
        
        # flags
        for f in node.findall('flag'):
            flag = f.get('name', '')
            if f.get('parameterized', '0') == '1':
                parameterized.append(('flag', flag))
                if f.get('value', '1') == '0':
                    continue
            if len(flag) > 1:
                cmd.append('--' + flag)
            else:
                cmd.append('-' + flag)
        # parameters
        for p in node.findall('parameter'):
            name = p.get('name', '')
            if p.find('parameterized') is not None:
                parameterized.append(('param', name))
            cmd.append('%s=%s' % (name,
                                  self._filterValue(self._getNodeText(p, 'value'))))
        
        task, err = GUI(show = None, checkError = True).ParseCommand(cmd = cmd)
        if err:
            GWarning(os.linesep.join(err))
        
        for opt, name in parameterized:
            if opt == 'flag':
                task.set_flag(name, True, element = 'parameterized')
            else:
                task.set_param(name, True, element = 'parameterized')
        
        return task

    def _processLoops(self):
        """Process model loops"""
        for node in self.root.findall('loop'):
            pos, size = self._getDim(node)
            text = self._filterValue(self._getNodeText(node, 'condition')).strip()
            aid = list()
            for anode in node.findall('item'):
                try:
                    aid.append(int(anode.text))
                except ValueError:
                    pass
            
            self.loops.append({ 'pos'     : pos,
                                'size'    : size,
                                'text'    : text,
                                'id'      : int(node.get('id', -1)),
                                'items'   : aid })
        
    def _processConditions(self):
        """Process model conditions"""
        for node in self.root.findall('if-else'):
            pos, size = self._getDim(node)
            text = self._filterValue(self._getNodeText(node, 'condition')).strip()
            aid = { 'if'   : list(),
                    'else' : list() }
            for b in aid.keys():
                bnode = node.find(b)
                if bnode is None:
                    continue
                for anode in bnode.findall('item'):
                    try:
                        aid[b].append(int(anode.text))
                    except ValueError:
                        pass
            
            self.conditions.append({ 'pos'     : pos,
                                     'size'    : size,
                                     'text'    : text,
                                     'id'      : int(node.get('id', -1)),
                                     'items'   : aid })

    def _processComments(self):
        """Process model comments"""
        for node in self.root.findall('comment'):
            pos, size = self._getDim(node)
            text = self._filterValue(node.text)

            self.comments.append({ 'pos'     : pos,
                                   'size'    : size,
                                   'text'    : text,
                                   'id'      : int(node.get('id', -1)),
                                   'text'    : text })
        
class WriteModelFile:
    """Generic class for writing model file"""
    def __init__(self, fd, model):
        self.fd         = fd
        self.model      = model
        self.properties = model.GetProperties()
        self.variables  = model.GetVariables()
        self.items      = model.GetItems()
        
        self.indent = 0
        
        self._header()
        
        self._window()
        self._properties()
        self._variables()
        self._items()
        
        dataList = list()
        for action in model.GetItems(objType = ModelAction):
            for rel in action.GetRelations():
                dataItem = rel.GetData()
                if dataItem not in dataList:
                    dataList.append(dataItem)
        self._data(dataList)
        
        self._footer()

    def _filterValue(self, value):
        """Escapes value to be stored in XML.

        :param value: string to be escaped as XML
        :return: a XML-valid string
        """
        value = saxutils.escape(value)
        return value
        
    def _header(self):
        """Write header"""
        self.fd.write('<?xml version="1.0" encoding="%s"?>\n' % GetDefaultEncoding(forceUTF8 = True))
        self.fd.write('<!DOCTYPE gxm SYSTEM "grass-gxm.dtd">\n')
        self.fd.write('%s<gxm>\n' % (' ' * self.indent))
        self.indent += 4
                
    def _footer(self):
        """Write footer"""
        self.indent -= 4
        self.fd.write('%s</gxm>\n' % (' ' * self.indent))

    def _window(self):
        """Write window properties"""
        canvas = self.model.GetCanvas()
        if canvas is None:
            return
        win  = canvas.parent
        pos  = win.GetPosition()
        size = win.GetSize()
        self.fd.write('%s<window pos="%d,%d" size="%d,%d" />\n' % \
                          (' ' * self.indent, pos[0], pos[1], size[0], size[1]))
        
    def _properties(self):
        """Write model properties"""
        self.fd.write('%s<properties>\n' % (' ' * self.indent))
        self.indent += 4
        if self.properties['name']:
            self.fd.write('%s<name>%s</name>\n' % (' ' * self.indent, EncodeString(self.properties['name'])))
        if self.properties['description']:
            self.fd.write('%s<description>%s</description>\n' % (' ' * self.indent,
                                                                 EncodeString(self.properties['description'])))
        if self.properties['author']:
            self.fd.write('%s<author>%s</author>\n' % (' ' * self.indent,
                                                       EncodeString(self.properties['author'])))
        
        if 'overwrite' in self.properties and \
                self.properties['overwrite']:
            self.fd.write('%s<flag name="overwrite" />\n' % (' ' * self.indent))
        self.indent -= 4
        self.fd.write('%s</properties>\n' % (' ' * self.indent))

    def _variables(self):
        """Write model variables"""
        if not self.variables:
            return
        self.fd.write('%s<variables>\n' % (' ' * self.indent))
        self.indent += 4
        for name, values in self.variables.iteritems():
            self.fd.write('%s<variable name="%s" type="%s">\n' % \
                              (' ' * self.indent, EncodeString(name), values['type']))
            self.indent += 4
            if 'value' in values:
                self.fd.write('%s<value>%s</value>\n' % \
                                  (' ' * self.indent, EncodeString(values['value'])))
            if 'description' in values:
                self.fd.write('%s<description>%s</description>\n' % \
                                  (' ' * self.indent, EncodeString(values['description'])))
            self.indent -= 4
            self.fd.write('%s</variable>\n' % (' ' * self.indent))
        self.indent -= 4
        self.fd.write('%s</variables>\n' % (' ' * self.indent))
        
    def _items(self):
        """Write actions/loops/conditions"""
        for item in self.items:
            if isinstance(item, ModelAction):
                self._action(item)
            elif isinstance(item, ModelLoop):
                self._loop(item)
            elif isinstance(item, ModelCondition):
                self._condition(item)
            elif isinstance(item, ModelComment):
                self._comment(item)
        
    def _action(self, action):
        """Write actions"""
        self.fd.write('%s<action id="%d" name="%s" pos="%d,%d" size="%d,%d">\n' % \
                          (' ' * self.indent, action.GetId(), EncodeString(action.GetLabel()), action.GetX(), action.GetY(),
                           action.GetWidth(), action.GetHeight()))
        self.indent += 4
        comment = action.GetComment()
        if comment:
            self.fd.write('%s<comment>%s</comment>\n' % (' ' * self.indent, EncodeString(comment)))
        self.fd.write('%s<task name="%s">\n' % (' ' * self.indent, action.GetLog(string = False)[0]))
        self.indent += 4
        if not action.IsEnabled():
            self.fd.write('%s<disabled />\n' % (' ' * self.indent))
        for key, val in action.GetParams().iteritems():
            if key == 'flags':
                for f in val:
                    if f.get('value', False) or f.get('parameterized', False):
                        if f.get('parameterized', False):
                            if f.get('value', False) == False:
                                self.fd.write('%s<flag name="%s" value="0" parameterized="1" />\n' %
                                              (' ' * self.indent, f.get('name', '')))
                            else:
                                self.fd.write('%s<flag name="%s" parameterized="1" />\n' %
                                              (' ' * self.indent, f.get('name', '')))
                        else:
                            self.fd.write('%s<flag name="%s" />\n' %
                                          (' ' * self.indent, f.get('name', '')))
            else: # parameter
                for p in val:
                    if not p.get('value', '') and not p.get('parameterized', False):
                        continue
                    self.fd.write('%s<parameter name="%s">\n' %
                                  (' ' * self.indent, p.get('name', '')))
                    self.indent += 4
                    if p.get('parameterized', False):
                        self.fd.write('%s<parameterized />\n' % (' ' * self.indent))
                    self.fd.write('%s<value>%s</value>\n' %
                                  (' ' * self.indent, self._filterValue(p.get('value', ''))))
                    self.indent -= 4
                    self.fd.write('%s</parameter>\n' % (' ' * self.indent))
        self.indent -= 4
        self.fd.write('%s</task>\n' % (' ' * self.indent))
        self.indent -= 4
        self.fd.write('%s</action>\n' % (' ' * self.indent))
                
    def _data(self, dataList):
        """Write data"""
        for data in dataList:
            self.fd.write('%s<data pos="%d,%d" size="%d,%d">\n' % \
                              (' ' * self.indent, data.GetX(), data.GetY(),
                               data.GetWidth(), data.GetHeight()))
            self.indent += 4
            self.fd.write('%s<data-parameter prompt="%s">\n' % \
                              (' ' * self.indent, data.GetPrompt()))
            self.indent += 4
            self.fd.write('%s<value>%s</value>\n' %
                          (' ' * self.indent, self._filterValue(data.GetValue())))
            self.indent -= 4
            self.fd.write('%s</data-parameter>\n' % (' ' * self.indent))
            
            if data.IsIntermediate():
                self.fd.write('%s<intermediate />\n' % (' ' * self.indent))

            # relations
            for ft in ('from', 'to'):
                for rel in data.GetRelations(ft):
                    if ft == 'from':
                        aid = rel.GetTo().GetId()
                    else:
                        aid  = rel.GetFrom().GetId()
                    self.fd.write('%s<relation dir="%s" id="%d" name="%s">\n' % \
                                      (' ' * self.indent, ft, aid, rel.GetLabel()))
                    self.indent += 4
                    for point in rel.GetLineControlPoints()[1:-1]:
                        self.fd.write('%s<point>\n' % (' ' * self.indent))
                        self.indent += 4
                        x, y = point.Get()
                        self.fd.write('%s<x>%d</x>\n' % (' ' * self.indent, int(x)))
                        self.fd.write('%s<y>%d</y>\n' % (' ' * self.indent, int(y)))
                        self.indent -= 4
                        self.fd.write('%s</point>\n' % (' ' * self.indent))
                    self.indent -= 4
                    self.fd.write('%s</relation>\n' % (' ' * self.indent))
                
            self.indent -= 4
            self.fd.write('%s</data>\n' % (' ' * self.indent))

    def _loop(self, loop):
        """Write loops"""
        self.fd.write('%s<loop id="%d" pos="%d,%d" size="%d,%d">\n' % \
                          (' ' * self.indent, loop.GetId(), loop.GetX(), loop.GetY(),
                           loop.GetWidth(), loop.GetHeight()))
        self.indent += 4
        cond = loop.GetLabel()
        if cond:
            self.fd.write('%s<condition>%s</condition>\n' %
                          (' ' * self.indent, self._filterValue(cond)))
        for item in loop.GetItems(self.model.GetItems(objType=ModelAction)):
            self.fd.write('%s<item>%d</item>\n' %
                          (' ' * self.indent, item.GetId()))
        self.indent -= 4
        self.fd.write('%s</loop>\n' % (' ' * self.indent))

    def _condition(self, condition):
        """Write conditions"""
        bbox = condition.GetBoundingBoxMin()
        self.fd.write('%s<if-else id="%d" pos="%d,%d" size="%d,%d">\n' % \
                          (' ' * self.indent, condition.GetId(), condition.GetX(), condition.GetY(),
                           bbox[0], bbox[1]))
        text = condition.GetLabel()
        self.indent += 4
        if text:
            self.fd.write('%s<condition>%s</condition>\n' %
                          (' ' * self.indent, self._filterValue(text)))
        items = condition.GetItems()
        for b in items.keys():
            if len(items[b]) < 1:
                continue
            self.fd.write('%s<%s>\n' % (' ' * self.indent, b))
            self.indent += 4
            for item in items[b]:
                self.fd.write('%s<item>%d</item>\n' %
                              (' ' * self.indent, item.GetId()))
            self.indent -= 4
            self.fd.write('%s</%s>\n' % (' ' * self.indent, b))
        
        self.indent -= 4
        self.fd.write('%s</if-else>\n' % (' ' * self.indent))

    def _comment(self, comment):
        """Write comment"""
        self.fd.write('%s<comment id="%d" pos="%d,%d" size="%d,%d">%s</comment>\n' % \
                          (' ' * self.indent, comment.GetId(), comment.GetX(), comment.GetY(),
                           comment.GetWidth(), comment.GetHeight(), EncodeString(comment.GetLabel())))
        
class WritePythonFile:
    def __init__(self, fd, model):
        """Class for exporting model to Python script

        :param fd: file descriptor
        """
        self.fd     = fd
        self.model  = model
        self.indent = 4

        self._writePython()

    def _getStandardizedOption(self, string):
        if string == 'raster':
            return 'G_OPT_R_MAP'
        elif string == 'vector':
            return 'G_OPT_V_MAP'
        elif string == 'mapset':
            return 'G_OPT_M_MAPSET'
        elif string == 'file':
            return 'G_OPT_F_INPUT'
        elif string == 'dir':
            return 'G_OPT_M_DIR'
        elif string == 'region':
            return 'G_OPT_M_REGION'
        
        return ''

    def _writePython(self):
        """Write model to file"""
        properties = self.model.GetProperties()
        
        # header
        self.fd.write(
r"""#!/usr/bin/env python
#
#%s
#
# MODULE:       %s
#
# AUTHOR(S):    %s
#               
# PURPOSE:      %s
#
# DATE:         %s
#
#%s
"""% ('#' * 77,
       EncodeString(properties['name']),
       EncodeString(properties['author']),
       EncodeString('\n# '.join(properties['description'].splitlines())),
       time.asctime(),
       '#' * 77))
        
        # UI
        self.fd.write(
r"""
#%%module
#%% description: %s
#%%end
"""% (EncodeString(' '.join(properties['description'].splitlines()))))

        variables = self.model.GetVariables()
        for key, data in variables.iteritems():
            otype = self._getStandardizedOption(data['type'])
            self.fd.write(
r"""
#%%option %s
#%% key: %s
#%% description: %s
#%% required: yes
"""% (otype, key, data['description']))
            if 'value' in data:
                self.fd.write("#%% answer: %s\n" % data['value'])
            self.fd.write("#% end\n")
        
        # import modules
        self.fd.write(
r"""
import sys
import os
import atexit

from grass.script import parser, run_command
""")
        
        # cleanup()
        rast, vect, rast3d, msg = self.model.GetIntermediateData()
        self.fd.write(
r"""
def cleanup():
""")
        if rast:
            self.fd.write(
r"""  run_command('g.remove', flags='f', type='raster',
                      name=%s)
"""% ','.join(map(lambda x: "'" + x + "'", rast)))
        if vect:
            self.fd.write(
r"""  run_command('g.remove', flags='f', type='vector',
                      name=%s)
"""% ','.join(map(lambda x: "'" + x + "'", vect)))
        if rast3d:
            self.fd.write(
r"""  run_command('g.remove', flags='f', type='raster_3d',
                      name=%s)
"""% ','.join(map(lambda x: "'" + x + "'", rast3d)))
        if not rast and not vect and not rast3d:
            self.fd.write('    pass\n')
        
        self.fd.write("\ndef main():\n")
        for item in self.model.GetItems():
            self._writePythonItem(item, variables = self.model.GetVariables())
        
        self.fd.write("\n    return 0\n")
        
        self.fd.write(
r"""
if __name__ == "__main__":
    options, flags = parser()
    atexit.register(cleanup)
    sys.exit(main())
""")
        
    def _writePythonItem(self, item, ignoreBlock = True, variables = {}):
        """Write model object to Python file"""
        if isinstance(item, ModelAction):
            if ignoreBlock and item.GetBlockId(): # ignore items in loops of conditions
                return
            self._writePythonAction(item, variables = variables)
        elif isinstance(item, ModelLoop) or isinstance(item, ModelCondition):
            # substitute condition
            cond = item.GetLabel()
            for variable in self.model.GetVariables():
                pattern = re.compile('%' + variable)
                if pattern.search(cond):
                    value = variables[variable].get('value', '')
                    if variables[variable].get('type', 'string') == 'string':
                        value = '"' + value + '"'
                    cond = pattern.sub(value, cond)
            if isinstance(item, ModelLoop):
                condVar, condText = map(lambda x: x.strip(), re.split('\s* in \s*', cond))
                cond = "%sfor %s in " % (' ' * self.indent, condVar)
                if condText[0] == '`' and condText[-1] == '`':
                    task = GUI(show = None).ParseCommand(cmd = utils.split(condText[1:-1]))
                    cond += "grass.read_command("
                    cond += self._getPythonActionCmd(task, len(cond), variables = [condVar]) + ".splitlines()"
                else:
                    cond += condText
                self.fd.write('%s:\n' % cond)
                self.indent += 4
                variablesLoop = variables.copy()
                variablesLoop[condVar] = None
                for action in item.GetItems(self.model.GetItems(objType=ModelAction)):
                    self._writePythonItem(action, ignoreBlock = False, variables = variablesLoop)
                self.indent -= 4
            if isinstance(item, ModelCondition):
                self.fd.write('%sif %s:\n' % (' ' * self.indent, cond))
                self.indent += 4
                condItems = item.GetItems()
                for action in condItems['if']:
                    self._writePythonItem(action, ignoreBlock = False)
                if condItems['else']:
                    self.indent -= 4
                    self.fd.write('%selse:\n' % (' ' * self.indent))
                    self.indent += 4
                    for action in condItems['else']:
                        self._writePythonItem(action, ignoreBlock = False)
                self.indent += 4
        self.fd.write('\n')
        if isinstance(item, ModelComment):
            self._writePythonComment(item)
        
    def _writePythonAction(self, item, variables = {}):
        """Write model action to Python file"""
        task = GUI(show = None).ParseCommand(cmd = item.GetLog(string = False))
        strcmd = "%srun_command(" % (' ' * self.indent)
        self.fd.write(strcmd + self._getPythonActionCmd(task, len(strcmd), variables) + '\n')
        
    def _getPythonActionCmd(self, task, cmdIndent, variables = {}):
        opts = task.get_options()
        
        ret = ''
        flags = ''
        params = list()
        
        for f in opts['flags']:
            if f.get('value', False):
                name = f.get('name', '')
                if len(name) > 1:
                    params.append('%s = True' % name)
                else:
                    flags += name
        
        for p in opts['params']:
            name = p.get('name', None)
            value = p.get('value', None)
            if name and value:
                ptype = p.get('type', 'string')
                foundVar = False
                
                for var in sorted(variables, key=len, reverse=True):
                    data = variables[var]
                    if '%' + var in value:
                        value = self._substituteVariable(value, var, data)
                        foundVar = True

                if foundVar or ptype != 'string':
                    params.append("%s = %s" % (name, value))
                else:
                    params.append('%s = "%s"' % (name, value))
        
        ret += '"%s"' % task.get_name()
        if flags:
            ret += ",\n%sflags = '%s'" % (' ' * cmdIndent, flags)
        if len(params) > 0:
            ret += ",\n"
            for opt in params[:-1]:
                ret += "%s%s,\n" % (' ' * cmdIndent, opt)
            ret += "%s%s)" % (' ' * cmdIndent, params[-1])
        else:
            ret += ")"
        
        return ret

    def _writePythonComment(self, item):
        """Write model comment to Python file"""
        for line in item.GetLabel().splitlines():
            self.fd.write('#' + line + '\n')

    def _substituteVariable(self, string, variable, data):
        """Substitute variable in the string

        :param string: string to be modified
        :param variable: variable to be substituted
        :param data: data related to the variable
        
        :return: modified string
        """
        result = ''
        ss = re.split("\w*(%"+variable+")w*", string)

        if not ss[0]:
            if data:
                return "options['%s']" % variable
            else:
                return variable
        
        for s in ss:
            if s == '"':
                continue
            
            if s == '%' + variable:
                if data:
                    result += "+options['%s']+" % variable
                else:
                    result += '+%s+' % variable
            else:
                result += '"' + s + '"'
        
        return result.strip('+')


class ModelParamDialog(wx.Dialog):
    def __init__(self, parent, model, params, id = wx.ID_ANY, title = _("Model parameters"),
                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
        """Model parameters dialog
        """
        self.parent = parent
        self._model  = model
        self.params = params
        self.tasks  = list() # list of tasks/pages
        
        wx.Dialog.__init__(self, parent = parent, id = id, title = title, style = style, **kwargs)
        
        self.notebook = GNotebook(parent = self, 
                                  style = globalvar.FNPageDStyle)
        
        panel = self._createPages()
        wx.CallAfter(self.notebook.SetSelection, 0)

        # intermediate data?
        self.interData = wx.CheckBox(parent = self, label = _("Delete intermediate data when finish"))
        self.interData.SetValue(True)
        rast, vect, rast3d, msg = self._model.GetIntermediateData()
        if not rast and not vect and not rast3d:
            self.interData.Hide()
        
        self.btnCancel = wx.Button(parent = self, id = wx.ID_CANCEL)
        self.btnRun    = wx.Button(parent = self, id = wx.ID_OK,
                                   label = _("&Run"))
        self.btnRun.SetDefault()
        
        self._layout()
        
        size = self.GetBestSize()
        self.SetMinSize(size)
        self.SetSize((size.width, size.height +
                      panel.constrained_size[1] -
                      panel.panelMinHeight))
                
    def _layout(self):
        btnSizer = wx.StdDialogButtonSizer()
        btnSizer.AddButton(self.btnCancel)
        btnSizer.AddButton(self.btnRun)
        btnSizer.Realize()
        
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(item = self.notebook, proportion = 1,
                      flag = wx.EXPAND)
        if self.interData.IsShown():
            mainSizer.Add(item = self.interData, proportion = 0,
                          flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)

            mainSizer.Add(item = wx.StaticLine(parent = self, id = wx.ID_ANY,
                                               style = wx.LI_HORIZONTAL),
                          proportion = 0,
                          flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 5) 
        
        mainSizer.Add(item = btnSizer, proportion = 0,
                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
        
        self.SetSizer(mainSizer)
        mainSizer.Fit(self)
        
    def _createPages(self):
        """Create for each parameterized module its own page"""
        nameOrdered = [''] * len(self.params.keys())
        for name, params in self.params.iteritems():
            nameOrdered[params['idx']] = name
        for name in nameOrdered:
            params = self.params[name]
            panel = self._createPage(name, params)
            if name == 'variables':
                name = _('Variables')
            self.notebook.AddPage(page = panel, text = name)
        
        return panel
    
    def _createPage(self, name, params):
        """Define notebook page"""
        if name in globalvar.grassCmd:
            task = gtask.grassTask(name)
        else:
            task = gtask.grassTask()
        task.flags  = params['flags']
        task.params = params['params']
        
        panel = CmdPanel(parent = self, id = wx.ID_ANY, task = task,
                         giface = GraphicalModelerGrassInterface(self._model))
        self.tasks.append(task)
        
        return panel

    def GetErrors(self):
        """Check for errors, get list of messages"""
        errList = list()
        for task in self.tasks:
            errList += task.get_cmd_error()
        
        return errList

    def DeleteIntermediateData(self):
        """Check if to detele intermediate data"""
        if self.interData.IsShown() and self.interData.IsChecked():
            return True
        
        return False
