#!/usr/bin/env python
# encoding: utf-8
"""
Title: SIMO data managent models

Model functions for accessing and managing SIMO data structures. Public
functions can be called from the model chains, other functions are auxiliary
functions.

*manager.py*

Copyright (c) 2007 Simosol Oy. Distributed under the GNU General Public License 2.0.
"""

import numpy
from collections import defaultdict

def _get_descendant_levels(sim, level, d_levs):
    h = sim.lexicon.hierarchy
    child_levels = h[level]['children']
    if child_levels is not None:
        for c_level in child_levels:
            d_levs[h[c_level]['ordinal']].append(c_level)
            _get_descendant_levels(sim, c_level, d_levs)

def _get_levels_to_remove(sim, start_level):
    d_levs = defaultdict(list)
    _get_descendant_levels(sim, start_level, d_levs)
    order = d_levs.keys()
    order.sort(reverse=True)
    to_remove = []
    for o in order:
        for lev in d_levs[o]:
            to_remove.append(lev)
    return to_remove

def remove_objects_from_current_level(sim, depthind, params, tind, toremove):
    """Remove objects from current evaluation level

    sim -- simulator instance
    depthind -- current model chain depth index as int
    params -- management model parameters
    tind -- target index
    """
    g_c = sim.data.get_children
    # get the level that all objects should be removed from
    level = sim.level
    to_remove = _get_levels_to_remove(sim, level)
    for i in range(tind.shape[0]):
        it, br, o = tind[i,0:3]
        for d_level in to_remove:
            descendants = g_c(it, br, o, level, d_level)
            if descendants is not None:
                sim.data.del_objects(it, br, d_level, descendants)
        sim.data.del_objects(it, br, level, [o])

def remove_objects_from_child_level(sim, depthind, params, tind, toremove):
    """
    Remove objects from child level and all its descendants

    sim -- simulator instance
    depthind -- current model chain depth index as int
    params -- management model parameters
    tind -- target index
    """
    g_c = sim.data.get_children
    # get the parent level (current evaluation level)
    parentlevel = sim.level
    # get the level that all objects should be removed from
    try:
        childlevel = params.parameters[0]['level']
    except:
        errorstr = "parameter 'child level' missing from management model "\
            "'remove_objects_from_child_level' parameters"
        sim.add_error(errorstr, None, None)
        return
    to_remove = _get_levels_to_remove(sim, childlevel)
    for i in range(tind.shape[0]):
        it, br, po = tind[i,0:3]
        children = g_c(it, br, po, parentlevel, childlevel)
        if children is not None:
            for child in children:
                for d_level in to_remove:
                    descendants = g_c(it, br, child, childlevel, d_level)
                    if descendants is not None:
                        sim.data.del_objects(it, br, d_level, descendants)
            sim.data.del_objects(it, br, childlevel, children)

def group_children_by_variable(sim, depthind, params, tind, toremove):
    """Group children by variable

    sim -- simulator instance
    depthind -- current model chain depth index as int
    params -- management model parameters
    tind -- target index
    """
    # get the parameter values
    parentlevel = sim.level
    try:
        targetlevel = params.parameters[0]['level']
        targetvar = params.parameters[1]['vardef'][1]
        childlevel = params.parameters[2]['level']
        groupvar = params.parameters[3]['vardef'][1]
    except KeyError, e:
        errorstr = "invalid key '%s' in parameters" % e[0]
        sim.add_error(errorstr, None, None)
        return
    try:
        create_hierarchy = bool(float(params.parameters[4]['value']))
    except:
        errorstr = "management model 'group_children_by_variable' 5th " \
                   "parameter 'construct_hierarchy' is not valid"
        sim.add_error(errorstr, None, None)
        return
    # TODO: check that the parent level does not exist already
    # TODO: check that child level exists
    # get the grouping attribute value for each childlevel object
    ctind, torem = sim.data.get_tind(parentlevel, depthind, childlevel)
    if len(torem) > 0:
        # TODO: handle missing objects
        pass
    groupvals, err = sim.data.get_value(ctind, groupvar)
    if err is not None:
        for e in err:
            sim.add_error(e, childlevel, ctind)
        # TODO: handle missing values
        pass
    # process one parent level object at a time
    unique = numpy.unique
    add2data = sim.data.add_objects
    add2links = sim.data.linkage.add_object
    setv = sim.data.set_value
    getc = sim.data.get_children
    hierarchy = sim.data.hierarchy
    for i in range(ctind.shape[0]):
        if i in torem:
            continue
        # form the groups under the current parent object by finding unique
        # grouping variable values
        start, end = ctind[i,3:]
        gvals = groupvals[start:end]
        glabels = unique(gvals)
        it, br, parent = ctind[i,0:3]
        # get the indices of current parent objects's children
        children = getc(it, br, parent, parentlevel, childlevel)
        # go through the grouping variable values and add as many new target
        # level objects as there are unique grouping values (glabels)
        for lab in glabels:
            # add new object to data and store the grouping variable value
            ntind = add2data(it, br, targetlevel, 1, parentlevel, parent)
            setv(targetlevel, ntind, [targetvar], [lab])
            pkey = (targetlevel, ntind[0,2])
            # go through the children with the current grouping variable value
            # and add the links between the new objects and the child objects
            for j in numpy.where(gvals == lab)[0]:
                okey = (childlevel, children[j])
                add2links(hierarchy, it, br, okey, pkey, False)


def main():
    pass

if __name__ == '__main__':
    main()



