#!/usr/bin/env python
# -*- python -*- coding: utf-8 -*-
#
# Copyright (C) 2016 CONTACT Software GmbH
# All rights reserved.
# http://www.contact.de/
#
# CDB:Browse
# pylint: disable-msg=E0213,E1103,E0102,E0203,W0212,W0621,W0201,W0142

__docformat__ = "restructuredtext en"
__revision__ = "$Id: tasks_schedule.py 184240 2018-09-21 07:04:29Z heg $"

import traceback
from cdb import cdbtime
from cdb import misc
from cdb import ue
from cdb import sig
from cdb import sqlapi
from collections import defaultdict
from cdb.typeconversion import to_python_rep

from cdb.objects import Object
from cdb.classbody import classbody
from cs.pcs.projects import calendar as Calendar
from cs.pcs.projects import Project
from cs.pcs.projects.tasks import Task


OFFSET_MIN = 0
OFFSET_MAX = 1000000
OFFSET_MAX_FORCED_BY_PROJECT = True


ES = u"early_start"
EF = u"early_finish"
LS = u"late_start"
LF = u"late_finish"
maxES = u"max_early_start"
maxEF = u"max_early_finish"
minLS = u"min_late_start"
minLF = u"min_late_finish"
SO = u"start_time_fcast"
EO = u"end_time_fcast"
SP = u"start_time_plan"
EP = u"end_time_plan"
FF = u"free_float"
DF = u"days_fcast"
DP = u"days"
ST = u"status_time_fcast"
DAYS_EARLY = u"days_early"
DAYS_LATE = u"days_late"
DAYS_OFFSET = u"days_offset"


ASAP = "0"  # as soon as possible
ALAP = "1"  # as late as possible
MSO = "2"   # must start on
MFO = "3"   # must finish on
SNET = "4"  # start no earlier than
SNLT = "5"  # start no later than
FNET = "6"  # finish no earlier than
FNLT = "7"  # finish no later than
VALID_CONSTRAINTS_FOR_TASK_GROUPS = [ASAP, SNET, FNLT]


CALCULATION_ATTRIBUTES = [u"start_time_fcast", u"end_time_fcast", u"days_fcast",
                         u"automatic", u"auto_update_time", u"constraint_type", u"constraint_date",
                         u"milestone", u"early_position", u"parent_task"]


def __log__(txt=None, **kwargs):
    # misc.cdblogv(misc.kLogMsg, 7, txt)
    pass


def cache_factory():
    return defaultdict(int)


def _norm_val(val):
    if not None:
        return val
    return u""


NETWORK_CALCULATED = defaultdict(cache_factory)
NETWORK_CACHE = defaultdict(cache_factory)
NETWORK_CONSTRAINTS = defaultdict(cache_factory)
NETWORK_DATE_CACHE = defaultdict(defaultdict)


class TaskNetwork(Object):

    project = None
    minimum_offset = OFFSET_MIN
    maximum_offset = OFFSET_MAX
    project_start_offset = OFFSET_MIN
    project_end_offset = OFFSET_MAX
    project_start_time_fcast_offset = OFFSET_MIN
    project_end_time_fcast_offset = OFFSET_MAX

    def __init__(self, project):
        self.project = project
        self.load(project)

    def load(self, project):
        self.clear()
        Calendar.loadTaskRelations(prj_id=project.cdb_project_id,
                                   check_access=False)
        minimum_start = project.getStartTimeFcast()
        if not minimum_start:
            minimum_start = cdbtime.Time(cdbtime.now()).datetime()
        maximum_end = minimum_start
        self.minimum_offset = self.getLateIndexByDate(minimum_start)
        self.maximum_offset = self.getEarlyIndexByDate(maximum_end)
        self.project_start_offset = self.minimum_offset
        self.project_end_offset = self.maximum_offset
        __log__("Offset (min/max): %s / %s  (%s / %s)" % (self.minimum_offset, self.maximum_offset,
                                                          self.getDateByIndex(self.minimum_offset),
                                                          self.getDateByIndex(self.maximum_offset)))

        for task in project.Tasks:
            # set the manually scheduled tasks
            start_date = task.getStartTimeFcast()
            end_date = task.getEndTimeFcast()
            days = max(0, task.getDaysFcast() - 1)
            if (task.percent_complet > 0 or task.automatic == 0) and start_date and end_date:
                start = self.getLateIndexByDate(start_date)
                end = start + days
                self.minimum_offset = min(self.minimum_offset, start)
                if not (task.is_group and task.auto_update_time and task.automatic):
                    self.maximum_offset = max(self.maximum_offset, end)
                    task.setValueFrame(start_offset=start, end_offset=end)
                    self.setCalculated(task, [SO, EO])
                __log__("Manual %s: Starts on %s (%s)" % (task.task_name, start, self.getDateByIndex(start)))

            start = None
            end = None
            # set constraints for automatically scheduled tasks
            if task.automatic:
                if task.constraint_type == MSO:
                    cd = task.getConstraintDate()
                    start = self.getLateIndexByDate(cd)
                    end = start + days
                    task.setValueFrame(early_start=start, early_finish=end,
                                       late_start=start,
                                       start_offset=start, end_offset=end,
                                       early_start_date=cd, late_start_date=cd,
                                       start_offset_date=cd)
                    self.setCalculated(task, [ES, EF, SO, EO])
                    self.setConstraintIndex(task, MSO, start, MFO, end)
                    __log__("Constraint %s: Must start on %s (%s)" % (task.task_name, start, self.getDateByIndex(start)))
                elif task.constraint_type == MFO:
                    cd = task.getConstraintDate()
                    end = self.getEarlyIndexByDate(cd)
                    start = end - days
                    task.setValueFrame(early_finish=end,
                                       late_start=start, late_finish=end,
                                       start_offset=start, end_offset=end,
                                       early_finish_date=cd, late_finish_date=cd,
                                       end_offset_date=cd)
                    self.setCalculated(task, [LS, LF, SO, EO])
                    self.setConstraintIndex(task, MSO, start, MFO, end)
                    __log__("Constraint %s: Must end on %s (%s)" % (task.task_name, end, self.getDateByIndex(end)))
                elif task.constraint_type == SNET:
                    start = self.getLateIndexByDate(task.getConstraintDate())
                    end = start + days
                    task.setValueFrame(early_start=start, early_finish=end)
                    self.setConstraintIndex(task, SNET, start, FNET, end)
                    __log__("Constraint %s: Start no earlier than %s (%s)" % (task.task_name, start, self.getDateByIndex(start)))
                elif task.constraint_type == SNLT:
                    start = self.getLateIndexByDate(task.getConstraintDate())
                    end = start + days
                    task.setValueFrame(late_start=start, late_finish=end)
                    self.setConstraintIndex(task, SNLT, start, FNLT, end)
                    __log__("Constraint %s: Start no later than %s (%s)" % (task.task_name, start, self.getDateByIndex(start)))
                elif task.constraint_type == FNET:
                    end = self.getEarlyIndexByDate(task.getConstraintDate())
                    start = end - days
                    task.setValueFrame(early_start=start, early_finish=end)
                    self.setConstraintIndex(task, SNET, start, FNET, end)
                    __log__("Constraint %s: Finish no earlier than %s (%s)" % (task.task_name, end, self.getDateByIndex(end)))
                elif task.constraint_type == FNLT:
                    end = self.getEarlyIndexByDate(task.getConstraintDate())
                    start = end - days
                    task.setValueFrame(late_start=start, late_finish=end)
                    self.setConstraintIndex(task, SNLT, start, FNLT, end)
                    __log__("Constraint %s: Finish no later than %s (%s)" % (task.task_name, end, self.getDateByIndex(end)))
                if start:
                    self.minimum_offset = min(self.minimum_offset, start)
                if end and not (task.is_group and task.auto_update_time and task.automatic):
                    self.maximum_offset = max(self.maximum_offset, end)

        # set manual tasks without date values to project start
        for task in project.Tasks:
            if not task.automatic and not (task.start_time_fcast and task.end_time_fcast):
                task.setValueFrame(early_start=self.minimum_offset,
                                   early_start_max=self.minimum_offset,
                                   start_offset=self.minimum_offset)
                self.setCalculated(task, [ES, maxES, SO])

        # calculate the earliest schedule
        for task in project.Tasks:
            offset = task.getEarlyFinishCalculated(self.minimum_offset)
            self.maximum_offset = max(self.maximum_offset, offset)
        if not project.auto_update_time:
            maximum_end = project.getEndTimeFcast()
            self.project_end_offset = self.getEarlyIndexByDate(maximum_end)
            if maximum_end:
                if OFFSET_MAX_FORCED_BY_PROJECT:
                    self.maximum_offset = self.project_end_offset
                else:
                    self.maximum_offset = max(self.maximum_offset, self.project_end_offset)
        __log__("Offset (min/max): %s / %s  (%s / %s)" % (self.minimum_offset, self.maximum_offset,
                                                          self.getDateByIndex(self.minimum_offset),
                                                          self.getDateByIndex(self.maximum_offset)))
        __log__("------------------------------------------------------------------")

        # calculate the latest schedule
        for task in project.Tasks:
            task.getLateStartCalculated(self.maximum_offset)
        __log__("------------------------------------------------------------------")

        # calculate free_float and the actual offset
        # for start_time_fcast, end_time_fcast, start_time_plan and end_time_plan
        for task in project.Tasks:
            end_offset = task.getEndTimeFcastOffsetCalculated()
            start_offset = task.getStartTimeFcastOffsetCalculated()
            end_offset_date = task.getDate(EO, end_offset)
            start_offset_date = task.getDate(SO, start_offset)
            start_plan, end_plan, start_plan_date, end_plan_date = task.getParentFrame()
            task.getFreeFloatCalculated()
            task.setValueFrame(start_offset=start_offset, end_offset=end_offset,
                               start_plan=start_plan, end_plan=end_plan,
                               start_offset_date=start_offset_date, end_offset_date=end_offset_date,
                               start_plan_date=start_plan_date, end_plan_date=end_plan_date)
            self.setCalculated(task, [SO, EO, SP, EP])
            self.setDays(task, plan_dates=(start_plan_date and end_plan_date))
        __log__("------------------------------------------------------------------")
        # calculate free_float and the actual offset
        # for start_time_fcast, end_time_fcast, start_time_plan and end_time_plan
        for task in project.Tasks:
            self.setStatusTimeFcast(task)
        __log__("------------------------------------------------------------------")

    def clear(self):
        NETWORK_CALCULATED.clear()
        NETWORK_CACHE.clear()
        NETWORK_CONSTRAINTS.clear()
        NETWORK_DATE_CACHE.clear()
        Calendar.clearChaches()

    def _calculateStatusTimeFcast(self, task):
        parent = Calendar.getParentTask(task)
        if parent:
            if NETWORK_CACHE[parent][SO] and NETWORK_CACHE[parent][EO]:
                if NETWORK_CACHE[parent][SO] > NETWORK_CACHE[task][SO] or NETWORK_CACHE[parent][EO] < NETWORK_CACHE[task][EO]:
                    return 3
        elif not self.project.auto_update_time:
            if self.project_start_offset and self.project_end_offset:
                if self.project_start_offset > NETWORK_CACHE[task][SO] or self.project_end_offset < NETWORK_CACHE[task][EO]:
                    return 3
        if NETWORK_CACHE[task][SO] and NETWORK_CACHE[task][SP] and NETWORK_CACHE[task][EO] and NETWORK_CACHE[task][EP]:
            if NETWORK_CACHE[task][SO] > NETWORK_CACHE[task][SP] or NETWORK_CACHE[task][EO] < NETWORK_CACHE[task][EP]:
                return 2
        return 1

    def setStatusTimeFcast(self, task):
        NETWORK_CACHE[task][ST] = self._calculateStatusTimeFcast(task)

    def setDays(self, task, plan_dates=True):
        NETWORK_CACHE[task][DF] = NETWORK_CACHE[task][EO] - NETWORK_CACHE[task][SO] + int(not task.milestone)
        if plan_dates:
            NETWORK_CACHE[task][DP] = NETWORK_CACHE[task][EP] - NETWORK_CACHE[task][SP] + int(not task.milestone)
        else:
            NETWORK_CACHE[task][DP] = None
        self.setCalculated(task, [DF, DP])

    def setConstraintIndex(self, task, start_constr=None, start=None, end_constr=None, end=None):
        __log__("Constraint %s: %s --> %s = %s (%s)" % (task.task_name, "setConstraintIndex", start_constr, start, self.getDateByIndex(start)))
        __log__("Constraint %s: %s --> %s = %s (%s)" % (task.task_name, "setConstraintIndex", end_constr, end, self.getDateByIndex(end)))
        if not task.is_group or start_constr == SNET:
            self._setStrongestConstraint(task=task, constraint_type=start_constr, constraint_date=start)
        if not task.is_group or end_constr == FNLT:
            self._setStrongestConstraint(task=task, constraint_type=end_constr, constraint_date=end)

    def _setStrongestConstraint(self, task, constraint_type, constraint_date):
        __log__("Constraint %s: %s --> %s = %s (%s)" % (task.task_name, "setStrongestConstraint",
                                                        constraint_type, constraint_date,
                                                        self.getDateByIndex(constraint_date)))
        if constraint_type in [MSO, MFO]:
            existing_date = task.getConstraintIndex(constraint_type)
            if not existing_date:
                NETWORK_CONSTRAINTS[task][constraint_type] = constraint_date
        elif constraint_type in [SNET, FNET]:
            existing_date = task.getConstraintIndex(constraint_type)
            if not existing_date or constraint_date < existing_date:
                NETWORK_CONSTRAINTS[task][constraint_type] = constraint_date
        elif constraint_type in [SNLT, FNLT]:
            existing_date = task.getConstraintIndex(constraint_type)
            if not existing_date or constraint_date < existing_date:
                NETWORK_CONSTRAINTS[task][constraint_type] = constraint_date

    def setCalculated(self, task, values=[]):
        for v in values:
            NETWORK_CALCULATED[task][v] = 1

    def getDateByIndex(self, myindex):
        return Calendar.getDateByIndex(calendar_profile_id=self.project.calendar_profile_id,
                                       myindex=myindex)

    def getEarlyIndexByDate(self, mydate):
        result = Calendar.getIndexByDate(self.project.calendar_profile_id, mydate)
        return result[0] if result else None

    def getLateIndexByDate(self, mydate):
        result = Calendar.getIndexByDate(self.project.calendar_profile_id, mydate)
        return result[1] if result else None

    def commitCPChanges(self):
        task_ids_res_changes = []
        task_ids = []
        for task in NETWORK_CACHE.keys():
            task_ids_res_changes += task.commitRESChanges()
            task_ids += task.commitCPChanges()
        self.updateDateValues()
        Calendar.adjustProjectWorkdays(self.project.cdb_project_id)
        Project._adjust_time_status(self.project.cdb_project_id)
        # reload the changed tasks
        Task.KeywordQuery(cdb_project_id=self.project.cdb_project_id, task_id=task_ids).Execute()
        return task_ids, task_ids_res_changes

    def updateDateValues(self):
        """
        persist schedule to database

        triggers multiple update statements which depend on the previous ones:

        1. update task dates
        2. project plan dates
        2a. project forecast dates (if its auto_update_time attribute is set)
        3. task float (german "Puffer")
        4. task relationship gaps
        5. task relationship violations
        """
        kwargs = {"cdb_project_id": self.project.cdb_project_id}
        cca = Project.MakeChangeControlAttributes()
        kwargs.update(cdb_mdate=sqlapi.SQLdbms_date(cca[u"cdb_mdate"]))
        kwargs.update(cdb_mpersno=cca[u"cdb_mpersno"])

        update_gap_by_view = """cdbpcs_taskrel_gaps_v SET gap = new_gap
                    WHERE pred_pid = '%(cdb_project_id)s'
                    OR succ_pid = '%(cdb_project_id)s'""" % kwargs
        update_gap_by_select = """cdbpcs_taskrel SET gap =
                    (SELECT CASE
                            WHEN cdbpcs_taskrel.rel_type = 'AA' THEN b.start_time_fcast_offset - a.start_time_fcast_offset
                            WHEN cdbpcs_taskrel.rel_type = 'AE' THEN b.end_time_fcast_offset - a.start_time_fcast_offset
                            WHEN cdbpcs_taskrel.rel_type = 'EA' THEN b.start_time_fcast_offset - a.end_time_fcast_offset
                            WHEN cdbpcs_taskrel.rel_type = 'EE' THEN b.end_time_fcast_offset - a.end_time_fcast_offset
                            ELSE 0 END +
                            CASE
                            WHEN a.milestone = 1 AND b.milestone = 1 AND a.early_position = 0 AND b.early_position = 1 THEN -1
                            WHEN a.milestone = 1 AND b.milestone = 0 AND a.early_position = 0 AND cdbpcs_taskrel.rel_type IN ('EA', 'AA') THEN  -1
                            WHEN a.milestone = 0 AND b.milestone = 1 AND b.early_position = 1 AND cdbpcs_taskrel.rel_type IN ('EA', 'EE') THEN  -1
                            WHEN a.milestone = 0 AND b.milestone = 0 AND cdbpcs_taskrel.rel_type IN ('EA') THEN  -1
                            ELSE 0 END +
                            CASE
                            WHEN a.status = 180 THEN a.days_fcast
                            ELSE 0 END
                    FROM cdbpcs_task a, cdbpcs_task b
                    WHERE cdbpcs_taskrel.cdb_project_id2 = a.cdb_project_id
                    AND cdbpcs_taskrel.task_id2 = a.task_id
                    AND cdbpcs_taskrel.cdb_project_id = b.cdb_project_id
                    AND cdbpcs_taskrel.task_id = b.task_id)
                WHERE cdb_project_id2 = '%(cdb_project_id)s'
                    OR cdb_project_id = '%(cdb_project_id)s'
                """ % kwargs

        update_gap_stmt = {
            sqlapi.DBMS_SQLITE: update_gap_by_select,
            sqlapi.DBMS_MSSQL: update_gap_by_view,
            sqlapi.DBMS_ORACLE: update_gap_by_view,
        }

        updates = [
            """cdbpcs_project
                SET start_time_plan = (SELECT CASE
                        WHEN MIN(cdbpcs_task.start_time_fcast) < MIN(cdbpcs_task.start_time_plan)
                        THEN MIN(cdbpcs_task.start_time_fcast)
                        ELSE MIN(cdbpcs_task.start_time_plan)
                        END
                        FROM cdbpcs_task
                        WHERE cdbpcs_task.cdb_project_id = '%(cdb_project_id)s'
                            AND cdbpcs_task.parent_task = ''
                    ), end_time_plan = (SELECT CASE
                        WHEN MAX(cdbpcs_task.end_time_fcast) > MAX(cdbpcs_task.end_time_plan)
                        THEN MAX(cdbpcs_task.end_time_fcast)
                        ELSE MAX(cdbpcs_task.end_time_plan)
                        END
                        FROM cdbpcs_task
                        WHERE cdbpcs_task.cdb_project_id = '%(cdb_project_id)s'
                            AND cdbpcs_task.parent_task = ''
                    ), cdb_mdate = %(cdb_mdate)s,
                    cdb_mpersno = '%(cdb_mpersno)s'
                WHERE cdb_project_id = '%(cdb_project_id)s'
                """ % kwargs,
            ("""cdbpcs_project
                SET start_time_fcast = start_time_plan,
                    end_time_fcast = end_time_plan,
                    days_fcast = days,
                    duration_fcast = duration,
                    cdb_mdate = %(cdb_mdate)s,
                    cdb_mpersno = '%(cdb_mpersno)s'
                WHERE cdb_project_id = '%(cdb_project_id)s'
                """ % kwargs) if self.project.auto_update_time else None,
            """cdbpcs_task
                SET total_float = late_finish_offset - end_time_fcast_offset,
                    cdb_mdate = %(cdb_mdate)s,
                    cdb_mpersno = '%(cdb_mpersno)s'
                WHERE cdb_project_id = '%(cdb_project_id)s'
                """ % kwargs,
            update_gap_stmt[sqlapi.SQLdbms()],
            """cdbpcs_task
                SET cdb_mdate = %(cdb_mdate)s,
                    cdb_mpersno = '%(cdb_mpersno)s'
                WHERE cdb_project_id = '%(cdb_project_id)s'
                AND task_id IN (SELECT task_id FROM cdbpcs_taskrel
                                WHERE cdb_project_id = '%(cdb_project_id)s'
                                AND (violation = 0 AND minimal_gap > gap
                                OR violation = 1 AND minimal_gap <= gap)
                                UNION
                                SELECT task_id2 FROM cdbpcs_taskrel
                                WHERE cdb_project_id2 = '%(cdb_project_id)s'
                                AND (violation = 0 AND minimal_gap > gap
                                OR violation = 1 AND minimal_gap <= gap))
                """ % kwargs,
            """cdbpcs_taskrel
                SET violation = CASE
                    WHEN minimal_gap <= gap
                        THEN 0
                        ELSE 1
                    END
                WHERE cdb_project_id = '%(cdb_project_id)s'
                    OR cdb_project_id2 = '%(cdb_project_id)s'
                """ % kwargs,
        ]
        for upd in updates:
            if upd:
                sqlapi.SQLupdate(upd)


