private void calculateStats()
        {
            foreach (Player player in log.getPlayerList())
            {
                Statistics.FinalStats[] phaseStats = new Statistics.FinalStats[phases.Count];
                for (int phaseIndex = 0; phaseIndex < phases.Count; phaseIndex++)
                {
                    Statistics.FinalStats final = new Statistics.FinalStats();

                    PhaseData phase = phases[phaseIndex];
                    long      start = phase.getStart() + log.getBossData().getFirstAware();
                    long      end   = phase.getEnd() + log.getBossData().getFirstAware();

                    List <DamageLog> damageLogs     = player.getDamageLogs(0, log, phase.getStart(), phase.getEnd());
                    List <DamageLog> damageLogsBoss = player.getDamageLogs(log.getBoss().getInstid(), log, phase.getStart(), phase.getEnd());
                    List <CastLog>   castLogs       = player.getCastLogs(log, phase.getStart(), phase.getEnd());

                    int instid = player.getInstid();

                    final.powerLoopCount = 0;
                    final.criticalRate   = 0;
                    final.criticalDmg    = 0;
                    final.scholarRate    = 0;
                    final.scholarDmg     = 0;
                    final.movingRate     = 0;
                    final.flankingRate   = 0;
                    final.glanceRate     = 0;
                    final.missed         = 0;
                    final.interupts      = 0;
                    final.invulned       = 0;
                    final.wasted         = 0;
                    final.timeWasted     = 0;
                    final.saved          = 0;
                    final.timeSaved      = 0;

                    final.powerLoopCountBoss = 0;
                    final.criticalRateBoss   = 0;
                    final.criticalDmgBoss    = 0;
                    final.scholarRateBoss    = 0;
                    final.scholarDmgBoss     = 0;
                    final.movingRateBoss     = 0;
                    final.flankingRateBoss   = 0;
                    final.glanceRateBoss     = 0;
                    final.missedBoss         = 0;
                    final.interuptsBoss      = 0;
                    final.invulnedBoss       = 0;

                    foreach (DamageLog log in damageLogs)
                    {
                        if (log.isCondi() == 0)
                        {
                            if (log.getResult() == ParseEnum.Result.Crit)
                            {
                                final.criticalRate++;
                                final.criticalDmg += log.getDamage();
                            }

                            if (log.isNinety() > 0)
                            {
                                final.scholarRate++;
                                final.scholarDmg += (int)(log.getDamage() / 11.0); //regular+10% damage
                            }

                            final.movingRate   += log.isMoving();
                            final.flankingRate += log.isFlanking();

                            if (log.getResult() == ParseEnum.Result.Glance)
                            {
                                final.glanceRate++;
                            }

                            if (log.getResult() == ParseEnum.Result.Blind)
                            {
                                final.missed++;
                            }

                            if (log.getResult() == ParseEnum.Result.Interrupt)
                            {
                                final.interupts++;
                            }

                            if (log.getResult() == ParseEnum.Result.Absorb)
                            {
                                final.invulned++;
                            }
                            final.powerLoopCount++;
                        }
                    }
                    foreach (DamageLog log in damageLogsBoss)
                    {
                        if (log.isCondi() == 0)
                        {
                            if (log.getResult() == ParseEnum.Result.Crit)
                            {
                                final.criticalRateBoss++;
                                final.criticalDmgBoss += log.getDamage();
                            }

                            if (log.isNinety() > 0)
                            {
                                final.scholarRateBoss++;
                                final.scholarDmgBoss += (int)(log.getDamage() / 11.0); //regular+10% damage
                            }

                            final.movingRateBoss   += log.isMoving();
                            final.flankingRateBoss += log.isFlanking();

                            if (log.getResult() == ParseEnum.Result.Glance)
                            {
                                final.glanceRateBoss++;
                            }

                            if (log.getResult() == ParseEnum.Result.Blind)
                            {
                                final.missedBoss++;
                            }

                            if (log.getResult() == ParseEnum.Result.Interrupt)
                            {
                                final.interuptsBoss++;
                            }

                            if (log.getResult() == ParseEnum.Result.Absorb)
                            {
                                final.invulnedBoss++;
                            }
                            final.powerLoopCountBoss++;
                        }
                    }
                    foreach (CastLog cl in castLogs)
                    {
                        if (cl.endActivation() == ParseEnum.Activation.CancelCancel)
                        {
                            final.wasted++;
                            final.timeWasted += cl.getActDur();
                        }
                        if (cl.endActivation() == ParseEnum.Activation.CancelFire)
                        {
                            final.saved++;
                            if (cl.getActDur() < cl.getExpDur())
                            {
                                final.timeSaved += cl.getExpDur() - cl.getActDur();
                            }
                        }
                    }

                    final.timeSaved  = final.timeSaved / 1000f;
                    final.timeWasted = final.timeWasted / 1000f;

                    final.totalDmg       = damageLogs.Sum(x => x.getDamage());
                    final.powerLoopCount = final.powerLoopCount == 0 ? 1 : final.powerLoopCount;

                    final.totalDmgBoss       = damageLogsBoss.Sum(x => x.getDamage());
                    final.powerLoopCountBoss = final.powerLoopCountBoss == 0 ? 1 : final.powerLoopCountBoss;

                    // Counts
                    CombatData combatData = log.getCombatData();
                    final.swapCount  = combatData.getStates(instid, ParseEnum.StateChange.WeaponSwap, start, end).Count();
                    final.downCount  = combatData.getStates(instid, ParseEnum.StateChange.ChangeDown, start, end).Count();
                    final.dodgeCount = combatData.getSkillCount(instid, 65001, start, end) + combatData.getBuffCount(instid, 40408, start, end); //dodge = 65001 mirage cloak =40408
                    final.ressCount  = combatData.getSkillCount(instid, 1066, start, end);                                                       //Res = 1066

                    // R.I.P
                    List <CombatItem> dead = combatData.getStates(instid, ParseEnum.StateChange.ChangeDead, start, end);
                    final.died = 0.0;
                    if (dead.Count() > 0)
                    {
                        final.died = dead[0].getTime() - start;
                    }

                    List <CombatItem> disconect = combatData.getStates(instid, ParseEnum.StateChange.Despawn, start, end);
                    final.dcd = 0.0;
                    if (disconect.Count() > 0)
                    {
                        final.dcd = disconect[0].getTime() - start;
                    }

                    phaseStats[phaseIndex] = final;
                }
                statistics.stats[player] = phaseStats;
            }
        }
        private void doMechData()
        {
            List <int> mIDList = new List <int>();

            foreach (Player p in p_list)
            {
                List <CombatItem> down = combat_data.getStates(p.getInstid(), ParseEnum.StateChange.ChangeDown, boss_data.getFirstAware(), boss_data.getLastAware());
                foreach (CombatItem pnt in down)
                {
                    mech_data.AddItem(new MechanicLog((long)((pnt.getTime() - boss_data.getFirstAware()) / 1000f), 0, "DOWN", 0, p, mech_data.GetPLoltyShape("DOWN")));
                }
                List <CombatItem> dead = combat_data.getStates(p.getInstid(), ParseEnum.StateChange.ChangeDead, boss_data.getFirstAware(), boss_data.getLastAware());
                foreach (CombatItem pnt in dead)
                {
                    mech_data.AddItem(new MechanicLog((long)((pnt.getTime() - boss_data.getFirstAware()) / 1000f), 0, "DEAD", 0, p, mech_data.GetPLoltyShape("DEAD")));
                }
                List <DamageLog> dls = p.getDamageTakenLogs(this, 0, boss_data.getAwareDuration());
                //Player hit by skill 3
                MechanicLog prevMech = null;
                foreach (DamageLog dLog in dls)
                {
                    string name = skill_data.getName(dLog.getID());
                    if (dLog.getResult().IsHit())
                    {
                        foreach (Mechanic mech in mech_data.GetMechList(boss_data.getID()).Where(x => x.GetMechType() == Mechanic.MechType.SkillOnPlayer))
                        {
                            //Prevent multi hit attacks form multi registering
                            if (prevMech != null)
                            {
                                if (dLog.getID() == prevMech.GetSkill() && mech.GetName() == prevMech.GetName() && (dLog.getTime() / 1000f) == prevMech.GetTime())
                                {
                                    break;
                                }
                            }
                            if (dLog.getID() == mech.GetSkill())
                            {
                                prevMech = new MechanicLog((long)(dLog.getTime() / 1000f), dLog.getID(), mech.GetName(), dLog.getDamage(), p, mech.GetPlotly());

                                mech_data.AddItem(prevMech);
                                break;
                            }
                        }
                    }
                }
                //Player gain buff 0,7
                foreach (CombatItem c in combat_data.getCombatList().Where(x => x.isBuffremove() == ParseEnum.BuffRemove.None && x.isStateChange() == ParseEnum.StateChange.Normal))
                {
                    if (p.getInstid() == c.getDstInstid())
                    {
                        if (c.isBuff() == 1 && c.getValue() > 0 && c.isBuffremove() == ParseEnum.BuffRemove.None && c.getResult().IsHit())
                        {
                            String name = skill_data.getName(c.getSkillID());
                            //buff on player 0
                            foreach (Mechanic mech in mech_data.GetMechList(boss_data.getID()).Where(x => x.GetMechType() == Mechanic.MechType.PlayerBoon))
                            {
                                if (c.getSkillID() == mech.GetSkill())
                                {
                                    //dst player
                                    mech_data.AddItem(new MechanicLog((long)((c.getTime() - boss_data.getFirstAware()) / 1000f), c.getSkillID(), mech.GetName(), c.getValue(), p, mech.GetPlotly()));
                                    break;
                                }
                            }
                            //player on player 7
                            foreach (Mechanic mech in mech_data.GetMechList(boss_data.getID()).Where(x => x.GetMechType() == Mechanic.MechType.PlayerOnPlayer))
                            {
                                if (c.getSkillID() == mech.GetSkill())
                                {
                                    //dst player
                                    mech_data.AddItem(new MechanicLog((long)((c.getTime() - boss_data.getFirstAware()) / 1000f), c.getSkillID(), mech.GetName(), c.getValue(), p, mech.GetPlotly()));
                                    //src player
                                    mech_data.AddItem(new MechanicLog((long)((c.getTime() - boss_data.getFirstAware()) / 1000f), c.getSkillID(), mech.GetName(), c.getValue(), p_list.FirstOrDefault(i => i.getInstid() == c.getSrcInstid()), mech.GetPlotly()));
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Parses all the data again and link related stuff to each other
        /// </summary>
        private void fillMissingData()
        {
            var agentsLookup = agent_data.getAllAgentsList().ToDictionary(a => a.getAgent());

            bool golem_mode = isGolem(boss_data.getID());

            // Set Agent instid, first_aware and last_aware
            var combat_list = combat_data.getCombatList();

            foreach (CombatItem c in combat_list)
            {
                if (agentsLookup.TryGetValue(c.getSrcAgent(), out var a))
                {
                    if (a.getInstid() == 0 && (c.isStateChange() == ParseEnum.StateChange.Normal || (golem_mode && isGolem(a.getID()) && c.isStateChange() == ParseEnum.StateChange.MaxHealthUpdate)))
                    {
                        a.setInstid(c.getSrcInstid());
                    }
                    if (a.getInstid() != 0)
                    {
                        if (a.getFirstAware() == 0)
                        {
                            a.setFirstAware(c.getTime());
                            a.setLastAware(c.getTime());
                        }
                        else
                        {
                            a.setLastAware(c.getTime());
                        }
                    }
                }
            }

            foreach (CombatItem c in combat_list)
            {
                if (c.getSrcMasterInstid() != 0)
                {
                    var master = agent_data.getAllAgentsList().Find(x => x.getInstid() == c.getSrcMasterInstid() && x.getFirstAware() < c.getTime() && c.getTime() < x.getLastAware());
                    if (master != null)
                    {
                        if (agentsLookup.TryGetValue(c.getSrcAgent(), out var minion) && minion.getFirstAware() < c.getTime() && c.getTime() < minion.getLastAware())
                        {
                            minion.setMasterAgent(master.getAgent());
                        }
                    }
                }
            }

            agent_data.clean();

            // Set Boss data agent, instid, first_aware, last_aware and name
            List <AgentItem> NPC_list      = agent_data.getNPCAgentList();
            HashSet <ulong>  multiple_boss = new HashSet <ulong>();

            foreach (AgentItem NPC in NPC_list)
            {
                if (NPC.getProf().EndsWith(boss_data.getID().ToString()))
                {
                    if (boss_data.getAgent() == 0)
                    {
                        boss_data.setAgent(NPC.getAgent());
                        boss_data.setInstid(NPC.getInstid());
                        boss_data.setFirstAware(NPC.getFirstAware());
                        boss_data.setName(NPC.getName());
                        boss_data.setTough(NPC.getToughness());
                    }
                    multiple_boss.Add(NPC.getAgent());
                    boss_data.setLastAware(NPC.getLastAware());
                }
            }
            if (multiple_boss.Count > 1)
            {
                agent_data.cleanInstid(boss_data.getInstid());
            }

            AgentItem bossAgent = agent_data.GetAgent(boss_data.getAgent());

            boss = new Boss(bossAgent);
            List <Point> bossHealthOverTime = new List <Point>();

            // a hack for buggy golem logs
            if (golem_mode)
            {
                ulong redirection = 0;

                foreach (AgentItem a in agent_data.getAllAgentsList())
                {
                    if (a.getID() == 19603)
                    {
                        redirection = a.getAgent();
                    }
                }

                if (redirection != 0)
                {
                    foreach (CombatItem c in combat_list)
                    {
                        if (c.getDstAgent() == 0 && c.getDstInstid() == 0 && c.isStateChange() == ParseEnum.StateChange.Normal && c.getIFF() == ParseEnum.IFF.Foe && c.isActivation() == ParseEnum.Activation.None)
                        {
                            c.setDstAgent(bossAgent.getAgent());
                            c.setDstInstid(bossAgent.getInstid());
                        }
                    }
                }
            }
            // Grab values threw combat data
            foreach (CombatItem c in combat_list)
            {
                if (c.getSrcInstid() == boss_data.getInstid() && c.isStateChange() == ParseEnum.StateChange.MaxHealthUpdate)//max health update
                {
                    boss_data.setHealth((int)c.getDstAgent());
                }
                switch (c.isStateChange())
                {
                case ParseEnum.StateChange.PointOfView:
                    if (log_data.getPOV() == "N/A")    //Point of View
                    {
                        ulong pov_agent = c.getSrcAgent();
                        if (agentsLookup.TryGetValue(pov_agent, out var p))
                        {
                            log_data.setPOV(p.getName());
                        }
                    }
                    break;

                case ParseEnum.StateChange.LogStart:
                    log_data.setLogStart(c.getValue());
                    break;

                case ParseEnum.StateChange.LogEnd:
                    log_data.setLogEnd(c.getValue());
                    break;

                case ParseEnum.StateChange.HealthUpdate:
                    //set health update
                    if (c.getSrcInstid() == boss_data.getInstid())
                    {
                        bossHealthOverTime.Add(new Point((int)(c.getTime() - boss_data.getFirstAware()), (int)c.getDstAgent()));
                    }
                    break;
                }
            }

            // Dealing with second half of Xera | ((22611300 * 0.5) + (25560600 * 0.5)
            if (boss_data.getID() == 16246)
            {
                int xera_2_instid = 0;
                foreach (AgentItem NPC in NPC_list)
                {
                    if (NPC.getProf().Contains("16286"))
                    {
                        bossHealthOverTime = new List <Point>();//reset boss health over time
                        xera_2_instid      = NPC.getInstid();
                        boss_data.setHealth(24085950);
                        boss.addPhaseData(boss_data.getLastAware());
                        boss.addPhaseData(NPC.getFirstAware());
                        boss_data.setLastAware(NPC.getLastAware());
                        foreach (CombatItem c in combat_list)
                        {
                            if (c.getSrcInstid() == xera_2_instid)
                            {
                                c.setSrcInstid(boss_data.getInstid());
                            }
                            if (c.getDstInstid() == xera_2_instid)
                            {
                                c.setDstInstid(boss_data.getInstid());
                            }
                            //set health update
                            if (c.getSrcInstid() == boss_data.getInstid() && c.isStateChange() == ParseEnum.StateChange.HealthUpdate)
                            {
                                bossHealthOverTime.Add(new Point((int)(c.getTime() - boss_data.getFirstAware()), (int)c.getDstAgent()));
                            }
                        }
                        break;
                    }
                }
            }
            //Dealing with Deimos split
            if (boss_data.getID() == 17154)
            {
                int deimos_2_instid = 0;
                foreach (AgentItem NPC in agent_data.getGadgetAgentList())
                {
                    if (NPC.getProf().Contains("08467") || NPC.getProf().Contains("08471"))
                    {
                        deimos_2_instid = NPC.getInstid();
                        long oldAware = boss_data.getLastAware();
                        if (NPC.getLastAware() < boss_data.getLastAware())
                        {
                            // No split
                            break;
                        }
                        boss.addPhaseData(NPC.getFirstAware() >= oldAware ? NPC.getFirstAware() : oldAware);
                        boss_data.setLastAware(NPC.getLastAware());
                        //List<CombatItem> fuckyou = combat_list.Where(x => x.getDstInstid() == deimos_2_instid ).ToList().Sum(x);
                        //int stop = 0;
                        foreach (CombatItem c in combat_list)
                        {
                            if (c.getTime() > oldAware)
                            {
                                if (c.getSrcInstid() == deimos_2_instid)
                                {
                                    c.setSrcInstid(boss_data.getInstid());
                                }
                                if (c.getDstInstid() == deimos_2_instid)
                                {
                                    c.setDstInstid(boss_data.getInstid());
                                }
                            }
                        }
                        break;
                    }
                }
            }
            boss_data.setHealthOverTime(bossHealthOverTime);//after xera in case of change

            // Re parse to see if the boss is dead and update last aware
            foreach (CombatItem c in combat_list)
            {
                //set boss dead
                if (c.isStateChange() == ParseEnum.StateChange.Reward)//got reward
                {
                    log_data.setBossKill(true);
                    boss_data.setLastAware(c.getTime());
                    break;
                }
                //set boss dead
                if (c.getSrcInstid() == boss_data.getInstid() && c.isStateChange() == ParseEnum.StateChange.ChangeDead && !log_data.getBosskill())//change dead
                {
                    log_data.setBossKill(true);
                    boss_data.setLastAware(c.getTime());
                }
            }

            //players
            if (p_list.Count == 0)
            {
                //Fix Disconected players
                var playerAgentList = agent_data.getPlayerAgentList();

                foreach (AgentItem playerAgent in playerAgentList)
                {
                    List <CombatItem> lp     = combat_data.getStates(playerAgent.getInstid(), ParseEnum.StateChange.Despawn, boss_data.getFirstAware(), boss_data.getLastAware());
                    Player            player = new Player(playerAgent);
                    bool skip = false;
                    foreach (Player p in p_list)
                    {
                        if (p.getAccount() == player.getAccount())//is this a copy of original?
                        {
                            skip = true;
                        }
                    }
                    if (skip)
                    {
                        continue;
                    }
                    if (lp.Count > 0)
                    {
                        //make all actions of other instances to original instid
                        foreach (AgentItem extra in NPC_list)
                        {
                            if (extra.getAgent() == playerAgent.getAgent())
                            {
                                var extra_login_Id = extra.getInstid();
                                foreach (CombatItem c in combat_list)
                                {
                                    if (c.getSrcInstid() == extra_login_Id)
                                    {
                                        c.setSrcInstid(playerAgent.getInstid());
                                    }
                                    if (c.getDstInstid() == extra_login_Id)
                                    {
                                        c.setDstInstid(playerAgent.getInstid());
                                    }
                                }
                                break;
                            }
                        }

                        player.SetDC(lp[0].getTime());
                        p_list.Add(player);
                    }
                    else//didnt dc
                    {
                        if (player.GetDC() == 0)
                        {
                            p_list.Add(player);
                        }
                    }
                }
            }
            // Sort
            p_list = p_list.OrderBy(a => a.getGroup()).ToList();
        }