public bool GetBranchEfecAccumMoney(long lGroup, ref long m_lRealAccMoney)
        {
            trace.Write(TraceLevel.Debug, "CM1GroupTree::GetBranchAccumMoney");
            bool fnResult = true;

            try
            {
                CM1Group group = GetGroupFromGrpId(lGroup);
                if (group == null)
                {
                    throw new InvalidOperationException("Could not obtain group of current operation");
                }

                long lGrpAccMoney = 0;
                m_lRealAccMoney = 0;

                while (group != null)
                {
                    lGrpAccMoney    = group.GetEfecAccMoney();
                    m_lRealAccMoney = Math.Max(lGrpAccMoney, m_lRealAccMoney);
                    group           = GetGroupParent(group.GetGrpId());
                }

                trace.Write(TraceLevel.Info, $"Money accumulate for branch of group {lGroup}: {m_lRealAccMoney}");
            }
            catch (Exception error)
            {
                trace.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            return(fnResult);
        }
        public void TraceBranchM1ComputeEx1(long lGrpId)
        {
            trace.Write(TraceLevel.Debug, "CM1GroupsTree::TraceBranchM1ComputeEx1");
            try
            {
                trace.Write(TraceLevel.Debug, $"Tracing branch of group: {lGrpId}");
                trace.Write(TraceLevel.Debug, $"[  Group  | Type | NumChilds | NumConstraits | PruneFase | Money | Minutes ]");
                CM1Group pGrp = GetGroupFromGrpId(lGrpId);

                while (pGrp != null)
                {
                    // For each group have to be a node
                    CM1Node pNode = GetFirstNodeFromIds(pGrp.GetGrpId());

                    if (pNode != null)
                    {
                        trace.Write(TraceLevel.Debug, $"[ {pGrp.GetGrpId(),-7} | {pGrp.GetGrpTypeId(),-4} | {pNode.ChildsNum,9} | {pNode.CnstrNum,-13} | {pNode.PruneFase,-9} | {pGrp.GetAccMoney(),-5} | {pGrp.GetAccMinutes(),-7} ]");
                    }
                    pGrp = GetGroupParent(pGrp.GetGrpId());
                }
            }
            catch (Exception error)
            {
                trace.Write(TraceLevel.Error, error.ToLogString());
            }
        }
        public bool AmpliationIsAllowed(long groupId, ref bool ampliationIsAllowed)
        {
            trace.Write(TraceLevel.Debug, "CM1GroupsTree::AmpliationIsAllowed");
            bool fnResult = true;

            try
            {
                CM1Group group     = GetGroupFromGrpId(groupId);
                float    isAllowed = GlobalDefs.DEF_UNDEFINED_VALUE;
                group.GetConstraint(CM1Constraint.CNSTR_AMP_ALLOW, ref isAllowed);

                if (isAllowed == CM1Constraint.CNSTR_UNDEFINED)
                {
                    isAllowed = 1;
                }

                ampliationIsAllowed = isAllowed != 0;
            }
            catch (Exception error)
            {
                trace.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            return(fnResult);
        }
        public CM1Group GetGroupParent(long lGrpId)
        {
            trace?.Write(TraceLevel.Debug, "CM1GroupsTree::GetGroupParent");
            bool fnResult = true;

            CM1Group nRdo = null;

            try
            {
                if (lGrpId == GlobalDefs.DEF_UNDEFINED_VALUE)
                {
                    throw new ArgumentNullException(nameof(lGrpId), "lGrpId == DEF_UNDEFINED_VALUE");
                }

                for (int i = 0; i < m_nNodeNum; i++)
                {
                    if (m_Nodes[i].GrpChild != null && m_Nodes[i].GrpChild.GetGrpId() == lGrpId)
                    {
                        return(GetGroupFromGrpId(m_Nodes[i].GrpId));
                    }
                }
            }
            catch (Exception error)
            {
                trace?.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            if (!fnResult)
            {
                nRdo = null;
            }

            return(nRdo);
        }
        public bool Copy(CM1Group pSrcGroup)
        {
            trace?.Write(TraceLevel.Debug, "CM1Group::Copy");
            bool fnResult = true;

            try
            {
                trace     = pSrcGroup.trace;
                m_lId     = pSrcGroup.m_lId;
                m_lTypeId = pSrcGroup.m_lTypeId;
                m_dtLast  = pSrcGroup.m_dtLast.Copy();
                m_lState  = pSrcGroup.m_lState;
                //M1ComputeEx0 Attributes
                m_lAccumMoney   = pSrcGroup.m_lAccumMoney;
                m_lAccumMinutes = pSrcGroup.m_lAccumMinutes;
                //M1ComputeEx1 Attributes
                m_lEfecAccumMoney   = pSrcGroup.m_lEfecAccumMoney;
                m_lEfecAccumMinutes = pSrcGroup.m_lEfecAccumMinutes;
                m_lRealAccumMoney   = pSrcGroup.m_lRealAccumMoney;
                m_lRealAccumMinutes = pSrcGroup.m_lRealAccumMinutes;

                int i = 0;
                for (i = 0; i < CM1Constraint.CNSTR_NUM; i++)
                {
                    m_Cnstr[i].Copy(pSrcGroup.m_Cnstr[i]);
                }
            }
            catch (Exception error)
            {
                trace?.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            return(fnResult);
        }
        // OPERATIONS
        /// <summary>
        ///	Returns the maximum minutes that a user can spend in a branch without overflowing the maximum money constraint in any group
        /// </summary>
        /// <param name="groupId"></param>
        /// <param name="minutes"></param>
        /// <returns>bool</returns>
        public bool GetBranchMaxAvailableMinutes(long groupId, ref long minutes, bool isComputeEx1 = false)
        {
            trace?.Write(TraceLevel.Debug, "CM1GroupTree::GetBranchMaxAvailableMoney");
            bool fnResult = true;

            try
            {
                CM1Group treeGroup = GetGroupFromGrpId(groupId);
                if (treeGroup == null)
                {
                    throw new ArgumentNullException(nameof(groupId), "Could not obtain group of current operation");
                }

                long lGrpAccMinutes = 0;
                long lGrpMaxMinutes = 0;

                minutes = 999999999;

                while (treeGroup != null)
                {
                    if (isComputeEx1)
                    {
                        lGrpAccMinutes = treeGroup.GetRealAccMinutes();
                    }
                    else
                    {
                        lGrpAccMinutes = treeGroup.GetAccMinutes();
                    }

                    lGrpMaxMinutes = treeGroup.GetMaxMinutes();

                    if (lGrpMaxMinutes != GlobalDefs.DEF_UNDEFINED_VALUE)
                    {
                        minutes = Math.Min(lGrpMaxMinutes - lGrpAccMinutes, minutes);
                    }

                    treeGroup = GetGroupParent(treeGroup.GetGrpId());
                }

                trace?.Write(TraceLevel.Info, $"Minutes left for branch of group {groupId}: {minutes}");
            }
            catch (Exception error)
            {
                trace?.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            return(fnResult);
        }
        public bool IsGroupInTree(long lTreeGrpId, long lGrpId)
        {
            trace?.Write(TraceLevel.Debug, "CM1GroupsTree::IsGroupInTree");

            CM1Group pGrp = null;
            bool     bRes = false;
            long     lActGrpId;

            try
            {
                if (lGrpId == GlobalDefs.DEF_UNDEFINED_VALUE)
                {
                    throw new ArgumentNullException(nameof(lGrpId), "lGrpId == DEF_UNDEFINED_VALUE");
                }

                if (lTreeGrpId == GlobalDefs.DEF_UNDEFINED_VALUE)
                {
                    throw new ArgumentNullException(nameof(lTreeGrpId), "lTreeGrpId == DEF_UNDEFINED_VALUE");
                }

                lActGrpId = lGrpId;
                if (lTreeGrpId == lActGrpId)
                {
                    bRes = true;
                }
                else
                {
                    do
                    {
                        pGrp = GetGroupParent(lActGrpId);
                        if (pGrp != null)
                        {
                            lActGrpId = pGrp.GetGrpId();
                            if (lTreeGrpId == lActGrpId)
                            {
                                bRes = true;
                                break;
                            }
                        }
                    }while (pGrp != null);
                }
            }
            catch (Exception error)
            {
                trace?.Write(TraceLevel.Error, error.ToLogString());
            }

            return(bRes);
        }
        public void Init()
        {
            m_nNodeNum  = 0;
            m_nGroupNum = 0;

            for (int i = 0; i < TREE_MAX_NODES; i++)
            {
                m_Nodes[i] = new CM1Node();
            }

            for (int i = 0; i < TREE_MAX_GROUPS; i++)
            {
                m_Groups[i] = new CM1Group();
            }
        }
        public bool GetBranchMinMoney(long groupId, ref long money, bool isComputeEx1 = false)
        {
            trace.Write(TraceLevel.Debug, "CM1GroupsTree::GetBranchMinMoney");
            bool fnResult = true;

            try
            {
                // Get group object of the group of the operation to evaluate
                CM1Group pGrp = GetGroupFromGrpId(groupId);

                if (pGrp == null)
                {
                    throw  new InvalidOperationException("Could not obtain group of current operation");
                }

                long lGrpAccMoney = 0;
                long lGrpMinMoney = 0;
                money = -1;

                while (pGrp != null)
                {
                    lGrpAccMoney = (isComputeEx1) ? pGrp.GetEfecAccMoney() : pGrp.GetAccMoney();
                    lGrpMinMoney = pGrp.GetMinMoney();

                    if (lGrpMinMoney == GlobalDefs.DEF_UNDEFINED_VALUE || lGrpMinMoney < lGrpAccMoney)
                    {
                        lGrpMinMoney = 0;
                    }
                    else
                    {
                        lGrpMinMoney -= lGrpAccMoney;
                    }

                    money = Math.Max(lGrpMinMoney, money);

                    pGrp = GetGroupParent(pGrp.GetGrpId());
                }
                trace.Write(TraceLevel.Info, $"Min. money for branch of group {groupId}: {money}");
            }
            catch (Exception error)
            {
                trace.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            return(fnResult);
        }
        public bool CheckHistoryState(long lGroup, ref int iHistoryRes)
        {
            trace.Write(TraceLevel.Debug, "CM1GroupTree::CheckHistoryState");
            bool fnResult = true;

            int  iState;
            bool bGrpStop = true;

            try
            {
                CM1Group group = GetGroupFromGrpId(lGroup);
                if (group == null)
                {
                    throw new InvalidOperationException("Could not obtain group of current operation");
                }

                iHistoryRes = (int)M1GroupState.GRP_ON;

                while (group != null)
                {
                    iState = (int)group.GetState();
                    lGroup = group.GetGrpId();

                    bGrpStop = (bGrpStop && (iState == (int)M1GroupState.GRP_STOP));

                    if (iState == (int)M1GroupState.GRP_REE)
                    {
                        iHistoryRes = iState;
                        break;
                    }

                    group = GetGroupParent(lGroup);
                }

                if (bGrpStop)
                {
                    iHistoryRes = (int)M1GroupState.GRP_STOP;
                }
            }
            catch (Exception error)
            {
                trace.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            return(fnResult);
        }
        public bool GetMinValueToChargeInRefund(long groupId, ref long minMoney)
        {
            trace.Write(TraceLevel.Debug, "CM1GroupsTree::GetMinValueToChargeInRefund");
            bool fnResult = true;

            try
            {
                // Get group object of the group of the operation to evaluate
                CM1Group pGrp = GetGroupFromGrpId(groupId);

                if (pGrp == null)
                {
                    throw new InvalidOperationException("Could not obtain group of current operation");
                }

                long lGrpMinValueToCharge = 0;
                long lMinValueToCharge    = 0;
                minMoney = -1;

                while (pGrp != null)
                {
                    lGrpMinValueToCharge = pGrp.GetMinValueToChargeInRefund();

                    if (lGrpMinValueToCharge == GlobalDefs.DEF_UNDEFINED_VALUE)
                    {
                        lMinValueToCharge = 0;
                    }
                    else
                    {
                        lMinValueToCharge = lGrpMinValueToCharge;
                    }

                    minMoney = Math.Max(lMinValueToCharge, minMoney);

                    pGrp = GetGroupParent(pGrp.GetGrpId());
                }
                trace.Write(TraceLevel.Info, $"Min. money to charge in refunds {groupId}: {minMoney}");
            }
            catch (Exception error)
            {
                trace.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            return(fnResult);
        }
        // Constraints
        public bool MergeOrAddConstraint(CM1Group pGrp, long lCnstrDef, float fValue)
        {
            trace?.Write(TraceLevel.Debug, "CM1GroupsTree::MergeOrAddConstraint");
            bool fnResult = true;

            try
            {
                if (pGrp == null)
                {
                    throw new ArgumentNullException(nameof(pGrp), "pGrp is NULL");
                }

                // TODO: Best control
                // If MergeConstraint fails I suppose that previosly this type of constraint
                // was not set ... so I add it.
                float?result = null;
                if (!pGrp.MergeConstraint(lCnstrDef, fValue, ref result))
                {
                    if (pGrp.AddConstraint(lCnstrDef, fValue))
                    {
                        CM1Node pNode = GetFirstNodeFromIds(pGrp.GetGrpId());
                        if (pNode != null)
                        {
                            pNode.CnstrNum++;
                        }
                    }
                    else
                    {
                        fnResult = false;
                    }
                }
            }
            catch (Exception error)
            {
                trace?.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            return(fnResult);
        }
        public int AddGroup(long lGrpId, long lGrpTypeId)
        {
            trace?.Write(TraceLevel.Debug, "CM1GroupsTree::AddGroup");

            bool fnResult = true;
            int  nRdo     = ADD_TREE_OK;

            try
            {
                if (GetGroupFromGrpId(lGrpId) != null)
                {
                    nRdo = ADD_TREE_WAS;
                }
                else
                {
                    m_Groups[m_nGroupNum] = new CM1Group();
                    m_Groups[m_nGroupNum].Init();
                    m_Groups[m_nGroupNum].SetGrpId(lGrpId);
                    m_Groups[m_nGroupNum].SetGrpTypeId(lGrpTypeId);
                    m_nGroupNum++;

                    nRdo = AddNode(lGrpId);
                }
            }
            catch (Exception error)
            {
                trace?.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            if (!fnResult)
            {
                nRdo = ADD_TREE_ERR;
            }

            return(nRdo);
        }
        public bool ApplyCurrentOperation(long groupId, COPSDate operationDate)
        {
            trace?.Write(TraceLevel.Debug, "CM1GroupTree::ApplyCurrentOperation");
            bool fnResult = true;

            try
            {
                //m_dtOper
                CM1Group group = GetGroupFromGrpId(groupId);
                if (group == null)
                {
                    throw new InvalidOperationException("Group of operation not found in tree");
                }

                long groupIdTmp;
                while (group != null)
                {
                    groupIdTmp = group.GetGrpId();

                    group.SetState((long)M1GroupState.GRP_ON);
                    group.SetLastDate(operationDate);

                    group = GetGroupParent(groupIdTmp);
                    if (group == null)
                    {
                        trace?.Write(TraceLevel.Error, $"Group of operation ({groupId}) has no parents in tree");
                    }
                }
            }
            catch (Exception error)
            {
                trace?.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            return(fnResult);
        }
        public bool GetBranchMaxInterdateReentry(long lGroup, ref long lMaxInDtRee)
        {
            trace.Write(TraceLevel.Debug, "CM1GroupsTree::GetBranchMaxInterdateReentry");
            bool fnResult = true;

            try
            {
                lMaxInDtRee = GlobalDefs.DEF_UNDEFINED_VALUE;

                // Get group object of the group of the operation to evaluate
                CM1Group pGrp = GetGroupFromGrpId(lGroup);

                if (pGrp == null)
                {
                    throw new InvalidOperationException("Could not obtain group of current operation");
                }

                long lGrpAccInDtRee = GlobalDefs.DEF_UNDEFINED_VALUE;

                while ((pGrp != null) && (lGrpAccInDtRee == GlobalDefs.DEF_UNDEFINED_VALUE))
                {
                    lGrpAccInDtRee = pGrp.GetMaxInterdateReentry();

                    pGrp = GetGroupParent(pGrp.GetGrpId());
                }

                lMaxInDtRee = lGrpAccInDtRee;
                trace.Write(TraceLevel.Info, $"Max. Interdate for reentry for group {lGroup}: {lMaxInDtRee}");
            }
            catch (Exception error)
            {
                trace.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            return(fnResult);
        }
        // Trace
        public void TraceFullTree()
        {
            trace.Write(TraceLevel.Debug, "CM1GroupsTree::TraceFullTree");
            try
            {
                trace?.Write(TraceLevel.Debug, $"#Groups: {GetGroupNum()} , #Nodes:{GetNodeNum()}");
                trace?.Write(TraceLevel.Debug, "Groups [  Group  | Type | NumChilds | NumConstraits | PruneFase ]   Childs {  Group  | ChildGroup }:");
                trace?.Write(TraceLevel.Debug, "Constraints ( Type |   Value   ):");

                for (int i = 0; i < GetGroupNum(); i++)
                {
                    CM1Group pGrp = m_Groups[i];

                    if (pGrp == null)
                    {
                        continue;
                    }

                    // For each group have to be a node
                    CM1Node pNode = GetFirstNodeFromIds(pGrp.GetGrpId());
                    if (pNode != null)
                    {
                        trace?.Write(TraceLevel.Debug, $"       [ {pGrp.GetGrpId(),7} | {pGrp.GetGrpTypeId(),4} | {pNode.ChildsNum,9} | {pNode.CnstrNum,13} | {pNode.PruneFase} ]\t");

                        // A node can have constraints
                        CM1Constraint[] pCnstr = pGrp.GetConstraints();
                        for (int k = 0; k < CM1Constraint.CNSTR_NUM; k++)
                        {
                            if (!pCnstr[k].IsValid())
                            {
                                continue;
                            }

                            trace?.Write(TraceLevel.Debug, $"( {pCnstr[k].TypeId, 4} | %{pCnstr[k].Value},4)");
                        }

                        // A node can have childs
                        for (int j = 0; j < GetNodeNum(); j++)
                        {
                            pNode = m_Nodes[j];

                            if (pNode == null || pNode.GrpChild == null || pNode.GrpId != pGrp.GetGrpId())
                            {
                                continue;
                            }

                            trace?.Write(TraceLevel.Debug, $" ( {pNode.GrpId,7} | {pNode.GrpChild.GetGrpId(),10} )\t");
                        }


                        ////sRdo += _T("\n");
                        //trace?.Write(TraceLevel.Info, (LPCTSTR)strRdo);

                        //if (!strCnstr.IsEmpty())
                        //{
                        //    /*
                        //    sRdo += _T("\t");
                        //    sRdo += sCnstr;
                        //    sRdo += _T("\n");
                        //    */
                        //    trace.Write(TRACE_M1_LEVEL, _T("\t%s"), (LPCTSTR)strCnstr);
                        //}
                    }
                }
            }
            catch (Exception error)
            {
                trace.Write(TraceLevel.Error, error.ToLogString());
            }
        }