@classbody
class Project(object):

    def recalculate_preparation(self, ctx=None):
        for attr in [attr for attr in CALCULATION_ATTRIBUTES if hasattr(self, attr) and hasattr(ctx.object, attr)]:
            if self[attr] != to_python_rep(self.__table_info__.column(attr).type(), ctx.object[attr]):
                ctx.keep(u"do_recalculation", 1)
                break

    def recalculate(self, ctx=None):
        try:
            if ctx and getattr(ctx.sys_args, "batch_mode", False):
                return
            if not Project.ByKeys(self.cdb_project_id):
                return
            if not ctx or (ctx.action in [u"create", u"copy", u"delete"] or
                           u"do_recalculation" in ctx.ue_args.get_attribute_names() and
                           int(ctx.ue_args[u"do_recalculation"])):
                if not self.msp_active:
                    self._recalculate()
        except Exception, e:
            misc.cdblogv(misc.kLogErr, 0, u"%s\n%s" % (e, unicode(traceback.format_exc())))

    def _recalculate(self):
        __log__("Start calculation...")
        tn = TaskNetwork(self)
        task_ids, task_ids_res_changes = tn.commitCPChanges()
        tn.clear()
        sig.emit(Project, "adjustAllocationsOnly")(self, task_ids_res_changes)
        sig.emit(Project, "do_consistency_checks")(self, task_ids)
        __log__("...end calculation!")


