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 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 CM1Node GetFirstNodeFromIds(long lGrpId, long lGrpChId = GlobalDefs.DEF_UNDEFINED_VALUE)
        {
            trace?.Write(TraceLevel.Debug, "CM1GroupsTree::GetFirstNodeFromIds");
            bool fnResult = true;

            CM1Node 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].GrpId == lGrpId &&                    // GrpId
                        ((lGrpChId != GlobalDefs.DEF_UNDEFINED_VALUE &&  // GrpChildId is defined
                          m_Nodes[i].GrpChild != null &&
                          m_Nodes[i].GrpChild.GetGrpId() == lGrpChId)
                         ||
                         (lGrpChId == GlobalDefs.DEF_UNDEFINED_VALUE &&                // GrpChildId is not defined
                          m_Nodes[i].GrpChild == null)
                        )
                        )
                    {
                        nRdo = m_Nodes[i];
                        break;
                    }
                }
            }
            catch (Exception error)
            {
                trace?.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            if (!fnResult)
            {
                nRdo = null;
            }

            return(nRdo);
        }
        // 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);
        }
        // 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());
            }
        }
        // Fase 3: Compact Tree
        public bool PruneFase3()
        {
            trace?.Write(TraceLevel.Debug, "CM1GroupsTree::PruneFase3");
            bool fnResult = true;

            try
            {
                CM1Node[] dummyNodes  = new CM1Node[CM1GroupsTree.TREE_MAX_NODES];
                int       nNewNodeNum = 0;

                // Add node-base
                int j;
                for (j = 0; j < GetNodeNum(); j++)
                {
                    CM1Node pNode = m_Nodes[j];

                    if (pNode == null)
                    {
                        continue;
                    }

                    // Node is valid?
                    if (pNode.GrpChild == null && pNode.PruneFase == PruneFase.SECOND)
                    {
                        // Yes, copy the node
                        CM1Node pNdDest = dummyNodes[nNewNodeNum++];

                        if (pNdDest != null)
                        {
                            pNdDest.Copy(pNode);
                        }
                    }

                    if (pNode.GrpChild == null)
                    {
                        pNode.Init();
                    }
                }

                // Add the childs (if parent and child are in the new node-base set)
                for (j = 0; j < GetNodeNum(); j++)
                {
                    CM1Node pNode = m_Nodes[j];

                    if (pNode == null)
                    {
                        continue;
                    }

                    // Not node-base, only child relationship
                    if (pNode.GrpChild != null)
                    {
                        // ... if parent and child are in the new node-base set ...
                        bool bFindParent = false;
                        bool bFindChild  = false;
                        for (int i = 0; i < nNewNodeNum; i++)
                        {
                            CM1Node pNdDest = dummyNodes[i];

                            if (pNdDest != null && pNdDest.GrpChild == null && pNdDest.GrpId == pNode.GrpId)
                            {
                                bFindParent = true;
                            }


                            if (pNdDest != null && pNdDest.GrpChild == null && pNdDest.GrpId == pNode.GrpChild.GetGrpId())
                            {
                                bFindChild = true;
                            }
                        }

                        if (bFindParent && bFindChild) // Yes, copy the node
                        {
                            CM1Node pNdDest = dummyNodes[nNewNodeNum++];

                            if (pNdDest != null)
                            {
                                pNdDest.Copy(pNode);
                            }
                        }
                    }

                    pNode.Init();
                }

                m_nNodeNum = 0;

                // First we add base nodes
                for (j = 0; j < nNewNodeNum; j++)
                {
                    CM1Node pNdSrc = dummyNodes[j];

                    if (pNdSrc == null)
                    {
                        continue;
                    }

                    if (pNdSrc.GrpChild == null)
                    {
                        AddNode(pNdSrc.GrpId, GlobalDefs.DEF_UNDEFINED_VALUE, pNdSrc.CnstrNum);
                    }
                }

                // Second we add child nodes
                for (j = 0; j < nNewNodeNum; j++)
                {
                    CM1Node pNdSrc = dummyNodes[j];

                    if (pNdSrc == null)
                    {
                        continue;
                    }

                    if (pNdSrc.GrpChild != null)
                    {
                        AddNode(pNdSrc.GrpId, pNdSrc.GrpChild.GetGrpId());
                    }
                }
            }
            catch (Exception error)
            {
                trace?.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            return(fnResult);
        }
        // Fase 2: Save childs/parents of operation group
        public bool PruneFase2(long lGrpId)
        {
            trace?.Write(TraceLevel.Debug, "CM1GroupsTree::PruneFase2");
            bool fnResult = true;

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

                // Look for the group
                CM1Node pNode = GetFirstNodeFromIds(lGrpId);
                if (pNode == null)
                {
                    throw new InvalidOperationException("Can't find the group");
                }

                if (pNode.PruneFase == PruneFase.FIRST)
                {
                    pNode.PruneFase = PruneFase.SECOND;

                    // Look for parents and childs
                    for (int j = 0; j < GetNodeNum(); j++)
                    {
                        CM1Node pLoopNode = m_Nodes[j];

                        if (pLoopNode == null || // No node or
                            pLoopNode == pNode)  // node-group
                        {
                            continue;
                        }

                        if (pLoopNode.GrpId == lGrpId && pLoopNode.GrpChild != null)        // Child
                        {
                            if (!PruneFase2(pLoopNode.GrpChild.GetGrpId()))
                            {
                                throw new InvalidOperationException("Error in PruneFase2(pLoopNode->m_pGrpChild->GetGrpId()");
                            }
                        }


                        if (pLoopNode.GrpChild != null && pLoopNode.GrpChild.GetGrpId() == lGrpId) // Parent
                        {
                            if (!PruneFase2(pLoopNode.GrpId))
                            {
                                throw new InvalidOperationException("Error in PruneFase2(pLoopNode->m_lGrpId)");
                            }
                        }
                    }
                }
            }
            catch (Exception error)
            {
                trace?.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            return(fnResult);
        }
        // Prune
        // Fase 1: Save childs when parent has constraints
        public bool PruneFase1(long lGrpId = GlobalDefs.DEF_UNDEFINED_VALUE)
        {
            trace?.Write(TraceLevel.Debug, "CM1GroupsTree::PruneFase1");
            bool fnResult = true;

            try
            {
                for (int j = 0; j < GetNodeNum(); j++)
                {
                    CM1Node pNode = m_Nodes[j];

                    if (pNode == null)
                    {
                        continue;
                    }

                    if (lGrpId == GlobalDefs.DEF_UNDEFINED_VALUE && // first time
                        pNode.GrpChild == null &&                   // all leaves
                        pNode.CnstrNum != 0 &&                      // with constraints
                        pNode.PruneFase != PruneFase.FIRST)         // not pruned before
                    {
                        pNode.PruneFase = PruneFase.FIRST;          // set the prune level
                        if (pNode.GrpId == GlobalDefs.DEF_UNDEFINED_VALUE)
                        {
                            throw new InvalidOperationException("Error pNode->m_lGrpId == DEF_UNDEFINED_VALUE");
                        }

                        if (!PruneFase1(pNode.GrpId))       // look for childs
                        {
                            throw new InvalidOperationException("Error in inner call");
                        }
                    }
                    else                                        // I look for a group (and it's childs)
                    {
                        if (pNode.GrpId == lGrpId &&            // It is the group
                            pNode.PruneFase != PruneFase.FIRST) // not pruned before
                        {
                            if (pNode.GrpChild == null)         // It is a leave, set and continue;
                            {
                                pNode.PruneFase = PruneFase.FIRST;
                            }
                            else // Has childs ... look for them
                            {
                                if (pNode.GrpChild.GetGrpId() == GlobalDefs.DEF_UNDEFINED_VALUE)
                                {
                                    throw new InvalidOperationException("Error pNode->m_pGrpChild->GetGrpId() == DEF_UNDEFINED_VALUE");
                                }
                                if (!PruneFase1(pNode.GrpChild.GetGrpId()))
                                {
                                    throw new InvalidOperationException("Error in inner call");
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception error)
            {
                trace?.Write(TraceLevel.Error, error.ToLogString());
                fnResult = false;
            }

            return(fnResult);
        }
        public int AddNode(long lGrpId, long lGrpChildId = GlobalDefs.DEF_UNDEFINED_VALUE, int nNumCnstr = 0)
        {
            trace?.Write(TraceLevel.Debug, "CM1GroupsTree::AddNode");

            bool fnResult = true;
            int  nRdo     = ADD_TREE_OK;

            try
            {
                if (GetFirstNodeFromIds(lGrpId, lGrpChildId) != null)
                {
                    nRdo = ADD_TREE_WAS;
                }
                else
                {
                    m_Nodes[m_nNodeNum].Init();
                    m_Nodes[m_nNodeNum].GrpId = lGrpId;
                    m_Nodes[m_nNodeNum].Grp   = GetGroupFromGrpId(lGrpId);
                    m_nGroupNum++;

                    // Fill Group child info
                    if (lGrpChildId != GlobalDefs.DEF_UNDEFINED_VALUE) // It's a child!
                    {
                        // Verify parent existence
                        CM1Node pParentNode = GetFirstNodeFromIds(lGrpId);
                        if (pParentNode == null)
                        {
                            throw new InvalidOperationException("Trying to add child node without parent node");
                        }

                        m_Nodes[m_nNodeNum].GrpChild  = GetGroupFromGrpId(lGrpChildId);
                        m_Nodes[m_nNodeNum].ChildsNum = 0;
                        m_Nodes[m_nNodeNum].CnstrNum  = 0;

                        // Number of childs++
                        pParentNode.ChildsNum++;
                    }
                    else // Not Child
                    {
                        m_Nodes[m_nNodeNum].GrpChild  = null;
                        m_Nodes[m_nNodeNum].ChildsNum = 0;
                        m_Nodes[m_nNodeNum].CnstrNum  = nNumCnstr;
                    }

                    m_nNodeNum++;   // Inc node counter

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

            if (!fnResult)
            {
                nRdo = ADD_TREE_ERR;
            }

            return(nRdo);
        }