@classbody
class Task(object):

    def recalculate_preparation(self, ctx=None):
        for attr in [attr for attr in CALCULATION_ATTRIBUTES if hasattr(self, attr) and hasattr(ctx.object, attr)]:
            if self[attr] != to_python_rep(self.__table_info__.column(attr).type(), ctx.object[attr]):
                ctx.keep(u"do_recalculation", 1)
                break

    def recalculate(self, ctx=None):
        try:
            if ctx and getattr(ctx.sys_args, "batch_mode", False):
                return
            if not Task.ByKeys(self.cdb_project_id, self.task_id):
                if not ctx or ctx.action != u"delete":
                    return
            if not ctx or (ctx.action in [u"create", u"copy", u"delete"] or
                           u"do_recalculation" in ctx.ue_args.get_attribute_names() and
                           int(ctx.ue_args[u"do_recalculation"])):
                self.Project.recalculate()
        except Exception, e:
            misc.cdblogv(misc.kLogErr, 0, u"%s\n%s" % (e, unicode(traceback.format_exc())))

    def commitRESChanges(self):
        if NETWORK_CACHE[self][SO] != self.getStartTimeFcastOffset():
            return [self.task_id]
        if NETWORK_CACHE[self][EO] != self.getEndTimeFcastOffset():
            return [self.task_id]
        return []

    def commitCPChanges(self):
        changes = {}
        new_es = _norm_val(NETWORK_CACHE[self][ES])
        new_ef = _norm_val(NETWORK_CACHE[self][EF])
        new_ls = _norm_val(NETWORK_CACHE[self][LS])
        new_lf = _norm_val(NETWORK_CACHE[self][LF])
        new_so = _norm_val(NETWORK_CACHE[self][SO])
        new_eo = _norm_val(NETWORK_CACHE[self][EO])
        new_sp = _norm_val(NETWORK_CACHE[self][SP])
        new_ep = _norm_val(NETWORK_CACHE[self][EP])

        new_esd = self.getDate(ES, new_es)
        new_efd = self.getDate(EF, new_ef)
        new_lsd = self.getDate(LS, new_ls)
        new_lfd = self.getDate(LF, new_lf)
        new_sod = self.getDate(SO, new_so)
        new_eod = self.getDate(EO, new_eo)
        new_spd = self.getDate(SP, new_sp)
        new_epd = self.getDate(EP, new_ep)

        new_ff = _norm_val(NETWORK_CACHE[self][FF])
        new_df = _norm_val(NETWORK_CACHE[self][DF])
        new_dp = _norm_val(NETWORK_CACHE[self][DP])
        new_st = _norm_val(NETWORK_CACHE[self][ST])

        if new_es != self.getEarlyStartOffset():
            changes[u"early_start_offset"] = new_es
        if new_esd != self.early_start:
            changes[u"early_start"] = new_esd
        if new_ef != self.getEarlyFinishOffset():
            changes[u"early_finish_offset"] = new_ef
        if new_efd != self.early_finish:
            changes[u"early_finish"] = new_efd
        if new_ls != self.getLateStartOffset():
            changes[u"late_start_offset"] = new_ls
        if new_lsd != self.late_start:
            changes[u"late_start"] = new_lsd
        if new_lf != self.getLateFinishOffset():
            changes[u"late_finish_offset"] = new_lf
        if new_lfd != self.late_finish:
            changes[u"late_finish"] = new_lfd
        if new_so != self.getStartTimeFcastOffset():
            changes[u"start_time_fcast_offset"] = new_so
        if new_sod != self.start_time_fcast and self.automatic:
            changes[u"start_time_fcast"] = new_sod
        if new_eo != self.getEndTimeFcastOffset():
            changes[u"end_time_fcast_offset"] = new_eo
        if new_eod != self.end_time_fcast and self.automatic:
            changes[u"end_time_fcast"] = new_eod
        if new_sp != self.getStartTimePlanOffset():
            changes[u"start_time_plan_offset"] = new_sp
        if new_spd != self.start_time_plan:
            changes[u"start_time_plan"] = new_spd
        if new_ep != self.getEndTimePlanOffset():
            changes[u"end_time_plan_offset"] = new_ep
        if new_epd != self.end_time_plan:
            changes[u"end_time_plan"] = new_epd
        if new_ff != self.getFreeFloat():
            changes[u"free_float"] = new_ff
        if new_dp != self.getDays():
            changes[u"days"] = new_dp
            changes[u"duration"] = new_dp * 8 if new_dp else 0
        if (self.auto_update_time and self.automatic) or not self.is_group:
            if (new_df != self.getDaysFcast() and
                    self.start_time_fcast and self.end_time_fcast):
                changes[u"days_fcast"] = new_df
                changes[u"duration_fcast"] = new_df * 8 if new_df else 0
        if new_st != self.status_time_fcast:
            changes[u"status_time_fcast"] = new_st
        if changes:
            cca = self.MakeChangeControlAttributes()
            changes.update(cdb_mdate=cca[u"cdb_mdate"])
            changes.update(cdb_mpersno=cca[u"cdb_mpersno"])
            self.Update(**changes)
            return [self.task_id]
        return []

    def getConstraintIndex(self, constraint):
        return NETWORK_CONSTRAINTS[self][constraint]

    def getConstraintDate(self):
        return self.constraint_date

    def getConstraintType(self):
        return self.constraint_type

    def isASAP(self):
        return self.constraint_type == ASAP

    def getDaysFcast(self):
        return self.days_fcast if self.days_fcast else 0

    def getEarlyStartOffset(self):
        return self.early_start_offset

    def getEarlyFinishOffset(self):
        return self.early_finish_offset

    def getLateStartOffset(self):
        return self.late_start_offset

    def getLateFinishOffset(self):
        return self.late_finish_offset

    def getStartTimeFcastOffset(self):
        return self.start_time_fcast_offset

    def getEndTimeFcastOffset(self):
        return self.end_time_fcast_offset

    def getStartTimePlanOffset(self):
        return self.start_time_plan_offset

    def getEndTimePlanOffset(self):
        return self.end_time_plan_offset

    def getFreeFloat(self):
        return self.free_float

    def getEarlyStartCalculated(self, minimum_offset=OFFSET_MIN, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][ES]:
            self.calculateEarlyStart(minimum_offset)
            NETWORK_CALCULATED[self][ES] = 1
            NETWORK_CALCULATED[self][maxES] = 1
        return NETWORK_CACHE[self][ES]

    def getEarlyFinishCalculated(self, minimum_offset=OFFSET_MIN, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][EF]:
            self.calculateEarlyFinish(minimum_offset)
            NETWORK_CALCULATED[self][EF] = 1
            NETWORK_CALCULATED[self][maxEF] = 1
        return NETWORK_CACHE[self][EF]

    def getLateStartCalculated(self, maximum_offset=OFFSET_MAX, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][LS]:
            self.calculateLateStart(maximum_offset)
            NETWORK_CALCULATED[self][LS] = 1
            NETWORK_CALCULATED[self][minLS] = 1
        return NETWORK_CACHE[self][LS]

    def getLateFinishCalculated(self, maximum_offset=OFFSET_MAX, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][LF]:
            self.calculateLateFinish(maximum_offset)
            NETWORK_CALCULATED[self][LF] = 1
            NETWORK_CALCULATED[self][minLF] = 1
        return NETWORK_CACHE[self][LF]

    def getEarlyStartCalculatedMax(self, minimum_offset=OFFSET_MIN, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][maxES]:
            self.calculateEarlyStart(minimum_offset)
            NETWORK_CALCULATED[self][ES] = 1
            NETWORK_CALCULATED[self][maxES] = 1
        if NETWORK_CALCULATED[self][SO]:
            return NETWORK_CACHE[self][SO]
        return NETWORK_CACHE[self][maxES]

    def getEarlyFinishCalculatedMax(self, minimum_offset=OFFSET_MIN, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][maxEF]:
            self.calculateEarlyFinish(minimum_offset)
            NETWORK_CALCULATED[self][EF] = 1
            NETWORK_CALCULATED[self][maxEF] = 1
        if NETWORK_CALCULATED[self][EO]:
            return NETWORK_CACHE[self][EO]
        return NETWORK_CACHE[self][maxEF]

    def getLateStartCalculatedMin(self, maximum_offset=OFFSET_MAX, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][minLS]:
            self.calculateLateStart(maximum_offset)
            NETWORK_CALCULATED[self][LS] = 1
            NETWORK_CALCULATED[self][minLS] = 1
        if NETWORK_CALCULATED[self][SO]:
            return NETWORK_CACHE[self][SO]
        return NETWORK_CACHE[self][minLS]

    def getLateFinishCalculatedMin(self, maximum_offset=OFFSET_MAX, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][minLF]:
            self.calculateLateFinish(maximum_offset)
            NETWORK_CALCULATED[self][LF] = 1
            NETWORK_CALCULATED[self][minLF] = 1
        if NETWORK_CALCULATED[self][EO]:
            return NETWORK_CACHE[self][EO]
        return NETWORK_CACHE[self][minLF]

    def getStartTimeFcastOffsetCalculated(self):
        if not NETWORK_CALCULATED[self][SO]:
            self.calculateStartTimeFcastOffset()
            NETWORK_CALCULATED[self][SO] = 1
        return NETWORK_CACHE[self][SO]

    def getEndTimeFcastOffsetCalculated(self):
        if not NETWORK_CALCULATED[self][EO]:
            self.calculateEndTimeFcastOffset()
            NETWORK_CALCULATED[self][EO] = 1
        return NETWORK_CACHE[self][EO]

    def getStartTimeFcastOffsetCalculatedValue(self):
        val = self.getStartTimeFcastOffsetCalculated()
        if not self.automatic and not (self.start_time_fcast and self.end_time_fcast):
            return u""
        return val if val else u""

    def getEndTimeFcastOffsetCalculatedValue(self):
        val = self.getEndTimeFcastOffsetCalculated()
        if not self.automatic and not (self.start_time_fcast and self.end_time_fcast):
            return u""
        return val if val else u""

    def getDaysCalculated(self, mode=None):
        if not mode:
            return self.getDaysFcast()
        if not NETWORK_CALCULATED[self][mode]:
            self.calculateDays(mode)
            NETWORK_CALCULATED[self][mode] = 1
        return NETWORK_CACHE[self][mode]

    def getFreeFloatCalculated(self, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][FF]:
            self.calculateFreeFloat()
            NETWORK_CALCULATED[self][FF] = 1
        return NETWORK_CACHE[self][FF]

    def setValueFrame(self, early_start=None, early_finish=None,
                      late_start=None, late_finish=None,
                      early_start_max=None, early_finish_max=None,
                      late_start_min=None, late_finish_min=None,
                      start_offset=None, end_offset=None,
                      start_plan=None, end_plan=None,
                      early_start_date=None, early_finish_date=None,
                      late_start_date=None, late_finish_date=None,
                      start_offset_date=None, end_offset_date=None,
                      start_plan_date=None, end_plan_date=None):
        self.setEarlyStart(early_start)
        self.setEarlyFinish(early_finish)
        self.setLateStart(late_start)
        self.setLateFinish(late_finish)
        self.setEarlyStartMax(early_start_max)
        self.setEarlyFinishMax(early_finish_max)
        self.setLateStartMin(late_start_min)
        self.setLateFinishMin(late_finish_min)
        self.setStartTimeFcastOffset(start_offset)
        self.setEndTimeFcastOffset(end_offset)
        self.setStartTimePlanOffset(start_plan, minimize=False)
        self.setEndTimePlanOffset(end_plan, minimize=False)
        self.setDate(ES, early_start_date)
        self.setDate(EF, early_finish_date)
        self.setDate(LS, late_start_date)
        self.setDate(LF, late_finish_date)
        self.setDate(SO, start_offset_date)
        self.setDate(EO, end_offset_date)
        self.setDate(SP, start_plan_date)
        self.setDate(EP, end_plan_date)

    def getParentFrame(self):
        tasks = []
        if self.is_group:
            tasks = Calendar.getActiveSubtasks(self, allsubtasks=True)
        if tasks:
            values = []
            for t in tasks:
                start = t.getStartTimeFcastOffsetCalculatedValue()
                end = t.getEndTimeFcastOffsetCalculatedValue()
                start_date = t.getDate(SO, start)
                end_date = t.getDate(EO, end)
                values.append((start, end, start_date, end_date))
            sd_values = filter(lambda x: x[0], values)
            ed_values = filter(lambda x: x[1], values)
            sd_dates = filter(lambda x: x[2], values)
            ed_dates = filter(lambda x: x[3], values)
            start = min(map(lambda x: x[0], sd_values)) if sd_values else None
            end = max(map(lambda x: x[1], ed_values)) if ed_values else None
            start_date = min(map(lambda x: x[2], sd_dates)) if sd_dates else None
            end_date = max(map(lambda x: x[3], ed_dates)) if ed_dates else None
            return start, end, start_date, end_date
        start = self.getStartTimeFcastOffsetCalculatedValue()
        end = self.getEndTimeFcastOffsetCalculatedValue()
        return start, end, self.getDate(SO, start), self.getDate(EO, end)

    def setFreeFloat(self, val):
        NETWORK_CACHE[self][FF] = val
        __log__("%s: %s --> %s" % (self.task_name, "setFreeFloat", val))

    def setEarlyStart(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][ES]:
                NETWORK_CACHE[self][ES] = val
            val = max(val, NETWORK_CACHE[self][ES]) if minimize else min(val, NETWORK_CACHE[self][ES])
            NETWORK_CACHE[self][ES] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setEarlyStart", val, self.getDateByIndex(val)))

    def setEarlyFinish(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][EF]:
                NETWORK_CACHE[self][EF] = val
            val = max(val, NETWORK_CACHE[self][EF]) if minimize else min(val, NETWORK_CACHE[self][EF])
            NETWORK_CACHE[self][EF] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setEarlyFinish", val, self.getDateByIndex(val)))

    def setLateStart(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][LS]:
                NETWORK_CACHE[self][LS] = val
            val = min(val, NETWORK_CACHE[self][LS]) if minimize else max(val, NETWORK_CACHE[self][LS])
            NETWORK_CACHE[self][LS] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setLateStart", val, self.getDateByIndex(val)))

    def setLateFinish(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][LF]:
                NETWORK_CACHE[self][LF] = val
            val = min(val, NETWORK_CACHE[self][LF]) if minimize else max(val, NETWORK_CACHE[self][LF])
            NETWORK_CACHE[self][LF] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setLateFinish", val, self.getDateByIndex(val)))

    def setEarlyStartMax(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][maxES]:
                NETWORK_CACHE[self][maxES] = val
            val = max(val, NETWORK_CACHE[self][maxES]) if minimize else min(val, NETWORK_CACHE[self][maxES])
            NETWORK_CACHE[self][maxES] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setEarlyStart2", val, self.getDateByIndex(val)))

    def setEarlyFinishMax(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][maxEF]:
                NETWORK_CACHE[self][maxEF] = val
            val = max(val, NETWORK_CACHE[self][maxEF]) if minimize else min(val, NETWORK_CACHE[self][maxEF])
            NETWORK_CACHE[self][maxEF] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setEarlyFinish2", val, self.getDateByIndex(val)))

    def setLateStartMin(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][minLS]:
                NETWORK_CACHE[self][minLS] = val
            val = min(val, NETWORK_CACHE[self][minLS]) if minimize else max(val, NETWORK_CACHE[self][minLS])
            NETWORK_CACHE[self][minLS] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setLateStart2", val, self.getDateByIndex(val)))

    def setLateFinishMin(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][minLF]:
                NETWORK_CACHE[self][minLF] = val
            val = min(val, NETWORK_CACHE[self][minLF]) if minimize else max(val, NETWORK_CACHE[self][minLF])
            NETWORK_CACHE[self][minLF] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setLateFinish2", val, self.getDateByIndex(val)))

    def setStartTimeFcastOffset(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][SO]:
                NETWORK_CACHE[self][SO] = val
            val = max(val, NETWORK_CACHE[self][SO]) if minimize else min(val, NETWORK_CACHE[self][SO])
            NETWORK_CACHE[self][SO] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setStartTimeFcastOffset", val, self.getDateByIndex(val)))

    def setEndTimeFcastOffset(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][EO]:
                NETWORK_CACHE[self][EO] = val
            val = max(val, NETWORK_CACHE[self][EO]) if minimize else min(val, NETWORK_CACHE[self][EO])
            NETWORK_CACHE[self][EO] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setEndTimeFcastOffset", val, self.getDateByIndex(val)))

    def setStartTimePlanOffset(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][SP]:
                NETWORK_CACHE[self][SP] = val
            val = max(val, NETWORK_CACHE[self][SP]) if minimize else min(val, NETWORK_CACHE[self][SP])
            NETWORK_CACHE[self][SP] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setStartTimePlanOffset", val, self.getDateByIndex(val)))

    def setEndTimePlanOffset(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][EP]:
                NETWORK_CACHE[self][EP] = val
            val = max(val, NETWORK_CACHE[self][EP]) if minimize else min(val, NETWORK_CACHE[self][EP])
            NETWORK_CACHE[self][EP] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setEndTimePlanOffset", val, self.getDateByIndex(val)))

    def getDate(self, key, index):
        value = NETWORK_DATE_CACHE[self].get(key, None)
        if value:
            return value
        return self.getDateByIndex(index)

    def setDate(self, key, val):
        if val:
            NETWORK_DATE_CACHE[self][key] = val

    def calculateDays(self, mode):
        val = int(not self.milestone)
        a = 0
        b = 0
        if mode == DAYS_EARLY:
            a = NETWORK_CACHE[self][EF]
            b = NETWORK_CACHE[self][ES]
        elif mode == DAYS_LATE:
            a = NETWORK_CACHE[self][LF]
            b = NETWORK_CACHE[self][LS]
        elif mode == DAYS_OFFSET:
            a = NETWORK_CACHE[self][EO]
            b = NETWORK_CACHE[self][SO]
        val += a - b
        NETWORK_CACHE[self][mode] = val
        __log__("%s: calculateDays(%s) %s - %s = %s" % (self.task_name, mode, a, b, val))

    def calculateFreeFloat(self):
        free_float = max(0, NETWORK_CACHE[self][LF] - NETWORK_CACHE[self][EO])
        values = []
        for task, connection_type in self.getDirektSuccessors(Calendar.START):
            min_gap = Calendar.getMinimalGap((self, Calendar.START), (task, connection_type))
            if self.task_id == task.parent_task:
                pass
            elif connection_type == Calendar.START:
                values.append(NETWORK_CACHE[task][ES] - min_gap - NETWORK_CACHE[self][SO])
            elif task != self:
                values.append(NETWORK_CACHE[task][EF] - min_gap - NETWORK_CACHE[self][SO])
        for task, connection_type in self.getDirektSuccessors(Calendar.END):
            min_gap = Calendar.getMinimalGap((self, Calendar.END), (task, connection_type))
            if task.task_id == self.parent_task:
                pass
            elif connection_type == Calendar.START:
                values.append(NETWORK_CACHE[task][ES] - min_gap - NETWORK_CACHE[self][EO])
            else:
                values.append(NETWORK_CACHE[task][EF] - min_gap - NETWORK_CACHE[self][EO])
        __log__("%s: calculateFreeFloat-Values = min(%s), no-successor-default = (%s)" % (self.task_name, values, free_float))
        if values:
            free_float = min(values)
        self.setFreeFloat(free_float)

    def calculateStartTimeFcastOffset(self):
        predecessors = self.getDirektPredecessors(Calendar.START)
        ct = self.getConstraintType()
        values = set()
        forced_by_subtasks = ct == ASAP and self.getConstraintIndex(SNET)
        for task, connection_type in predecessors:
            if connection_type == Calendar.START:
                min_gap = Calendar.getMinimalGap((task, connection_type),
                                                 (self, Calendar.START))
                values.add(task.getStartTimeFcastOffsetCalculated() + min_gap)
            elif connection_type == Calendar.END:
                eo = task.getEndTimeFcastOffsetCalculated()
                if task.is_group and task.auto_update_time and task.automatic:
                    Calendar.refreshTaskRelation(task, self, mode=DAYS_OFFSET)
                min_gap = Calendar.getMinimalGap((task, connection_type),
                                                 (self, Calendar.START))
                values.add(eo + min_gap)
        val = max(NETWORK_CACHE[self][ES], NETWORK_CACHE[self][maxES])
        if values:
            __log__("%s: calculateStartTimeFcastOffset-Values = (%s)" % (self.task_name, values))
            val = max(values)
            if ct == ALAP:
                val = max(val, min(NETWORK_CACHE[self][LS],
                                   NETWORK_CACHE[self][minLS]))
            elif ct in [SNET, FNET] or forced_by_subtasks:
                constraint = self.getConstraintIndex(SNET)
                if Calendar.hasNoPredecessors(self):
                    val = constraint
                else:
                    val = max(val, constraint)
            elif ct in [SNLT, FNLT]:
                constraint = self.getConstraintIndex(SNLT)
                val = min(val, constraint)
            __log__("%s: calculateStartTimeFcastOffset-Chosen = %s (%s)" % (self.task_name, val, self.getDateByIndex(val)))
        else:
            if ct == ALAP:
                val = max(val, min(NETWORK_CACHE[self][LS],
                                   NETWORK_CACHE[self][minLS]))
            elif ct in [SNET, FNET] or forced_by_subtasks:
                constraint = self.getConstraintIndex(SNET)
                val = constraint
            elif ct in [SNLT, FNLT]:
                constraint = self.getConstraintIndex(SNLT)
                val = min(val, constraint)
            __log__("%s: calculateStartTimeFcastOffset-No preds = %s (%s)" % (self.task_name, val, self.getDateByIndex(val)))
        self.setStartTimeFcastOffset(val)

    def calculateEndTimeFcastOffset(self):
        predecessors = self.getDirektPredecessors(Calendar.END)
        values = set()
        for task, connection_type in predecessors:
            min_gap = Calendar.getMinimalGap((task, connection_type),
                                             (self, Calendar.END))
            if task == self:
                if not (self.is_group and self.auto_update_time
                        and self.automatic):
                    values.clear()
                    values.add(task.getStartTimeFcastOffsetCalculated() + min_gap)
                    break
            elif connection_type == Calendar.START:
                values.add(task.getStartTimeFcastOffsetCalculated() + min_gap)
            elif connection_type == Calendar.END:
                values.add(task.getEndTimeFcastOffsetCalculated() + min_gap)
        if not values:
            min_gap = Calendar.getMinimalGap((self, Calendar.START),
                                             (self, Calendar.END))
            values.add(self.getStartTimeFcastOffsetCalculated() + min_gap)
        val = max(values)
        __log__("%s: calculateEndTimeFcastOffset-Values = (%s)" % (self.task_name, values))
        __log__("%s: calculateEndTimeFcastOffset-Chosen = %s (%s)" % (self.task_name, val, self.getDateByIndex(val)))
        self.setEndTimeFcastOffset(val)

        # adjust start of task group in case there are some sub tasks set to a late start
        if self.is_group and self.auto_update_time and self.automatic:
            val = min(map(lambda x: x.getStartTimeFcastOffsetCalculated(), Calendar.getSubtasks(self)))
            NETWORK_CACHE[self][SO] = val
            NETWORK_CALCULATED[self][SO] = 1
            self.setStartTimeFcastOffset(val, minimize=False)
        self.calculateDays(DAYS_OFFSET)

    def calculateEarlyStart(self, minimum_offset):
        predecessors = self.getDirektPredecessors(Calendar.START)
        values = set()
        values2 = set()
        for task, connection_type in predecessors:
            if connection_type == Calendar.START:
                min_gap = Calendar.getMinimalGap((task, connection_type),
                                                 (self, Calendar.START))
                values.add(task.getEarlyStartCalculated(minimum_offset) + min_gap)
                values2.add(task.getEarlyStartCalculatedMax(minimum_offset) + min_gap)
            elif connection_type == Calendar.END:
                ef = task.getEarlyFinishCalculated(minimum_offset)
                ef_max = task.getEarlyFinishCalculatedMax(minimum_offset)
                if task.is_group and task.auto_update_time and task.automatic:
                    Calendar.refreshTaskRelation(task, self, mode=DAYS_EARLY)
                min_gap = Calendar.getMinimalGap((task, connection_type),
                                                 (self, Calendar.START))
                values.add(ef + min_gap)
                values2.add(ef_max + min_gap)
        val = minimum_offset
        val2 = minimum_offset
        if values:
            __log__("%s: calculateEarlyStart-Values = (%s)" % (self.task_name, values))
            __log__("%s: calculateEarlyStart-Values2 = (%s)" % (self.task_name, values2))
            val = max(values)
            val2 = max(values2)
            __log__("%s: calculateEarlyStart-Chosen = %s (%s)" % (self.task_name, val, self.getDateByIndex(val)))
            __log__("%s: calculateEarlyStart-Chosen2 = %s (%s)" % (self.task_name, val2, self.getDateByIndex(val2)))
        else:
            __log__("%s: calculateEarlyStart-No preds = %s (%s)" % (self.task_name, val, self.getDateByIndex(val)))
            __log__("%s: calculateEarlyStart-No preds2 = %s (%s)" % (self.task_name, val2, self.getDateByIndex(val2)))
        if NETWORK_CACHE[self][SO]:
            values = values2 = [NETWORK_CACHE[self][SO]]
        self.setEarlyStart(val)
        self.setEarlyStartMax(val2)

    def calculateEarlyFinish(self, minimum_offset):
        predecessors = self.getDirektPredecessors(Calendar.END)
        values = set()
        values2 = set()
        for task, connection_type in predecessors:
            min_gap = Calendar.getMinimalGap((task, connection_type),
                                             (self, Calendar.END))
            if task == self:
                if not (self.is_group and self.auto_update_time
                        and self.automatic):
                    values.clear()
                    values2.clear()
                    values.add(task.getEarlyStartCalculated(minimum_offset) + min_gap)
                    values2.add(task.getEarlyStartCalculatedMax(minimum_offset) + min_gap)
                    break
            elif connection_type == Calendar.START:
                values.add(task.getEarlyStartCalculated(minimum_offset) + min_gap)
                values2.add(task.getEarlyStartCalculatedMax(minimum_offset) + min_gap)
            elif connection_type == Calendar.END:
                values.add(task.getEarlyFinishCalculated(minimum_offset) + min_gap)
                values2.add(task.getEarlyFinishCalculatedMax(minimum_offset) + min_gap)
        if not values:
            min_gap = Calendar.getMinimalGap((self, Calendar.START),
                                             (self, Calendar.END))
            values.add(self.getEarlyStartCalculated(minimum_offset) + min_gap)
            values2.add(self.getEarlyStartCalculatedMax(minimum_offset) + min_gap)
        __log__("%s: calculateEarlyFinish-Values = (%s)" % (self.task_name, values))
        __log__("%s: calculateEarlyFinish-Values2 = (%s)" % (self.task_name, values2))
        self.setEarlyFinish(max(values))
        self.setEarlyFinishMax(max(values2))
        self.calculateDays(DAYS_EARLY)

    def calculateLateStart(self, maximum_offset):
        successors = self.getDirektSuccessors(Calendar.START)
        values = set()
        values2 = set()
        for task, connection_type in successors:
            min_gap = Calendar.getMinimalGap((self, Calendar.START),
                                             (task, connection_type))
            if task == self:
                if not self.is_group:
                    values.clear()
                    values2.clear()
                    values.add(task.getLateFinishCalculated(maximum_offset) - min_gap)
                    values2.add(task.getLateFinishCalculatedMin(maximum_offset) - min_gap)
                    break
            elif connection_type == Calendar.START:
                values.add(task.getLateStartCalculated(maximum_offset) - min_gap)
                values2.add(task.getLateStartCalculatedMin(maximum_offset) - min_gap)
            elif connection_type == Calendar.END:
                values.add(task.getLateFinishCalculated(maximum_offset) - min_gap)
                values2.add(task.getLateFinishCalculatedMin(maximum_offset) - min_gap)
        __log__("%s: calculateLateStart-Values = (%s)" % (self.task_name, values))
        __log__("%s: calculateLateStart-Values2 = (%s)" % (self.task_name, values2))
        self.setLateStart(min(values))
        self.setLateStartMin(min(values2))
        self.calculateDays(DAYS_LATE)

    def calculateLateFinish(self, maximum_offset):
        successors = self.getDirektSuccessors(Calendar.END)
        if not successors:
            __log__("%s: calculateLateFinish-No succs = %s (%s)" % (self.task_name, maximum_offset, self.getDateByIndex(maximum_offset)))
            self.setLateFinish(maximum_offset)
            self.setLateFinishMin(maximum_offset)
        else:
            values = set()
            values2 = set()
            for task, connection_type in successors:
                if connection_type == Calendar.START:
                    ls = task.getLateStartCalculated(maximum_offset)
                    ls_max = task.getLateStartCalculatedMin(maximum_offset)
                    if task.is_group and task.auto_update_time and task.automatic:
                        Calendar.refreshTaskRelation(self, task, mode=DAYS_LATE)
                    min_gap = Calendar.getMinimalGap((self, Calendar.END),
                                                 (task, connection_type))
                    values.add(ls - min_gap)
                    values2.add(ls_max - min_gap)
                elif connection_type == Calendar.END:
                    min_gap = Calendar.getMinimalGap((self, Calendar.END),
                                                 (task, connection_type))
                    values.add(task.getLateFinishCalculated(maximum_offset) - min_gap)
                    values2.add(task.getLateFinishCalculatedMin(maximum_offset) - min_gap)
            __log__("%s: calculateLateFinish-Values = (%s)" % (self.task_name, values))
            __log__("%s: calculateLateFinish-Values2 = (%s)" % (self.task_name, values2))
            self.setLateFinish(min(values))
            self.setLateFinishMin(min(values2))

    def checkConstraints(self, ctx=None):
        if self.is_group and not self.constraint_type in VALID_CONSTRAINTS_FOR_TASK_GROUPS:
            raise ue.Exception("cdbpcs_task_group_constraint", self.task_name)

6458dd13c521ae1354c407b4d6fe2c16e1abf5b5
#!/usr/bin/env python
# -*- python -*- coding: utf-8 -*-
#
# Copyright (C) 2016 CONTACT Software GmbH
# All rights reserved.
# http://www.contact.de/
#
# CDB:Browse
# pylint: disable-msg=E0213,E1103,E0102,E0203,W0212,W0621,W0201,W0142

__docformat__ = "restructuredtext en"
__revision__ = "$Id: tasks_schedule.py 184240 2018-09-21 07:04:29Z heg $"

import traceback
from cdb import cdbtime
from cdb import misc
from cdb import ue
from cdb import sig
from cdb import sqlapi
from collections import defaultdict
from cdb.typeconversion import to_python_rep

from cdb.objects import Object
from cdb.classbody import classbody
from cs.pcs.projects import calendar as Calendar
from cs.pcs.projects import Project
from cs.pcs.projects.tasks import Task


OFFSET_MIN = 0
OFFSET_MAX = 1000000
OFFSET_MAX_FORCED_BY_PROJECT = True


ES = u"early_start"
EF = u"early_finish"
LS = u"late_start"
LF = u"late_finish"
maxES = u"max_early_start"
maxEF = u"max_early_finish"
minLS = u"min_late_start"
minLF = u"min_late_finish"
SO = u"start_time_fcast"
EO = u"end_time_fcast"
SP = u"start_time_plan"
EP = u"end_time_plan"
FF = u"free_float"
DF = u"days_fcast"
DP = u"days"
ST = u"status_time_fcast"
DAYS_EARLY = u"days_early"
DAYS_LATE = u"days_late"
DAYS_OFFSET = u"days_offset"


ASAP = "0"  # as soon as possible
ALAP = "1"  # as late as possible
MSO = "2"   # must start on
MFO = "3"   # must finish on
SNET = "4"  # start no earlier than
SNLT = "5"  # start no later than
FNET = "6"  # finish no earlier than
FNLT = "7"  # finish no later than
VALID_CONSTRAINTS_FOR_TASK_GROUPS = [ASAP, SNET, FNLT]


CALCULATION_ATTRIBUTES = [u"start_time_fcast", u"end_time_fcast", u"days_fcast",
                         u"automatic", u"auto_update_time", u"constraint_type", u"constraint_date",
                         u"milestone", u"early_position", u"parent_task"]


def __log__(txt=None, **kwargs):
    # misc.cdblogv(misc.kLogMsg, 7, txt)
    pass


def cache_factory():
    return defaultdict(int)


def _norm_val(val):
    if not None:
        return val
    return u""


NETWORK_CALCULATED = defaultdict(cache_factory)
NETWORK_CACHE = defaultdict(cache_factory)
NETWORK_CONSTRAINTS = defaultdict(cache_factory)
NETWORK_DATE_CACHE = defaultdict(defaultdict)


class TaskNetwork(Object):

    project = None
    minimum_offset = OFFSET_MIN
    maximum_offset = OFFSET_MAX
    project_start_offset = OFFSET_MIN
    project_end_offset = OFFSET_MAX
    project_start_time_fcast_offset = OFFSET_MIN
    project_end_time_fcast_offset = OFFSET_MAX

    def __init__(self, project):
        self.project = project
        self.load(project)

    def load(self, project):
        self.clear()
        Calendar.loadTaskRelations(prj_id=project.cdb_project_id,
                                   check_access=False)
        minimum_start = project.getStartTimeFcast()
        if not minimum_start:
            minimum_start = cdbtime.Time(cdbtime.now()).datetime()
        maximum_end = minimum_start
        self.minimum_offset = self.getLateIndexByDate(minimum_start)
        self.maximum_offset = self.getEarlyIndexByDate(maximum_end)
        self.project_start_offset = self.minimum_offset
        self.project_end_offset = self.maximum_offset
        __log__("Offset (min/max): %s / %s  (%s / %s)" % (self.minimum_offset, self.maximum_offset,
                                                          self.getDateByIndex(self.minimum_offset),
                                                          self.getDateByIndex(self.maximum_offset)))

        for task in project.Tasks:
            # set the manually scheduled tasks
            start_date = task.getStartTimeFcast()
            end_date = task.getEndTimeFcast()
            days = max(0, task.getDaysFcast() - 1)
            if (task.percent_complet > 0 or task.automatic == 0) and start_date and end_date:
                start = self.getLateIndexByDate(start_date)
                end = start + days
                self.minimum_offset = min(self.minimum_offset, start)
                if not (task.is_group and task.auto_update_time and task.automatic):
                    self.maximum_offset = max(self.maximum_offset, end)
                    task.setValueFrame(start_offset=start, end_offset=end)
                    self.setCalculated(task, [SO, EO])
                __log__("Manual %s: Starts on %s (%s)" % (task.task_name, start, self.getDateByIndex(start)))

            start = None
            end = None
            # set constraints for automatically scheduled tasks
            if task.automatic:
                if task.constraint_type == MSO:
                    cd = task.getConstraintDate()
                    start = self.getLateIndexByDate(cd)
                    end = start + days
                    task.setValueFrame(early_start=start, early_finish=end,
                                       late_start=start,
                                       start_offset=start, end_offset=end,
                                       early_start_date=cd, late_start_date=cd,
                                       start_offset_date=cd)
                    self.setCalculated(task, [ES, EF, SO, EO])
                    self.setConstraintIndex(task, MSO, start, MFO, end)
                    __log__("Constraint %s: Must start on %s (%s)" % (task.task_name, start, self.getDateByIndex(start)))
                elif task.constraint_type == MFO:
                    cd = task.getConstraintDate()
                    end = self.getEarlyIndexByDate(cd)
                    start = end - days
                    task.setValueFrame(early_finish=end,
                                       late_start=start, late_finish=end,
                                       start_offset=start, end_offset=end,
                                       early_finish_date=cd, late_finish_date=cd,
                                       end_offset_date=cd)
                    self.setCalculated(task, [LS, LF, SO, EO])
                    self.setConstraintIndex(task, MSO, start, MFO, end)
                    __log__("Constraint %s: Must end on %s (%s)" % (task.task_name, end, self.getDateByIndex(end)))
                elif task.constraint_type == SNET:
                    start = self.getLateIndexByDate(task.getConstraintDate())
                    end = start + days
                    task.setValueFrame(early_start=start, early_finish=end)
                    self.setConstraintIndex(task, SNET, start, FNET, end)
                    __log__("Constraint %s: Start no earlier than %s (%s)" % (task.task_name, start, self.getDateByIndex(start)))
                elif task.constraint_type == SNLT:
                    start = self.getLateIndexByDate(task.getConstraintDate())
                    end = start + days
                    task.setValueFrame(late_start=start, late_finish=end)
                    self.setConstraintIndex(task, SNLT, start, FNLT, end)
                    __log__("Constraint %s: Start no later than %s (%s)" % (task.task_name, start, self.getDateByIndex(start)))
                elif task.constraint_type == FNET:
                    end = self.getEarlyIndexByDate(task.getConstraintDate())
                    start = end - days
                    task.setValueFrame(early_start=start, early_finish=end)
                    self.setConstraintIndex(task, SNET, start, FNET, end)
                    __log__("Constraint %s: Finish no earlier than %s (%s)" % (task.task_name, end, self.getDateByIndex(end)))
                elif task.constraint_type == FNLT:
                    end = self.getEarlyIndexByDate(task.getConstraintDate())
                    start = end - days
                    task.setValueFrame(late_start=start, late_finish=end)
                    self.setConstraintIndex(task, SNLT, start, FNLT, end)
                    __log__("Constraint %s: Finish no later than %s (%s)" % (task.task_name, end, self.getDateByIndex(end)))
                if start:
                    self.minimum_offset = min(self.minimum_offset, start)
                if end and not (task.is_group and task.auto_update_time and task.automatic):
                    self.maximum_offset = max(self.maximum_offset, end)

        # set manual tasks without date values to project start
        for task in project.Tasks:
            if not task.automatic and not (task.start_time_fcast and task.end_time_fcast):
                task.setValueFrame(early_start=self.minimum_offset,
                                   early_start_max=self.minimum_offset,
                                   start_offset=self.minimum_offset)
                self.setCalculated(task, [ES, maxES, SO])

        # calculate the earliest schedule
        for task in project.Tasks:
            offset = task.getEarlyFinishCalculated(self.minimum_offset)
            self.maximum_offset = max(self.maximum_offset, offset)
        if not project.auto_update_time:
            maximum_end = project.getEndTimeFcast()
            self.project_end_offset = self.getEarlyIndexByDate(maximum_end)
            if maximum_end:
                if OFFSET_MAX_FORCED_BY_PROJECT:
                    self.maximum_offset = self.project_end_offset
                else:
                    self.maximum_offset = max(self.maximum_offset, self.project_end_offset)
        __log__("Offset (min/max): %s / %s  (%s / %s)" % (self.minimum_offset, self.maximum_offset,
                                                          self.getDateByIndex(self.minimum_offset),
                                                          self.getDateByIndex(self.maximum_offset)))
        __log__("------------------------------------------------------------------")

        # calculate the latest schedule
        for task in project.Tasks:
            task.getLateStartCalculated(self.maximum_offset)
        __log__("------------------------------------------------------------------")

        # calculate free_float and the actual offset
        # for start_time_fcast, end_time_fcast, start_time_plan and end_time_plan
        for task in project.Tasks:
            end_offset = task.getEndTimeFcastOffsetCalculated()
            start_offset = task.getStartTimeFcastOffsetCalculated()
            end_offset_date = task.getDate(EO, end_offset)
            start_offset_date = task.getDate(SO, start_offset)
            start_plan, end_plan, start_plan_date, end_plan_date = task.getParentFrame()
            task.getFreeFloatCalculated()
            task.setValueFrame(start_offset=start_offset, end_offset=end_offset,
                               start_plan=start_plan, end_plan=end_plan,
                               start_offset_date=start_offset_date, end_offset_date=end_offset_date,
                               start_plan_date=start_plan_date, end_plan_date=end_plan_date)
            self.setCalculated(task, [SO, EO, SP, EP])
            self.setDays(task, plan_dates=(start_plan_date and end_plan_date))
        __log__("------------------------------------------------------------------")
        # calculate free_float and the actual offset
        # for start_time_fcast, end_time_fcast, start_time_plan and end_time_plan
        for task in project.Tasks:
            self.setStatusTimeFcast(task)
        __log__("------------------------------------------------------------------")

    def clear(self):
        NETWORK_CALCULATED.clear()
        NETWORK_CACHE.clear()
        NETWORK_CONSTRAINTS.clear()
        NETWORK_DATE_CACHE.clear()
        Calendar.clearChaches()

    def _calculateStatusTimeFcast(self, task):
        parent = Calendar.getParentTask(task)
        if parent:
            if NETWORK_CACHE[parent][SO] and NETWORK_CACHE[parent][EO]:
                if NETWORK_CACHE[parent][SO] > NETWORK_CACHE[task][SO] or NETWORK_CACHE[parent][EO] < NETWORK_CACHE[task][EO]:
                    return 3
        elif not self.project.auto_update_time:
            if self.project_start_offset and self.project_end_offset:
                if self.project_start_offset > NETWORK_CACHE[task][SO] or self.project_end_offset < NETWORK_CACHE[task][EO]:
                    return 3
        if NETWORK_CACHE[task][SO] and NETWORK_CACHE[task][SP] and NETWORK_CACHE[task][EO] and NETWORK_CACHE[task][EP]:
            if NETWORK_CACHE[task][SO] > NETWORK_CACHE[task][SP] or NETWORK_CACHE[task][EO] < NETWORK_CACHE[task][EP]:
                return 2
        return 1

    def setStatusTimeFcast(self, task):
        NETWORK_CACHE[task][ST] = self._calculateStatusTimeFcast(task)

    def setDays(self, task, plan_dates=True):
        NETWORK_CACHE[task][DF] = NETWORK_CACHE[task][EO] - NETWORK_CACHE[task][SO] + int(not task.milestone)
        if plan_dates:
            NETWORK_CACHE[task][DP] = NETWORK_CACHE[task][EP] - NETWORK_CACHE[task][SP] + int(not task.milestone)
        else:
            NETWORK_CACHE[task][DP] = None
        self.setCalculated(task, [DF, DP])

    def setConstraintIndex(self, task, start_constr=None, start=None, end_constr=None, end=None):
        __log__("Constraint %s: %s --> %s = %s (%s)" % (task.task_name, "setConstraintIndex", start_constr, start, self.getDateByIndex(start)))
        __log__("Constraint %s: %s --> %s = %s (%s)" % (task.task_name, "setConstraintIndex", end_constr, end, self.getDateByIndex(end)))
        if not task.is_group or start_constr == SNET:
            self._setStrongestConstraint(task=task, constraint_type=start_constr, constraint_date=start)
        if not task.is_group or end_constr == FNLT:
            self._setStrongestConstraint(task=task, constraint_type=end_constr, constraint_date=end)

    def _setStrongestConstraint(self, task, constraint_type, constraint_date):
        __log__("Constraint %s: %s --> %s = %s (%s)" % (task.task_name, "setStrongestConstraint",
                                                        constraint_type, constraint_date,
                                                        self.getDateByIndex(constraint_date)))
        if constraint_type in [MSO, MFO]:
            existing_date = task.getConstraintIndex(constraint_type)
            if not existing_date:
                NETWORK_CONSTRAINTS[task][constraint_type] = constraint_date
        elif constraint_type in [SNET, FNET]:
            existing_date = task.getConstraintIndex(constraint_type)
            if not existing_date or constraint_date < existing_date:
                NETWORK_CONSTRAINTS[task][constraint_type] = constraint_date
        elif constraint_type in [SNLT, FNLT]:
            existing_date = task.getConstraintIndex(constraint_type)
            if not existing_date or constraint_date < existing_date:
                NETWORK_CONSTRAINTS[task][constraint_type] = constraint_date

    def setCalculated(self, task, values=[]):
        for v in values:
            NETWORK_CALCULATED[task][v] = 1

    def getDateByIndex(self, myindex):
        return Calendar.getDateByIndex(calendar_profile_id=self.project.calendar_profile_id,
                                       myindex=myindex)

    def getEarlyIndexByDate(self, mydate):
        result = Calendar.getIndexByDate(self.project.calendar_profile_id, mydate)
        return result[0] if result else None

    def getLateIndexByDate(self, mydate):
        result = Calendar.getIndexByDate(self.project.calendar_profile_id, mydate)
        return result[1] if result else None

    def commitCPChanges(self):
        task_ids_res_changes = []
        task_ids = []
        for task in NETWORK_CACHE.keys():
            task_ids_res_changes += task.commitRESChanges()
            task_ids += task.commitCPChanges()
        self.updateDateValues()
        Calendar.adjustProjectWorkdays(self.project.cdb_project_id)
        Project._adjust_time_status(self.project.cdb_project_id)
        # reload the changed tasks
        Task.KeywordQuery(cdb_project_id=self.project.cdb_project_id, task_id=task_ids).Execute()
        return task_ids, task_ids_res_changes

    def updateDateValues(self):
        """
        persist schedule to database

        triggers multiple update statements which depend on the previous ones:

        1. update task dates
        2. project plan dates
        2a. project forecast dates (if its auto_update_time attribute is set)
        3. task float (german "Puffer")
        4. task relationship gaps
        5. task relationship violations
        """
        kwargs = {"cdb_project_id": self.project.cdb_project_id}
        cca = Project.MakeChangeControlAttributes()
        kwargs.update(cdb_mdate=sqlapi.SQLdbms_date(cca[u"cdb_mdate"]))
        kwargs.update(cdb_mpersno=cca[u"cdb_mpersno"])

        update_gap_by_view = """cdbpcs_taskrel_gaps_v SET gap = new_gap
                    WHERE pred_pid = '%(cdb_project_id)s'
                    OR succ_pid = '%(cdb_project_id)s'""" % kwargs
        update_gap_by_select = """cdbpcs_taskrel SET gap =
                    (SELECT CASE
                            WHEN cdbpcs_taskrel.rel_type = 'AA' THEN b.start_time_fcast_offset - a.start_time_fcast_offset
                            WHEN cdbpcs_taskrel.rel_type = 'AE' THEN b.end_time_fcast_offset - a.start_time_fcast_offset
                            WHEN cdbpcs_taskrel.rel_type = 'EA' THEN b.start_time_fcast_offset - a.end_time_fcast_offset
                            WHEN cdbpcs_taskrel.rel_type = 'EE' THEN b.end_time_fcast_offset - a.end_time_fcast_offset
                            ELSE 0 END +
                            CASE
                            WHEN a.milestone = 1 AND b.milestone = 1 AND a.early_position = 0 AND b.early_position = 1 THEN -1
                            WHEN a.milestone = 1 AND b.milestone = 0 AND a.early_position = 0 AND cdbpcs_taskrel.rel_type IN ('EA', 'AA') THEN  -1
                            WHEN a.milestone = 0 AND b.milestone = 1 AND b.early_position = 1 AND cdbpcs_taskrel.rel_type IN ('EA', 'EE') THEN  -1
                            WHEN a.milestone = 0 AND b.milestone = 0 AND cdbpcs_taskrel.rel_type IN ('EA') THEN  -1
                            ELSE 0 END +
                            CASE
                            WHEN a.status = 180 THEN a.days_fcast
                            ELSE 0 END
                    FROM cdbpcs_task a, cdbpcs_task b
                    WHERE cdbpcs_taskrel.cdb_project_id2 = a.cdb_project_id
                    AND cdbpcs_taskrel.task_id2 = a.task_id
                    AND cdbpcs_taskrel.cdb_project_id = b.cdb_project_id
                    AND cdbpcs_taskrel.task_id = b.task_id)
                WHERE cdb_project_id2 = '%(cdb_project_id)s'
                    OR cdb_project_id = '%(cdb_project_id)s'
                """ % kwargs

        update_gap_stmt = {
            sqlapi.DBMS_SQLITE: update_gap_by_select,
            sqlapi.DBMS_MSSQL: update_gap_by_view,
            sqlapi.DBMS_ORACLE: update_gap_by_view,
        }

        updates = [
            """cdbpcs_project
                SET start_time_plan = (SELECT CASE
                        WHEN MIN(cdbpcs_task.start_time_fcast) < MIN(cdbpcs_task.start_time_plan)
                        THEN MIN(cdbpcs_task.start_time_fcast)
                        ELSE MIN(cdbpcs_task.start_time_plan)
                        END
                        FROM cdbpcs_task
                        WHERE cdbpcs_task.cdb_project_id = '%(cdb_project_id)s'
                            AND cdbpcs_task.parent_task = ''
                    ), end_time_plan = (SELECT CASE
                        WHEN MAX(cdbpcs_task.end_time_fcast) > MAX(cdbpcs_task.end_time_plan)
                        THEN MAX(cdbpcs_task.end_time_fcast)
                        ELSE MAX(cdbpcs_task.end_time_plan)
                        END
                        FROM cdbpcs_task
                        WHERE cdbpcs_task.cdb_project_id = '%(cdb_project_id)s'
                            AND cdbpcs_task.parent_task = ''
                    ), cdb_mdate = %(cdb_mdate)s,
                    cdb_mpersno = '%(cdb_mpersno)s'
                WHERE cdb_project_id = '%(cdb_project_id)s'
                """ % kwargs,
            ("""cdbpcs_project
                SET start_time_fcast = start_time_plan,
                    end_time_fcast = end_time_plan,
                    days_fcast = days,
                    duration_fcast = duration,
                    cdb_mdate = %(cdb_mdate)s,
                    cdb_mpersno = '%(cdb_mpersno)s'
                WHERE cdb_project_id = '%(cdb_project_id)s'
                """ % kwargs) if self.project.auto_update_time else None,
            """cdbpcs_task
                SET total_float = late_finish_offset - end_time_fcast_offset,
                    cdb_mdate = %(cdb_mdate)s,
                    cdb_mpersno = '%(cdb_mpersno)s'
                WHERE cdb_project_id = '%(cdb_project_id)s'
                """ % kwargs,
            update_gap_stmt[sqlapi.SQLdbms()],
            """cdbpcs_task
                SET cdb_mdate = %(cdb_mdate)s,
                    cdb_mpersno = '%(cdb_mpersno)s'
                WHERE cdb_project_id = '%(cdb_project_id)s'
                AND task_id IN (SELECT task_id FROM cdbpcs_taskrel
                                WHERE cdb_project_id = '%(cdb_project_id)s'
                                AND (violation = 0 AND minimal_gap > gap
                                OR violation = 1 AND minimal_gap <= gap)
                                UNION
                                SELECT task_id2 FROM cdbpcs_taskrel
                                WHERE cdb_project_id2 = '%(cdb_project_id)s'
                                AND (violation = 0 AND minimal_gap > gap
                                OR violation = 1 AND minimal_gap <= gap))
                """ % kwargs,
            """cdbpcs_taskrel
                SET violation = CASE
                    WHEN minimal_gap <= gap
                        THEN 0
                        ELSE 1
                    END
                WHERE cdb_project_id = '%(cdb_project_id)s'
                    OR cdb_project_id2 = '%(cdb_project_id)s'
                """ % kwargs,
        ]
        for upd in updates:
            if upd:
                sqlapi.SQLupdate(upd)


@classbody
class Project(object):

    def recalculate_preparation(self, ctx=None):
        for attr in [attr for attr in CALCULATION_ATTRIBUTES if hasattr(self, attr) and hasattr(ctx.object, attr)]:
            if self[attr] != to_python_rep(self.__table_info__.column(attr).type(), ctx.object[attr]):
                ctx.keep(u"do_recalculation", 1)
                break

    def recalculate(self, ctx=None):
        try:
            if ctx and getattr(ctx.sys_args, "batch_mode", False):
                return
            if not Project.ByKeys(self.cdb_project_id):
                return
            if not ctx or (ctx.action in [u"create", u"copy", u"delete"] or
                           u"do_recalculation" in ctx.ue_args.get_attribute_names() and
                           int(ctx.ue_args[u"do_recalculation"])):
                if not self.msp_active:
                    self._recalculate()
        except Exception, e:
            misc.cdblogv(misc.kLogErr, 0, u"%s\n%s" % (e, unicode(traceback.format_exc())))

    def _recalculate(self):
        __log__("Start calculation...")
        tn = TaskNetwork(self)
        task_ids, task_ids_res_changes = tn.commitCPChanges()
        tn.clear()
        sig.emit(Project, "adjustAllocationsOnly")(self, task_ids_res_changes)
        sig.emit(Project, "do_consistency_checks")(self, task_ids)
        __log__("...end calculation!")


@classbody
class Task(object):

    def recalculate_preparation(self, ctx=None):
        for attr in [attr for attr in CALCULATION_ATTRIBUTES if hasattr(self, attr) and hasattr(ctx.object, attr)]:
            if self[attr] != to_python_rep(self.__table_info__.column(attr).type(), ctx.object[attr]):
                ctx.keep(u"do_recalculation", 1)
                break

    def recalculate(self, ctx=None):
        try:
            if ctx and getattr(ctx.sys_args, "batch_mode", False):
                return
            if not Task.ByKeys(self.cdb_project_id, self.task_id):
                if not ctx or ctx.action != u"delete":
                    return
            if not ctx or (ctx.action in [u"create", u"copy", u"delete"] or
                           u"do_recalculation" in ctx.ue_args.get_attribute_names() and
                           int(ctx.ue_args[u"do_recalculation"])):
                self.Project.recalculate()
        except Exception, e:
            misc.cdblogv(misc.kLogErr, 0, u"%s\n%s" % (e, unicode(traceback.format_exc())))

    def commitRESChanges(self):
        if NETWORK_CACHE[self][SO] != self.getStartTimeFcastOffset():
            return [self.task_id]
        if NETWORK_CACHE[self][EO] != self.getEndTimeFcastOffset():
            return [self.task_id]
        return []

    def commitCPChanges(self):
        changes = {}
        new_es = _norm_val(NETWORK_CACHE[self][ES])
        new_ef = _norm_val(NETWORK_CACHE[self][EF])
        new_ls = _norm_val(NETWORK_CACHE[self][LS])
        new_lf = _norm_val(NETWORK_CACHE[self][LF])
        new_so = _norm_val(NETWORK_CACHE[self][SO])
        new_eo = _norm_val(NETWORK_CACHE[self][EO])
        new_sp = _norm_val(NETWORK_CACHE[self][SP])
        new_ep = _norm_val(NETWORK_CACHE[self][EP])

        new_esd = self.getDate(ES, new_es)
        new_efd = self.getDate(EF, new_ef)
        new_lsd = self.getDate(LS, new_ls)
        new_lfd = self.getDate(LF, new_lf)
        new_sod = self.getDate(SO, new_so)
        new_eod = self.getDate(EO, new_eo)
        new_spd = self.getDate(SP, new_sp)
        new_epd = self.getDate(EP, new_ep)

        new_ff = _norm_val(NETWORK_CACHE[self][FF])
        new_df = _norm_val(NETWORK_CACHE[self][DF])
        new_dp = _norm_val(NETWORK_CACHE[self][DP])
        new_st = _norm_val(NETWORK_CACHE[self][ST])

        if new_es != self.getEarlyStartOffset():
            changes[u"early_start_offset"] = new_es
        if new_esd != self.early_start:
            changes[u"early_start"] = new_esd
        if new_ef != self.getEarlyFinishOffset():
            changes[u"early_finish_offset"] = new_ef
        if new_efd != self.early_finish:
            changes[u"early_finish"] = new_efd
        if new_ls != self.getLateStartOffset():
            changes[u"late_start_offset"] = new_ls
        if new_lsd != self.late_start:
            changes[u"late_start"] = new_lsd
        if new_lf != self.getLateFinishOffset():
            changes[u"late_finish_offset"] = new_lf
        if new_lfd != self.late_finish:
            changes[u"late_finish"] = new_lfd
        if new_so != self.getStartTimeFcastOffset():
            changes[u"start_time_fcast_offset"] = new_so
        if new_sod != self.start_time_fcast and self.automatic:
            changes[u"start_time_fcast"] = new_sod
        if new_eo != self.getEndTimeFcastOffset():
            changes[u"end_time_fcast_offset"] = new_eo
        if new_eod != self.end_time_fcast and self.automatic:
            changes[u"end_time_fcast"] = new_eod
        if new_sp != self.getStartTimePlanOffset():
            changes[u"start_time_plan_offset"] = new_sp
        if new_spd != self.start_time_plan:
            changes[u"start_time_plan"] = new_spd
        if new_ep != self.getEndTimePlanOffset():
            changes[u"end_time_plan_offset"] = new_ep
        if new_epd != self.end_time_plan:
            changes[u"end_time_plan"] = new_epd
        if new_ff != self.getFreeFloat():
            changes[u"free_float"] = new_ff
        if new_dp != self.getDays():
            changes[u"days"] = new_dp
            changes[u"duration"] = new_dp * 8 if new_dp else 0
        if (self.auto_update_time and self.automatic) or not self.is_group:
            if (new_df != self.getDaysFcast() and
                    self.start_time_fcast and self.end_time_fcast):
                changes[u"days_fcast"] = new_df
                changes[u"duration_fcast"] = new_df * 8 if new_df else 0
        if new_st != self.status_time_fcast:
            changes[u"status_time_fcast"] = new_st
        if changes:
            cca = self.MakeChangeControlAttributes()
            changes.update(cdb_mdate=cca[u"cdb_mdate"])
            changes.update(cdb_mpersno=cca[u"cdb_mpersno"])
            self.Update(**changes)
            return [self.task_id]
        return []

    def getConstraintIndex(self, constraint):
        return NETWORK_CONSTRAINTS[self][constraint]

    def getConstraintDate(self):
        return self.constraint_date

    def getConstraintType(self):
        return self.constraint_type

    def isASAP(self):
        return self.constraint_type == ASAP

    def getDaysFcast(self):
        return self.days_fcast if self.days_fcast else 0

    def getEarlyStartOffset(self):
        return self.early_start_offset

    def getEarlyFinishOffset(self):
        return self.early_finish_offset

    def getLateStartOffset(self):
        return self.late_start_offset

    def getLateFinishOffset(self):
        return self.late_finish_offset

    def getStartTimeFcastOffset(self):
        return self.start_time_fcast_offset

    def getEndTimeFcastOffset(self):
        return self.end_time_fcast_offset

    def getStartTimePlanOffset(self):
        return self.start_time_plan_offset

    def getEndTimePlanOffset(self):
        return self.end_time_plan_offset

    def getFreeFloat(self):
        return self.free_float

    def getEarlyStartCalculated(self, minimum_offset=OFFSET_MIN, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][ES]:
            self.calculateEarlyStart(minimum_offset)
            NETWORK_CALCULATED[self][ES] = 1
            NETWORK_CALCULATED[self][maxES] = 1
        return NETWORK_CACHE[self][ES]

    def getEarlyFinishCalculated(self, minimum_offset=OFFSET_MIN, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][EF]:
            self.calculateEarlyFinish(minimum_offset)
            NETWORK_CALCULATED[self][EF] = 1
            NETWORK_CALCULATED[self][maxEF] = 1
        return NETWORK_CACHE[self][EF]

    def getLateStartCalculated(self, maximum_offset=OFFSET_MAX, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][LS]:
            self.calculateLateStart(maximum_offset)
            NETWORK_CALCULATED[self][LS] = 1
            NETWORK_CALCULATED[self][minLS] = 1
        return NETWORK_CACHE[self][LS]

    def getLateFinishCalculated(self, maximum_offset=OFFSET_MAX, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][LF]:
            self.calculateLateFinish(maximum_offset)
            NETWORK_CALCULATED[self][LF] = 1
            NETWORK_CALCULATED[self][minLF] = 1
        return NETWORK_CACHE[self][LF]

    def getEarlyStartCalculatedMax(self, minimum_offset=OFFSET_MIN, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][maxES]:
            self.calculateEarlyStart(minimum_offset)
            NETWORK_CALCULATED[self][ES] = 1
            NETWORK_CALCULATED[self][maxES] = 1
        if NETWORK_CALCULATED[self][SO]:
            return NETWORK_CACHE[self][SO]
        return NETWORK_CACHE[self][maxES]

    def getEarlyFinishCalculatedMax(self, minimum_offset=OFFSET_MIN, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][maxEF]:
            self.calculateEarlyFinish(minimum_offset)
            NETWORK_CALCULATED[self][EF] = 1
            NETWORK_CALCULATED[self][maxEF] = 1
        if NETWORK_CALCULATED[self][EO]:
            return NETWORK_CACHE[self][EO]
        return NETWORK_CACHE[self][maxEF]

    def getLateStartCalculatedMin(self, maximum_offset=OFFSET_MAX, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][minLS]:
            self.calculateLateStart(maximum_offset)
            NETWORK_CALCULATED[self][LS] = 1
            NETWORK_CALCULATED[self][minLS] = 1
        if NETWORK_CALCULATED[self][SO]:
            return NETWORK_CACHE[self][SO]
        return NETWORK_CACHE[self][minLS]

    def getLateFinishCalculatedMin(self, maximum_offset=OFFSET_MAX, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][minLF]:
            self.calculateLateFinish(maximum_offset)
            NETWORK_CALCULATED[self][LF] = 1
            NETWORK_CALCULATED[self][minLF] = 1
        if NETWORK_CALCULATED[self][EO]:
            return NETWORK_CACHE[self][EO]
        return NETWORK_CACHE[self][minLF]

    def getStartTimeFcastOffsetCalculated(self):
        if not NETWORK_CALCULATED[self][SO]:
            self.calculateStartTimeFcastOffset()
            NETWORK_CALCULATED[self][SO] = 1
        return NETWORK_CACHE[self][SO]

    def getEndTimeFcastOffsetCalculated(self):
        if not NETWORK_CALCULATED[self][EO]:
            self.calculateEndTimeFcastOffset()
            NETWORK_CALCULATED[self][EO] = 1
        return NETWORK_CACHE[self][EO]

    def getStartTimeFcastOffsetCalculatedValue(self):
        val = self.getStartTimeFcastOffsetCalculated()
        if not self.automatic and not (self.start_time_fcast and self.end_time_fcast):
            return u""
        return val if val else u""

    def getEndTimeFcastOffsetCalculatedValue(self):
        val = self.getEndTimeFcastOffsetCalculated()
        if not self.automatic and not (self.start_time_fcast and self.end_time_fcast):
            return u""
        return val if val else u""

    def getDaysCalculated(self, mode=None):
        if not mode:
            return self.getDaysFcast()
        if not NETWORK_CALCULATED[self][mode]:
            self.calculateDays(mode)
            NETWORK_CALCULATED[self][mode] = 1
        return NETWORK_CACHE[self][mode]

    def getFreeFloatCalculated(self, recalculate=False):
        if recalculate or not NETWORK_CALCULATED[self][FF]:
            self.calculateFreeFloat()
            NETWORK_CALCULATED[self][FF] = 1
        return NETWORK_CACHE[self][FF]

    def setValueFrame(self, early_start=None, early_finish=None,
                      late_start=None, late_finish=None,
                      early_start_max=None, early_finish_max=None,
                      late_start_min=None, late_finish_min=None,
                      start_offset=None, end_offset=None,
                      start_plan=None, end_plan=None,
                      early_start_date=None, early_finish_date=None,
                      late_start_date=None, late_finish_date=None,
                      start_offset_date=None, end_offset_date=None,
                      start_plan_date=None, end_plan_date=None):
        self.setEarlyStart(early_start)
        self.setEarlyFinish(early_finish)
        self.setLateStart(late_start)
        self.setLateFinish(late_finish)
        self.setEarlyStartMax(early_start_max)
        self.setEarlyFinishMax(early_finish_max)
        self.setLateStartMin(late_start_min)
        self.setLateFinishMin(late_finish_min)
        self.setStartTimeFcastOffset(start_offset)
        self.setEndTimeFcastOffset(end_offset)
        self.setStartTimePlanOffset(start_plan, minimize=False)
        self.setEndTimePlanOffset(end_plan, minimize=False)
        self.setDate(ES, early_start_date)
        self.setDate(EF, early_finish_date)
        self.setDate(LS, late_start_date)
        self.setDate(LF, late_finish_date)
        self.setDate(SO, start_offset_date)
        self.setDate(EO, end_offset_date)
        self.setDate(SP, start_plan_date)
        self.setDate(EP, end_plan_date)

    def getParentFrame(self):
        tasks = []
        if self.is_group:
            tasks = Calendar.getActiveSubtasks(self, allsubtasks=True)
        if tasks:
            values = []
            for t in tasks:
                start = t.getStartTimeFcastOffsetCalculatedValue()
                end = t.getEndTimeFcastOffsetCalculatedValue()
                start_date = t.getDate(SO, start)
                end_date = t.getDate(EO, end)
                values.append((start, end, start_date, end_date))
            sd_values = filter(lambda x: x[0], values)
            ed_values = filter(lambda x: x[1], values)
            sd_dates = filter(lambda x: x[2], values)
            ed_dates = filter(lambda x: x[3], values)
            start = min(map(lambda x: x[0], sd_values)) if sd_values else None
            end = max(map(lambda x: x[1], ed_values)) if ed_values else None
            start_date = min(map(lambda x: x[2], sd_dates)) if sd_dates else None
            end_date = max(map(lambda x: x[3], ed_dates)) if ed_dates else None
            return start, end, start_date, end_date
        start = self.getStartTimeFcastOffsetCalculatedValue()
        end = self.getEndTimeFcastOffsetCalculatedValue()
        return start, end, self.getDate(SO, start), self.getDate(EO, end)

    def setFreeFloat(self, val):
        NETWORK_CACHE[self][FF] = val
        __log__("%s: %s --> %s" % (self.task_name, "setFreeFloat", val))

    def setEarlyStart(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][ES]:
                NETWORK_CACHE[self][ES] = val
            val = max(val, NETWORK_CACHE[self][ES]) if minimize else min(val, NETWORK_CACHE[self][ES])
            NETWORK_CACHE[self][ES] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setEarlyStart", val, self.getDateByIndex(val)))

    def setEarlyFinish(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][EF]:
                NETWORK_CACHE[self][EF] = val
            val = max(val, NETWORK_CACHE[self][EF]) if minimize else min(val, NETWORK_CACHE[self][EF])
            NETWORK_CACHE[self][EF] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setEarlyFinish", val, self.getDateByIndex(val)))

    def setLateStart(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][LS]:
                NETWORK_CACHE[self][LS] = val
            val = min(val, NETWORK_CACHE[self][LS]) if minimize else max(val, NETWORK_CACHE[self][LS])
            NETWORK_CACHE[self][LS] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setLateStart", val, self.getDateByIndex(val)))

    def setLateFinish(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][LF]:
                NETWORK_CACHE[self][LF] = val
            val = min(val, NETWORK_CACHE[self][LF]) if minimize else max(val, NETWORK_CACHE[self][LF])
            NETWORK_CACHE[self][LF] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setLateFinish", val, self.getDateByIndex(val)))

    def setEarlyStartMax(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][maxES]:
                NETWORK_CACHE[self][maxES] = val
            val = max(val, NETWORK_CACHE[self][maxES]) if minimize else min(val, NETWORK_CACHE[self][maxES])
            NETWORK_CACHE[self][maxES] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setEarlyStart2", val, self.getDateByIndex(val)))

    def setEarlyFinishMax(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][maxEF]:
                NETWORK_CACHE[self][maxEF] = val
            val = max(val, NETWORK_CACHE[self][maxEF]) if minimize else min(val, NETWORK_CACHE[self][maxEF])
            NETWORK_CACHE[self][maxEF] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setEarlyFinish2", val, self.getDateByIndex(val)))

    def setLateStartMin(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][minLS]:
                NETWORK_CACHE[self][minLS] = val
            val = min(val, NETWORK_CACHE[self][minLS]) if minimize else max(val, NETWORK_CACHE[self][minLS])
            NETWORK_CACHE[self][minLS] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setLateStart2", val, self.getDateByIndex(val)))

    def setLateFinishMin(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][minLF]:
                NETWORK_CACHE[self][minLF] = val
            val = min(val, NETWORK_CACHE[self][minLF]) if minimize else max(val, NETWORK_CACHE[self][minLF])
            NETWORK_CACHE[self][minLF] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setLateFinish2", val, self.getDateByIndex(val)))

    def setStartTimeFcastOffset(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][SO]:
                NETWORK_CACHE[self][SO] = val
            val = max(val, NETWORK_CACHE[self][SO]) if minimize else min(val, NETWORK_CACHE[self][SO])
            NETWORK_CACHE[self][SO] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setStartTimeFcastOffset", val, self.getDateByIndex(val)))

    def setEndTimeFcastOffset(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][EO]:
                NETWORK_CACHE[self][EO] = val
            val = max(val, NETWORK_CACHE[self][EO]) if minimize else min(val, NETWORK_CACHE[self][EO])
            NETWORK_CACHE[self][EO] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setEndTimeFcastOffset", val, self.getDateByIndex(val)))

    def setStartTimePlanOffset(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][SP]:
                NETWORK_CACHE[self][SP] = val
            val = max(val, NETWORK_CACHE[self][SP]) if minimize else min(val, NETWORK_CACHE[self][SP])
            NETWORK_CACHE[self][SP] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setStartTimePlanOffset", val, self.getDateByIndex(val)))

    def setEndTimePlanOffset(self, val, minimize=True):
        if val:
            if not NETWORK_CACHE[self][EP]:
                NETWORK_CACHE[self][EP] = val
            val = max(val, NETWORK_CACHE[self][EP]) if minimize else min(val, NETWORK_CACHE[self][EP])
            NETWORK_CACHE[self][EP] = val
            __log__("%s: %s --> %s (%s)" % (self.task_name, "setEndTimePlanOffset", val, self.getDateByIndex(val)))

    def getDate(self, key, index):
        value = NETWORK_DATE_CACHE[self].get(key, None)
        if value:
            return value
        return self.getDateByIndex(index)

    def setDate(self, key, val):
        if val:
            NETWORK_DATE_CACHE[self][key] = val

    def calculateDays(self, mode):
        val = int(not self.milestone)
        a = 0
        b = 0
        if mode == DAYS_EARLY:
            a = NETWORK_CACHE[self][EF]
            b = NETWORK_CACHE[self][ES]
        elif mode == DAYS_LATE:
            a = NETWORK_CACHE[self][LF]
            b = NETWORK_CACHE[self][LS]
        elif mode == DAYS_OFFSET:
            a = NETWORK_CACHE[self][EO]
            b = NETWORK_CACHE[self][SO]
        val += a - b
        NETWORK_CACHE[self][mode] = val
        __log__("%s: calculateDays(%s) %s - %s = %s" % (self.task_name, mode, a, b, val))

    def calculateFreeFloat(self):
        free_float = max(0, NETWORK_CACHE[self][LF] - NETWORK_CACHE[self][EO])
        values = []
        for task, connection_type in self.getDirektSuccessors(Calendar.START):
            min_gap = Calendar.getMinimalGap((self, Calendar.START), (task, connection_type))
            if self.task_id == task.parent_task:
                pass
            elif connection_type == Calendar.START:
                values.append(NETWORK_CACHE[task][ES] - min_gap - NETWORK_CACHE[self][SO])
            elif task != self:
                values.append(NETWORK_CACHE[task][EF] - min_gap - NETWORK_CACHE[self][SO])
        for task, connection_type in self.getDirektSuccessors(Calendar.END):
            min_gap = Calendar.getMinimalGap((self, Calendar.END), (task, connection_type))
            if task.task_id == self.parent_task:
                pass
            elif connection_type == Calendar.START:
                values.append(NETWORK_CACHE[task][ES] - min_gap - NETWORK_CACHE[self][EO])
            else:
                values.append(NETWORK_CACHE[task][EF] - min_gap - NETWORK_CACHE[self][EO])
        __log__("%s: calculateFreeFloat-Values = min(%s), no-successor-default = (%s)" % (self.task_name, values, free_float))
        if values:
            free_float = min(values)
        self.setFreeFloat(free_float)

    def calculateStartTimeFcastOffset(self):
        predecessors = self.getDirektPredecessors(Calendar.START)
        ct = self.getConstraintType()
        values = set()
        forced_by_subtasks = ct == ASAP and self.getConstraintIndex(SNET)
        for task, connection_type in predecessors:
            if connection_type == Calendar.START:
                min_gap = Calendar.getMinimalGap((task, connection_type),
                                                 (self, Calendar.START))
                values.add(task.getStartTimeFcastOffsetCalculated() + min_gap)
            elif connection_type == Calendar.END:
                eo = task.getEndTimeFcastOffsetCalculated()
                if task.is_group and task.auto_update_time and task.automatic:
                    Calendar.refreshTaskRelation(task, self, mode=DAYS_OFFSET)
                min_gap = Calendar.getMinimalGap((task, connection_type),
                                                 (self, Calendar.START))
                values.add(eo + min_gap)
        val = max(NETWORK_CACHE[self][ES], NETWORK_CACHE[self][maxES])
        if values:
            __log__("%s: calculateStartTimeFcastOffset-Values = (%s)" % (self.task_name, values))
            val = max(values)
            if ct == ALAP:
                val = max(val, min(NETWORK_CACHE[self][LS],
                                   NETWORK_CACHE[self][minLS]))
            elif ct in [SNET, FNET] or forced_by_subtasks:
                constraint = self.getConstraintIndex(SNET)
                if Calendar.hasNoPredecessors(self):
                    val = constraint
                else:
                    val = max(val, constraint)
            elif ct in [SNLT, FNLT]:
                constraint = self.getConstraintIndex(SNLT)
                val = min(val, constraint)
            __log__("%s: calculateStartTimeFcastOffset-Chosen = %s (%s)" % (self.task_name, val, self.getDateByIndex(val)))
        else:
            if ct == ALAP:
                val = max(val, min(NETWORK_CACHE[self][LS],
                                   NETWORK_CACHE[self][minLS]))
            elif ct in [SNET, FNET] or forced_by_subtasks:
                constraint = self.getConstraintIndex(SNET)
                val = constraint
            elif ct in [SNLT, FNLT]:
                constraint = self.getConstraintIndex(SNLT)
                val = min(val, constraint)
            __log__("%s: calculateStartTimeFcastOffset-No preds = %s (%s)" % (self.task_name, val, self.getDateByIndex(val)))
        self.setStartTimeFcastOffset(val)

    def calculateEndTimeFcastOffset(self):
        predecessors = self.getDirektPredecessors(Calendar.END)
        values = set()
        for task, connection_type in predecessors:
            min_gap = Calendar.getMinimalGap((task, connection_type),
                                             (self, Calendar.END))
            if task == self:
                if not (self.is_group and self.auto_update_time
                        and self.automatic):
                    values.clear()
                    values.add(task.getStartTimeFcastOffsetCalculated() + min_gap)
                    break
            elif connection_type == Calendar.START:
                values.add(task.getStartTimeFcastOffsetCalculated() + min_gap)
            elif connection_type == Calendar.END:
                values.add(task.getEndTimeFcastOffsetCalculated() + min_gap)
        if not values:
            min_gap = Calendar.getMinimalGap((self, Calendar.START),
                                             (self, Calendar.END))
            values.add(self.getStartTimeFcastOffsetCalculated() + min_gap)
        val = max(values)
        __log__("%s: calculateEndTimeFcastOffset-Values = (%s)" % (self.task_name, values))
        __log__("%s: calculateEndTimeFcastOffset-Chosen = %s (%s)" % (self.task_name, val, self.getDateByIndex(val)))
        self.setEndTimeFcastOffset(val)

        # adjust start of task group in case there are some sub tasks set to a late start
        if self.is_group and self.auto_update_time and self.automatic:
            val = min(map(lambda x: x.getStartTimeFcastOffsetCalculated(), Calendar.getSubtasks(self)))
            NETWORK_CACHE[self][SO] = val
            NETWORK_CALCULATED[self][SO] = 1
            self.setStartTimeFcastOffset(val, minimize=False)
        self.calculateDays(DAYS_OFFSET)

    def calculateEarlyStart(self, minimum_offset):
        predecessors = self.getDirektPredecessors(Calendar.START)
        values = set()
        values2 = set()
        for task, connection_type in predecessors:
            if connection_type == Calendar.START:
                min_gap = Calendar.getMinimalGap((task, connection_type),
                                                 (self, Calendar.START))
                values.add(task.getEarlyStartCalculated(minimum_offset) + min_gap)
                values2.add(task.getEarlyStartCalculatedMax(minimum_offset) + min_gap)
            elif connection_type == Calendar.END:
                ef = task.getEarlyFinishCalculated(minimum_offset)
                ef_max = task.getEarlyFinishCalculatedMax(minimum_offset)
                if task.is_group and task.auto_update_time and task.automatic:
                    Calendar.refreshTaskRelation(task, self, mode=DAYS_EARLY)
                min_gap = Calendar.getMinimalGap((task, connection_type),
                                                 (self, Calendar.START))
                values.add(ef + min_gap)
                values2.add(ef_max + min_gap)
        val = minimum_offset
        val2 = minimum_offset
        if values:
            __log__("%s: calculateEarlyStart-Values = (%s)" % (self.task_name, values))
            __log__("%s: calculateEarlyStart-Values2 = (%s)" % (self.task_name, values2))
            val = max(values)
            val2 = max(values2)
            __log__("%s: calculateEarlyStart-Chosen = %s (%s)" % (self.task_name, val, self.getDateByIndex(val)))
            __log__("%s: calculateEarlyStart-Chosen2 = %s (%s)" % (self.task_name, val2, self.getDateByIndex(val2)))
        else:
            __log__("%s: calculateEarlyStart-No preds = %s (%s)" % (self.task_name, val, self.getDateByIndex(val)))
            __log__("%s: calculateEarlyStart-No preds2 = %s (%s)" % (self.task_name, val2, self.getDateByIndex(val2)))
        if NETWORK_CACHE[self][SO]:
            values = values2 = [NETWORK_CACHE[self][SO]]
        self.setEarlyStart(val)
        self.setEarlyStartMax(val2)

    def calculateEarlyFinish(self, minimum_offset):
        predecessors = self.getDirektPredecessors(Calendar.END)
        values = set()
        values2 = set()
        for task, connection_type in predecessors:
            min_gap = Calendar.getMinimalGap((task, connection_type),
                                             (self, Calendar.END))
            if task == self:
                if not (self.is_group and self.auto_update_time
                        and self.automatic):
                    values.clear()
                    values2.clear()
                    values.add(task.getEarlyStartCalculated(minimum_offset) + min_gap)
                    values2.add(task.getEarlyStartCalculatedMax(minimum_offset) + min_gap)
                    break
            elif connection_type == Calendar.START:
                values.add(task.getEarlyStartCalculated(minimum_offset) + min_gap)
                values2.add(task.getEarlyStartCalculatedMax(minimum_offset) + min_gap)
            elif connection_type == Calendar.END:
                values.add(task.getEarlyFinishCalculated(minimum_offset) + min_gap)
                values2.add(task.getEarlyFinishCalculatedMax(minimum_offset) + min_gap)
        if not values:
            min_gap = Calendar.getMinimalGap((self, Calendar.START),
                                             (self, Calendar.END))
            values.add(self.getEarlyStartCalculated(minimum_offset) + min_gap)
            values2.add(self.getEarlyStartCalculatedMax(minimum_offset) + min_gap)
        __log__("%s: calculateEarlyFinish-Values = (%s)" % (self.task_name, values))
        __log__("%s: calculateEarlyFinish-Values2 = (%s)" % (self.task_name, values2))
        self.setEarlyFinish(max(values))
        self.setEarlyFinishMax(max(values2))
        self.calculateDays(DAYS_EARLY)

    def calculateLateStart(self, maximum_offset):
        successors = self.getDirektSuccessors(Calendar.START)
        values = set()
        values2 = set()
        for task, connection_type in successors:
            min_gap = Calendar.getMinimalGap((self, Calendar.START),
                                             (task, connection_type))
            if task == self:
                if not self.is_group:
                    values.clear()
                    values2.clear()
                    values.add(task.getLateFinishCalculated(maximum_offset) - min_gap)
                    values2.add(task.getLateFinishCalculatedMin(maximum_offset) - min_gap)
                    break
            elif connection_type == Calendar.START:
                values.add(task.getLateStartCalculated(maximum_offset) - min_gap)
                values2.add(task.getLateStartCalculatedMin(maximum_offset) - min_gap)
            elif connection_type == Calendar.END:
                values.add(task.getLateFinishCalculated(maximum_offset) - min_gap)
                values2.add(task.getLateFinishCalculatedMin(maximum_offset) - min_gap)
        __log__("%s: calculateLateStart-Values = (%s)" % (self.task_name, values))
        __log__("%s: calculateLateStart-Values2 = (%s)" % (self.task_name, values2))
        self.setLateStart(min(values))
        self.setLateStartMin(min(values2))
        self.calculateDays(DAYS_LATE)

    def calculateLateFinish(self, maximum_offset):
        successors = self.getDirektSuccessors(Calendar.END)
        if not successors:
            __log__("%s: calculateLateFinish-No succs = %s (%s)" % (self.task_name, maximum_offset, self.getDateByIndex(maximum_offset)))
            self.setLateFinish(maximum_offset)
            self.setLateFinishMin(maximum_offset)
        else:
            values = set()
            values2 = set()
            for task, connection_type in successors:
                if connection_type == Calendar.START:
                    ls = task.getLateStartCalculated(maximum_offset)
                    ls_max = task.getLateStartCalculatedMin(maximum_offset)
                    if task.is_group and task.auto_update_time and task.automatic:
                        Calendar.refreshTaskRelation(self, task, mode=DAYS_LATE)
                    min_gap = Calendar.getMinimalGap((self, Calendar.END),
                                                 (task, connection_type))
                    values.add(ls - min_gap)
                    values2.add(ls_max - min_gap)
                elif connection_type == Calendar.END:
                    min_gap = Calendar.getMinimalGap((self, Calendar.END),
                                                 (task, connection_type))
                    values.add(task.getLateFinishCalculated(maximum_offset) - min_gap)
                    values2.add(task.getLateFinishCalculatedMin(maximum_offset) - min_gap)
            __log__("%s: calculateLateFinish-Values = (%s)" % (self.task_name, values))
            __log__("%s: calculateLateFinish-Values2 = (%s)" % (self.task_name, values2))
            self.setLateFinish(min(values))
            self.setLateFinishMin(min(values2))

    def checkConstraints(self, ctx=None):
        if self.is_group and not self.constraint_type in VALID_CONSTRAINTS_FOR_TASK_GROUPS:
            raise ue.Exception("cdbpcs_task_group_constraint", self.task_name)
