private void ArchiveBattle(SpringBattle battle, string path)
        {
            if (!string.IsNullOrEmpty(battle.GlacierArchiveID))
            {
                Trace.TraceWarning("Unable to archive battle {0} : already archived", battle.SpringBattleID);
                return;
            }

            if (string.IsNullOrEmpty(battle.ReplayFileName))
            {
                Trace.TraceWarning("Unable to archive battle {0} : no file name in DB", battle.SpringBattleID);
                return;
            }

            using (var fs = File.OpenRead(path))
            {
                var result = StoreArchive(fs, $"Spring battle {battle.SpringBattleID}");
                if (!string.IsNullOrEmpty(result.ArchiveId) && (result.HttpStatusCode == HttpStatusCode.OK || result.HttpStatusCode == HttpStatusCode.Accepted || result.HttpStatusCode == HttpStatusCode.Created))
                {
                    battle.GlacierArchiveID = result.ArchiveId;
                    Trace.TraceInformation("Spring battle {0} archived as {1}", battle.SpringBattleID, result.ArchiveId);
                }
                else
                {

                    throw new Exception("Invalid glacier upload: " + result.HttpStatusCode);
                }
            }
        }
        private static void ProcessElos(Spring.SpringBattleContext result, ZkLobbyServer.ZkLobbyServer server, ZkDataContext db, SpringBattle sb)
        {
            bool noElo = result.OutputExtras.Any(x => x?.StartsWith("noElo", true, System.Globalization.CultureInfo.CurrentCulture) == true);

            Dictionary<int, int> orgLevels = sb.SpringBattlePlayers.Select(x => x.Account).ToDictionary(x => x.AccountID, x => x.Level);

            sb.CalculateAllElo(noElo);
            foreach (var u in sb.SpringBattlePlayers.Where(x => !x.IsSpectator)) u.Account.CheckLevelUp();

            db.SaveChanges();

            try
            {
                foreach (Account a in sb.SpringBattlePlayers.Where(x => !x.IsSpectator).Select(x => x.Account)) server.PublishAccountUpdate(a);
            }
            catch (Exception ex)
            {
                Trace.TraceError("error updating extension data: {0}", ex);
            }

            foreach (Account account in sb.SpringBattlePlayers.Select(x => x.Account))
            {
                if (account.Level > orgLevels[account.AccountID])
                {
                    try
                    {
                        string message = string.Format("Congratulations {0}! You just leveled up to level {1}. {3}/Users/Detail/{2}",
                            account.Name,
                            account.Level,
                            account.AccountID,
                            GlobalConst.BaseSiteUrl);
                        //text.AppendLine(message);
                        server.GhostPm(account.Name, message);
                    }
                    catch (Exception ex)
                    {
                        Trace.TraceError("Error sending level up lobby message: {0}", ex);
                    }
                }
            }
        }
        /// <summary>
        /// Updates shadow influence and new owners
        /// </summary>
        /// <param name="db"></param>
        /// <param name="sb">optional spring batle that caused this change (for event logging)</param>
        public static void SetPlanetOwners(ZkDataContext db = null, SpringBattle sb = null)
        {
            if (db == null) db = new ZkDataContext();

            Galaxy gal = db.Galaxies.Single(x => x.IsDefault);
            foreach (Planet planet in gal.Planets)
            {
                if (planet.OwnerAccountID != null) foreach (var ps in planet.PlanetStructures.Where(x => x.OwnerAccountID == null))
                    {
                        ps.OwnerAccountID = planet.OwnerAccountID;
                        ps.IsActive = false;
                        ps.ActivatedOnTurn = null;
                    }


                PlanetFaction best = planet.PlanetFactions.OrderByDescending(x => x.Influence).FirstOrDefault();
                Faction newFaction = planet.Faction;
                Account newAccount = planet.Account;

                if (best == null || best.Influence < GlobalConst.InfluenceToCapturePlanet)
                {
                    // planet not capture 

                    if (planet.Faction != null)
                    {
                        var curFacInfluence =
                            planet.PlanetFactions.Where(x => x.FactionID == planet.OwnerFactionID).Select(x => x.Influence).FirstOrDefault();

                        if (curFacInfluence <= GlobalConst.InfluenceToLosePlanet)
                        {
                            // owners have too small influence, planet belong to nobody
                            newFaction = null;
                            newAccount = null;
                        }
                    }
                }
                else
                {
                    if (best.Faction != planet.Faction)
                    {
                        newFaction = best.Faction;

                        // best attacker without planets
                        Account candidate =
                            planet.AccountPlanets.Where(
                                x => x.Account.FactionID == newFaction.FactionID && x.AttackPoints > 0 && !x.Account.Planets.Any()).OrderByDescending(
                                    x => x.AttackPoints).Select(x => x.Account).FirstOrDefault();

                        if (candidate == null)
                        {
                            // best attacker
                            candidate =
                                planet.AccountPlanets.Where(x => x.Account.FactionID == newFaction.FactionID && x.AttackPoints > 0).OrderByDescending(
                                    x => x.AttackPoints).ThenBy(x => x.Account.Planets.Count()).Select(x => x.Account).FirstOrDefault();
                        }

                        // best player without planets
                        if (candidate == null)
                        {
                            candidate =
                                newFaction.Accounts.Where(x => !x.Planets.Any()).OrderByDescending(x => x.AccountPlanets.Sum(y => y.AttackPoints)).
                                    FirstOrDefault();
                        }

                        // best with planets 
                        if (candidate == null)
                        {
                            candidate =
                                newFaction.Accounts.OrderByDescending(x => x.AccountPlanets.Sum(y => y.AttackPoints)).
                                    FirstOrDefault();
                        }

                        newAccount = candidate;
                    }
                }

                // change has occured
                if (newFaction != planet.Faction)
                {
                    // disable structures 
                    foreach (PlanetStructure structure in planet.PlanetStructures.Where(x => x.StructureType.OwnerChangeDisablesThis))
                    {
                        structure.IsActive = false;
                        structure.ActivatedOnTurn = null;
                        structure.Account = newAccount;
                    }

                    // delete structures being lost on planet change
                    foreach (PlanetStructure structure in
                        planet.PlanetStructures.Where(structure => structure.StructureType.OwnerChangeDeletesThis).ToList()) db.PlanetStructures.DeleteOnSubmit(structure);

                    // reset attack points memory
                    foreach (AccountPlanet acp in planet.AccountPlanets) acp.AttackPoints = 0;

                    if (newFaction == null)
                    {
                        Account account = planet.Account;
                        Clan clan = null;
                        if (account != null)
                        {
                            clan = planet.Account != null ? planet.Account.Clan : null;
                        }

                        db.Events.InsertOnSubmit(Global.CreateEvent("{0} planet {1} owned by {2} {3} was abandoned. {4}",
                                                                    planet.Faction,
                                                                    planet,
                                                                    account,
                                                                    clan,
                                                                    sb));
                        if (account != null) {
                            Global.Server.GhostPm(planet.Account.Name, string.Format(
                                "Warning, you just lost planet {0}!! {2}/PlanetWars/Planet/{1}",
                                planet.Name,
                                planet.PlanetID,
                                GlobalConst.BaseSiteUrl));
                        }
                    }
                    else
                    {
                        // new real owner

                        // log messages
                        if (planet.OwnerAccountID == null) // no previous owner
                        {
                            db.Events.InsertOnSubmit(Global.CreateEvent("{0} has claimed planet {1} for {2} {3}. {4}",
                                                                        newAccount,
                                                                        planet,
                                                                        newFaction,
                                                                        newAccount.Clan,
                                                                        sb));
                            Global.Server.GhostPm(newAccount.Name, string.Format(
                                "Congratulations, you now own planet {0}!! {2}/PlanetWars/Planet/{1}",
                                planet.Name,
                                planet.PlanetID,
                                GlobalConst.BaseSiteUrl));
                        }
                        else
                        {
                            db.Events.InsertOnSubmit(Global.CreateEvent("{0} of {1} {2} has captured planet {3} from {4} of {5} {6}. {7}",
                                                                        newAccount,
                                                                        newFaction,
                                                                        newAccount.Clan,
                                                                        planet,
                                                                        planet.Account,
                                                                        planet.Faction,
                                                                        planet.Account.Clan,
                                                                        sb));

                            Global.Server.GhostPm(newAccount.Name, string.Format(
                                "Congratulations, you now own planet {0}!! {2}/PlanetWars/Planet/{1}",
                                planet.Name,
                                planet.PlanetID,
                                GlobalConst.BaseSiteUrl));

                            Global.Server.GhostPm(planet.Account.Name, string.Format(
                                "Warning, you just lost planet {0}!! {2}/PlanetWars/Planet/{1}",
                                planet.Name,
                                planet.PlanetID,
                                GlobalConst.BaseSiteUrl));
                        }
                    }

                    planet.Faction = newFaction;
                    planet.Account = newAccount;
                }
                ReturnPeacefulDropshipsHome(db, planet);
            }
            db.SaveChanges();
        }
    /// <summary>
    /// Process planet wars turn
    /// </summary>
    /// <param name="mapName"></param>
    /// <param name="extraData"></param>
    /// <param name="db"></param>
    /// <param name="winNum">0 = attacker wins, 1 = defender wins</param>
    /// <param name="players"></param>
    /// <param name="text"></param>
    /// <param name="sb"></param>
    public static void EndTurn(string mapName, List<string> extraData, ZkDataContext db, int? winNum, List<Account> players, StringBuilder text, SpringBattle sb, List<Account> attackers)
    {
        if (extraData == null) extraData = new List<string>();
        Galaxy gal = db.Galaxies.Single(x => x.IsDefault);
        Planet planet = gal.Planets.Single(x => x.Resource.InternalName == mapName);

        text.AppendFormat("Battle on {1}/PlanetWars/Planet/{0} has ended\n", planet.PlanetID, GlobalConst.BaseSiteUrl);


        Faction attacker = attackers.Where(x => x.Faction != null).Select(x => x.Faction).First();
        var defenders = players.Where(x => x.FactionID != attacker.FactionID && x.FactionID != null).ToList();
        bool isAttackerWinner;
        bool wasAttackerCcDestroyed = false;
        bool wasDefenderCcDestroyed = false;

        if (winNum != null)
        {
            if (winNum == 0) isAttackerWinner = true;
            else isAttackerWinner = false;

            wasAttackerCcDestroyed = extraData.Any(x => x.StartsWith("hqkilled,0"));
            wasDefenderCcDestroyed = extraData.Any(x => x.StartsWith("hqkilled,1"));
        }
        else
        {
            text.AppendFormat("No winner on this battle!");
            Trace.TraceError("PW battle without a winner {0}", sb != null ? sb.SpringBattleID : (int?)null);
            return;
        }

        int dropshipsSent = (planet.PlanetFactions.Where(x => x.Faction == attacker).Sum(x => (int?)x.Dropships) ?? 0);
        bool isLinked = planet.CanDropshipsAttack(attacker);
        string influenceReport = "";

        // distribute influence
        // save influence gains
        // give influence to main attackers
        double planetDropshipDefs = (planet.PlanetStructures.Where(x => x.IsActive).Sum(x => x.StructureType.EffectDropshipDefense) ?? 0);
        double planetIpDefs = (planet.PlanetStructures.Where(x => x.IsActive).Sum(x => x.StructureType.EffectReduceBattleInfluenceGain) ?? 0);

        double baseInfluence = GlobalConst.BaseInfluencePerBattle;
        double influence = baseInfluence;


        double effectiveShips = Math.Max(0, (dropshipsSent - planetDropshipDefs));
        double shipBonus = effectiveShips*GlobalConst.InfluencePerShip;

        double defenseBonus = -planetIpDefs;
        double techBonus = attacker.GetFactionUnlocks().Count() * GlobalConst.InfluencePerTech;
        double ipMultiplier = 1;
        string ipReason;
        if (!isAttackerWinner)
        {
            if (wasDefenderCcDestroyed)
            {
                ipMultiplier = 0.2;
                ipReason = "from losing but killing defender's CC";
            }
            else
            {
                ipMultiplier = 0;
                ipReason = "from losing horribly";
            }
        }
        else
        {
            if (wasAttackerCcDestroyed)
            {
                ipReason = "from losing own CC";
                ipMultiplier = 0.5;
            }
            else
            {
                ipReason = "from winning flawlessly";
            }
        }
        if (!isLinked && effectiveShips < GlobalConst.DropshipsForFullWarpIPGain)
        {
            var newMult = effectiveShips/GlobalConst.DropshipsForFullWarpIPGain;
            ipMultiplier *= newMult ;
            ipReason = ipReason + string.Format(" and reduced to {0}% because only {1} of {2} ships needed for warp attack got past defenses", (int)(newMult*100.0), (int)effectiveShips, GlobalConst.DropshipsForFullWarpIPGain);
        }


        influence = (influence + shipBonus + techBonus) * ipMultiplier + defenseBonus;
        if (influence < 0) influence = 0;
        influence = Math.Floor(influence * 100) / 100;

        // main winner influence 
        PlanetFaction entry = planet.PlanetFactions.FirstOrDefault(x => x.Faction == attacker);
        if (entry == null)
        {
            entry = new PlanetFaction { Faction = attacker, Planet = planet, };
            planet.PlanetFactions.Add(entry);
        }

        entry.Influence += influence;


        // clamping of influence
        // gained over 100, sole owner
        if (entry.Influence >= 100)
        {
            entry.Influence = 100;
            foreach (var pf in planet.PlanetFactions.Where(x => x.Faction != attacker)) pf.Influence = 0;
        }
        else
        {
            var sumOthers = planet.PlanetFactions.Where(x => x.Faction != attacker).Sum(x => (double?)x.Influence) ?? 0;
            if (sumOthers + entry.Influence > 100)
            {
                var excess = sumOthers + entry.Influence - 100;
                foreach (var pf in planet.PlanetFactions.Where(x => x.Faction != attacker)) pf.Influence -= pf.Influence / sumOthers * excess;
            }
        }
        try
        {
            influenceReport = String.Format("{0} gained {1} influence ({2}% {3} of {4}{5}{6}{7})",
                attacker.Shortcut,
                influence,
                (int)(ipMultiplier * 100.0),
                ipReason,
                baseInfluence + " base",
                techBonus > 0 ? " +" + techBonus + " from techs" : "",
                shipBonus > 0 ? " +" + shipBonus + " from dropships" : "",
                defenseBonus != 0 ? " -" + -defenseBonus + " from defenses" : "");
        }
        catch (Exception ex)
        {
            Trace.TraceError(ex.ToString());
        }


        // distribute metal
        var attackersTotalMetal = Math.Floor(GlobalConst.PlanetWarsAttackerMetal);
        var defendersTotalMetal = Math.Floor(GlobalConst.PlanetWarsDefenderMetal);
        var attackerMetal = Math.Floor(attackersTotalMetal / attackers.Count);
        var defenderMetal = Math.Floor(defendersTotalMetal / defenders.Count);
        foreach (Account w in attackers)
        {
            w.ProduceMetal(attackerMetal);
            var ev = Global.CreateEvent("{0} gained {1} metal from battle {2}",
                                        w,
                                        attackerMetal,
                                        sb);
            db.Events.InsertOnSubmit(ev);
            text.AppendLine(ev.PlainText);
        }

        foreach (Account w in defenders)
        {
            w.ProduceMetal(defenderMetal);
            var ev = Global.CreateEvent("{0} gained {1} metal from battle {2}",
                                        w,
                                        defenderMetal,
                                        sb);

            db.Events.InsertOnSubmit(ev);
            text.AppendLine(ev.PlainText);
        }


        // remove dropships
        foreach (var pf in planet.PlanetFactions.Where(x => x.Faction == attacker)) pf.Dropships = 0;


        // add attack points
        foreach (Account acc in players)
        {
            int ap = acc.Faction == attacker ? GlobalConst.AttackPointsForVictory : GlobalConst.AttackPointsForDefeat;
            if (acc.Faction != null)
            {
                AccountPlanet apentry = planet.AccountPlanets.SingleOrDefault(x => x.AccountID == acc.AccountID);
                if (apentry == null)
                {
                    apentry = new AccountPlanet { AccountID = acc.AccountID, PlanetID = planet.PlanetID };
                    db.AccountPlanets.InsertOnSubmit(apentry);
                }

                apentry.AttackPoints += ap;
            }
            acc.PwAttackPoints += ap;
        }

        // paranoia!
        try
        {
            var mainEvent = Global.CreateEvent("{0} attacked {1} {2} in {3} and {4}. {5}",
                attacker,
                planet.Faction,
                planet,
                sb,
                isAttackerWinner ? "won. " : "lost. ",
                influenceReport
                );
            db.Events.InsertOnSubmit(mainEvent);
            text.AppendLine(mainEvent.PlainText);

        }
        catch (Exception ex)
        {
            Trace.TraceError(ex.ToString());
        }

        // destroy pw structures killed ingame
        if (!isAttackerWinner)
        {
            var handled = new List<string>();
            foreach (string line in extraData.Where(x => x.StartsWith("structurekilled")))
            {
                string[] data = line.Substring(16).Split(',');
                string unitName = data[0];
                if (handled.Contains(unitName)) continue;
                handled.Add(unitName);
                foreach (PlanetStructure s in planet.PlanetStructures.Where(x => x.StructureType.IngameUnitName == unitName))
                {
                    if (s.StructureType.IsIngameDestructible)
                    {
                        s.IsActive = false;
                        s.ActivatedOnTurn = gal.Turn + (int)(s.StructureType.TurnsToActivate * (GlobalConst.StructureIngameDisableTimeMult - 1));

                        var ev = Global.CreateEvent("{0} has been disabled on {1} planet {2}. {3}", s.StructureType.Name, planet.Faction, planet, sb);
                        db.Events.InsertOnSubmit(ev);
                        text.AppendLine(ev.PlainText);
                    }
                }
            }
        }
        else
        {
            // attacker won disable all
            foreach (var s in planet.PlanetStructures.Where(x => x.StructureType.IsIngameDestructible))
            {
                s.IsActive = false;
                s.ActivatedOnTurn = gal.Turn + (int)(s.StructureType.TurnsToActivate * (GlobalConst.StructureIngameDisableTimeMult - 1));
            }
            // destroy structures by battle (usually defenses)
            foreach (PlanetStructure s in planet.PlanetStructures.Where(x => x.StructureType.BattleDeletesThis).ToList()) planet.PlanetStructures.Remove(s);

            var ev = Global.CreateEvent("All structures have been disabled on {0} planet {1}. {2}", planet.Faction, planet, sb);
            db.Events.InsertOnSubmit(ev);
            text.AppendLine(ev.PlainText);
        }

        db.SaveChanges();

        gal.DecayInfluence();
        gal.SpreadInfluence();

        // process faction energies
        foreach (var fac in db.Factions.Where(x => !x.IsDeleted)) fac.ProcessEnergy(gal.Turn);

        // process production
        gal.ProcessProduction();


        // process treaties
        foreach (var tr in db.FactionTreaties.Where(x => x.TreatyState == TreatyState.Accepted || x.TreatyState == TreatyState.Suspended))
        {
            if (tr.ProcessTrade(false))
            {
                tr.TreatyState = TreatyState.Accepted;
                if (tr.TurnsTotal != null)
                {
                    tr.TurnsRemaining--;
                    if (tr.TurnsRemaining <= 0)
                    {
                        tr.TreatyState = TreatyState.Invalid;
                        db.FactionTreaties.DeleteOnSubmit(tr);
                    }
                }
            }
            else tr.TreatyState = TreatyState.Suspended;
        }

        // burn extra energy
        foreach (var fac in db.Factions.Where(x => !x.IsDeleted)) fac.ConvertExcessEnergyToMetal();


        int? oldOwner = planet.OwnerAccountID;
        gal.Turn++;
        db.SaveChanges();

        db = new ZkDataContext(); // is this needed - attempt to fix setplanetownersbeing buggy
        PlanetwarsController.SetPlanetOwners(db, sb);
        gal = db.Galaxies.Single(x => x.IsDefault);

        planet = gal.Planets.Single(x => x.Resource.InternalName == mapName);
        if (planet.OwnerAccountID != oldOwner && planet.OwnerAccountID != null)
        {
            text.AppendFormat("Congratulations!! Planet {0} was conquered by {1} !!  {3}/PlanetWars/Planet/{2}\n",
                planet.Name,
                planet.Account.Name,
                planet.PlanetID,
                GlobalConst.BaseSiteUrl);
        }

        try
        {

            // store history
            foreach (Planet p in gal.Planets)
            {
                db.PlanetOwnerHistories.InsertOnSubmit(new PlanetOwnerHistory
                {
                    PlanetID = p.PlanetID,
                    OwnerAccountID = p.OwnerAccountID,
                    OwnerClanID = p.OwnerAccountID != null ? p.Account.ClanID : null,
                    OwnerFactionID = p.OwnerFactionID,
                    Turn = gal.Turn
                });
            }

            db.SubmitChanges();
        }
        catch (Exception ex)
        {
            Trace.TraceError(ex.ToString());
            text.AppendLine("error saving history: " + ex);
        }

        //rotate map
        if (GlobalConst.RotatePWMaps)
        {
            db = new ZkDataContext();
            gal = db.Galaxies.Single(x => x.IsDefault);
            planet = gal.Planets.Single(x => x.Resource.InternalName == mapName);
            var mapList = db.Resources.Where(x => x.MapPlanetWarsIcon != null && x.Planets.Where(p => p.GalaxyID == gal.GalaxyID).Count() == 0 && x.FeaturedOrder != null
                                                  && x.ResourceID != planet.MapResourceID && x.MapWaterLevel == planet.Resource.MapWaterLevel).ToList();
            if (mapList.Count > 0)
            {
                int r = new Random().Next(mapList.Count);
                int resourceID = mapList[r].ResourceID;
                Resource newMap = db.Resources.Single(x => x.ResourceID == resourceID);
                text.AppendLine(String.Format("Map cycler - {0} maps found, selected map {1} to replace map {2}", mapList.Count, newMap.InternalName, planet.Resource.InternalName));
                planet.Resource = newMap;
                gal.IsDirty = true;
            }
            else
            {
                text.AppendLine("Map cycler - no maps found");
            }
            db.SaveChanges();
        }
    }
        public static MvcHtmlString PrintBattle(this HtmlHelper helper, SpringBattle battle, bool? isVictory = null) {
            var url = Global.UrlHelper();
            var icon = "";
            if (isVictory == true) icon = "battlewon.png";
            else if (isVictory == null) icon = "spec.png";
            else icon = "battlelost.png";

            icon = string.Format("<img src='/img/battles/{0}' class='vcenter' />", icon);

            if (battle.IsMission) icon += " <img src='/img/battles/mission.png' alt='Mission' class='vcenter' />";
            if (battle.HasBots) icon += " <img src='/img/battles/robot.png' alt='Bots' class='vcenter' />";

            if (battle.BattleType == "Multiplayer") icon += " <img src='/img/battles/multiplayer.png' alt='Multiplayer' class='vcenter' />";
            else if (battle.BattleType == "Singleplayer") icon += " <img src='/img/battles/singleplayer.png' alt='Singleplayer' class='vcenter' />";

            return
                new MvcHtmlString(string.Format("<span><a href='{0}'>{4} B{1}</a> {2} on {3}</span>",
                                                url.Action("Detail", "Battles", new { id = battle.SpringBattleID }),
                                                battle.SpringBattleID,
                                                battle.PlayerCount,
                                                PrintMap(helper, battle.ResourceByMapResourceID.InternalName),
                                                icon));
        }
        public static string SubmitSpringBattleResult(BattleContext context,
                                                      string password,
                                                      BattleResult result,
                                                      List<BattlePlayerResult> players,
                                                      List<string> extraData)
        {
            try
            {
                Account acc = AuthServiceClient.VerifyAccountPlain(context.AutohostName, password);
                if (acc == null) throw new Exception("Account name or password not valid");
                AutohostMode mode = context.GetMode();
                var db = new ZkDataContext();

                if (extraData == null) extraData = new List<string>();


                var sb = new SpringBattle
                         {
                             HostAccountID = acc.AccountID,
                             Duration = result.Duration,
                             EngineGameID = result.EngineBattleID,
                             MapResourceID = db.Resources.Single(x => x.InternalName == result.Map).ResourceID,
                             ModResourceID = db.Resources.Single(x => x.InternalName == result.Mod).ResourceID,
                             HasBots = result.IsBots,
                             IsMission = result.IsMission,
                             PlayerCount = players.Count(x => !x.IsSpectator),
                             StartTime = result.StartTime,
                             Title = result.Title,
                             ReplayFileName = result.ReplayName,
                             EngineVersion = result.EngineVersion,
                         };
                db.SpringBattles.InsertOnSubmit(sb);

                foreach (BattlePlayerResult p in players)
                {
                    sb.SpringBattlePlayers.Add(new SpringBattlePlayer
                                               {
                                                   AccountID = db.Accounts.First(x => x.AccountID == p.LobbyID).AccountID,
                                                   AllyNumber = p.AllyNumber,
                                                   CommanderType = p.CommanderType,
                                                   IsInVictoryTeam = p.IsVictoryTeam,
                                                   IsSpectator = p.IsSpectator,
                                                   LoseTime = p.LoseTime
                                               });
                }

                db.SubmitChanges();

                // awards
                foreach (string line in extraData.Where(x => x.StartsWith("award")))
                {
                    string[] partsSpace = line.Substring(6).Split(new[] { ' ' }, 3);
                    string name = partsSpace[0];
                    string awardType = partsSpace[1];
                    string awardText = partsSpace[2];

                    SpringBattlePlayer player = sb.SpringBattlePlayers.FirstOrDefault(x => x.Account.Name == name);
                    if (player != null)
                    {
                        db.AccountBattleAwards.InsertOnSubmit(new AccountBattleAward
                                                              {
                                                                  AccountID = player.AccountID,
                                                                  SpringBattleID = sb.SpringBattleID,
                                                                  AwardKey = awardType,
                                                                  AwardDescription = awardText
                                                              });
                    }
                }

                var text = new StringBuilder();
                bool isPlanetwars = false;
                if (mode == AutohostMode.Planetwars && sb.SpringBattlePlayers.Count(x => !x.IsSpectator) >= 2 && sb.Duration >= GlobalConst.MinDurationForPlanetwars)
                {
                    // test that factions are not intermingled (each faction only has one ally number) - if they are it wasnt actually PW balanced
                    if (
                        sb.SpringBattlePlayers.Where(x => !x.IsSpectator && x.Account.Faction != null)
                          .GroupBy(x => x.Account.Faction)
                          .All(grp => grp.Select(x => x.AllyNumber).Distinct().Count() < 2))
                    {
                        isPlanetwars = true;

                        
                        List<int> winnerTeams =
    sb.SpringBattlePlayers.Where(x => x.IsInVictoryTeam && !x.IsSpectator).Select(x => x.AllyNumber).Distinct().ToList();
                        int? winNum = null;
                        if (winnerTeams.Count == 1)
                        {
                            winNum = winnerTeams[0];
                            if (winNum > 1)
                            {
                                winNum = null;
                                text.AppendLine("ERROR: Invalid winner");
                            } 
                        }

                        PlanetWarsTurnHandler.EndTurn(result.Map, extraData, db, winNum, sb.SpringBattlePlayers.Where(x => !x.IsSpectator).Select(x => x.Account).ToList(), text, sb, sb.SpringBattlePlayers.Where(x => !x.IsSpectator && x.AllyNumber == 0).Select(x => x.Account).ToList());

                        Global.PlanetWarsMatchMaker.RemoveFromRunningBattles(context.AutohostName);
                    }
                    else
                    {
                        text.AppendLine("Battle wasn't PlanetWars balanced, it counts as a normal team game only");
                    }

                }

                bool noElo = (extraData.FirstOrDefault(x => x.StartsWith("noElo", true, System.Globalization.CultureInfo.CurrentCulture)) != null);
                try
                {
                    db.SubmitChanges();
                }
                catch (System.Data.Linq.DuplicateKeyException ex)
                {
                    Trace.TraceError(ex.ToString());
                }

                Dictionary<int, int> orgLevels = sb.SpringBattlePlayers.Select(x => x.Account).ToDictionary(x => x.AccountID, x => x.Level);

                sb.CalculateAllElo(noElo, isPlanetwars);
                foreach (var u in sb.SpringBattlePlayers.Where(x => !x.IsSpectator)) u.Account.CheckLevelUp();
                
                db.SubmitAndMergeChanges();

                try
                {
                    foreach (Account a in sb.SpringBattlePlayers.Where(x => !x.IsSpectator).Select(x => x.Account)) Global.Server.PublishAccountUpdate(a);
                }
                catch (Exception ex)
                {
                    Trace.TraceError("error updating extension data: {0}", ex);
                }

                foreach (Account account in sb.SpringBattlePlayers.Select(x => x.Account))
                {
                    if (account.Level > orgLevels[account.AccountID])
                    {
                        try
                        {
                            string message =
                                string.Format("Congratulations {0}! You just leveled up to level {1}. {3}/Users/Detail/{2}",
                                              account.Name,
                                              account.Level,
                                              account.AccountID,
                                              GlobalConst.BaseSiteUrl);
                            //text.AppendLine(message);
                            Global.Server.GhostPm(account.Name, message);
                        }
                        catch (Exception ex)
                        {
                            Trace.TraceError("Error sending level up lobby message: {0}", ex);
                        }
                    }
                }

                text.AppendLine(string.Format("BATTLE DETAILS AND REPLAY ----> {1}/Battles/Detail/{0} <-----", sb.SpringBattleID, GlobalConst.BaseSiteUrl));

                /*
                // create debriefing room, join players there and output message
                string channelName = "B" + sb.SpringBattleID;
                var joinplayers = new List<string>();
                joinplayers.AddRange(context.Players.Select(x => x.Name)); // add those who were there at start
                joinplayers.AddRange(sb.SpringBattlePlayers.Select(x => x.Account.Name)); // add those who played
                Battle bat = Global.Server.Battles.Values.FirstOrDefault(x => x.Founder.Name == context.AutohostName); // add those in lobby atm


                var conf = context.GetConfig();
                if (bat != null && (conf == null || conf.MinToJuggle == null)) // if not qm room do not join those who are in battle
                {
                    List<string> inbatPlayers = bat.Users.Keys.ToList();
                    joinplayers.RemoveAll(x => inbatPlayers.Contains(x));
                }
                foreach (string jp in joinplayers.Distinct().Where(x => x != context.AutohostName)) tas.ForceJoinChannel(jp, channelName);
                tas.JoinChannel(channelName); // join nightwatch and say it
                tas.Say(SayPlace.Channel, channelName, text.ToString(), true);
                tas.LeaveChannel(channelName);*/

                //text.Append(string.Format("Debriefing in #{0} - zk://chat/channel/{0}  ", channelName));
                return text.ToString();
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.ToString());
                return ex.ToString();
            }
        }
        private static void ProcessPlanetWars(Spring.SpringBattleContext result, ZkLobbyServer.ZkLobbyServer server, SpringBattle sb, ZkDataContext db, StringBuilder text)
        {
            if (result.LobbyStartContext.Mode != AutohostMode.Planetwars || sb.PlayerCount < 2 || sb.Duration >= GlobalConst.MinDurationForPlanetwars) return;

            List<int> winnerTeams = sb.SpringBattlePlayers.Where(x => x.IsInVictoryTeam && !x.IsSpectator).Select(x => x.AllyNumber).Distinct().ToList();
            int? winNum = null;
            if (winnerTeams.Count == 1)
            {
                winNum = winnerTeams[0];
                if (winNum > 1) winNum = null;
            }

            PlanetWarsTurnHandler.EndTurn(result.LobbyStartContext.Map,
                result.OutputExtras,
                db,
                winNum,
                sb.SpringBattlePlayers.Where(x => !x.IsSpectator).Select(x => x.Account).ToList(),
                text,
                sb,
                sb.SpringBattlePlayers.Where(x => !x.IsSpectator && x.AllyNumber == 0).Select(x => x.Account).ToList(),
                server.PlanetWarsEventCreator);

            // TODO HACK Global.PlanetWarsMatchMaker.RemoveFromRunningBattles(context.AutohostName);
        }
        private static void ProcessExtras(List<string> extras, SpringBattle sb, ZkDataContext db)
        {
            // awards
            foreach (string line in extras.Where(x => x?.StartsWith("award") == true))
            {
                string[] partsSpace = line.Substring(6).Split(new[] { ' ' }, 3);
                string name = partsSpace[0];
                string awardType = partsSpace[1];
                string awardText = partsSpace[2];

                // prevent hax: tourney cups and event coins are never given automatically
                if (awardType != null && (awardType == "gold" || awardType == "silver" || awardType == "bronze")) continue;
                if (awardType != null && (awardType == "goldcoin" || awardType == "silvercoin" || awardType == "bronzecoin")) continue;

                SpringBattlePlayer player = sb.SpringBattlePlayers.FirstOrDefault(x => x.Account?.Name == name);
                if (player != null)
                {
                    db.AccountBattleAwards.InsertOnSubmit(new AccountBattleAward
                    {
                        AccountID = player.AccountID,
                        SpringBattleID = sb.SpringBattleID,
                        AwardKey = awardType,
                        AwardDescription = awardText
                    });
                }
            }

            // chatlogs
            foreach (string line in extras.Where(x => x?.StartsWith("CHATLOG") == true))
            {
                string[] partsSpace = line.Substring(8).Split(new[] { ' ' }, 2);
                string name = partsSpace[0];
                string chatlog = partsSpace[1];

                SpringBattlePlayer player = sb.SpringBattlePlayers.FirstOrDefault(x => x.Account?.Name == name);
                if (player != null)
                {
                    db.LobbyChatHistories.InsertOnSubmit(new LobbyChatHistory
                    {
                        IsEmote = false,
                        SayPlace = SayPlace.Game,
                        User = name,
                        Ring = false,
                        Target = "B" + sb.SpringBattleID,
                        Text = chatlog,
                        Time = DateTime.UtcNow
                    });
                }
            }
        }
        private static SpringBattle SaveSpringBattle(Spring.SpringBattleContext result, ZkDataContext db)
        {
            var sb = new SpringBattle
            {
                HostAccountID = Account.AccountByName(db, result.LobbyStartContext.FounderName)?.AccountID,
                Mode = result.LobbyStartContext.Mode,
                Duration = result.Duration,
                EngineGameID = result.EngineBattleID,
                MapResourceID = db.Resources.Single(x => x.InternalName == result.LobbyStartContext.Map).ResourceID,
                ModResourceID = db.Resources.Single(x => x.InternalName == result.LobbyStartContext.Mod).ResourceID,
                HasBots = result.LobbyStartContext.Bots.Any(),
                IsMission = result.LobbyStartContext.IsMission,
                PlayerCount = result.ActualPlayers.Count(x => !x.IsSpectator),
                StartTime = result.StartTime,
                Title = result.LobbyStartContext.Title,
                ReplayFileName = result.ReplayName,
                EngineVersion = result.LobbyStartContext.EngineVersion,
                IsMatchMaker = result.LobbyStartContext.IsMatchMakerGame
            };
            db.SpringBattles.InsertOnSubmit(sb);

            foreach (BattlePlayerResult p in result.ActualPlayers)
            {
                var account = Account.AccountByName(db, p.Name);
                if (account != null)
                {
                    sb.SpringBattlePlayers.Add(new SpringBattlePlayer
                    {
                        Account = account,
                        AccountID = account.AccountID,
                        AllyNumber = p.AllyNumber,
                        IsInVictoryTeam = p.IsVictoryTeam,
                        IsSpectator = p.IsSpectator,
                        LoseTime = p.LoseTime
                    });
                }
            }

            db.SaveChanges();

            return db.SpringBattles.FirstOrDefault(x => x.SpringBattleID == sb.SpringBattleID); // reselect from db to get proper lazy proxies
        }
        private static void ProcessElos(SpringBattleContext result, ZkLobbyServer.ZkLobbyServer server, ZkDataContext db, SpringBattle sb)
        {
            bool noElo = result.OutputExtras.Any(x => x?.StartsWith("noElo", true, System.Globalization.CultureInfo.CurrentCulture) == true);

            sb.CalculateAllElo(noElo);
            foreach (var u in sb.SpringBattlePlayers.Where(x => !x.IsSpectator)) u.Account.CheckLevelUp();

            db.SaveChanges();

            try
            {
                foreach (Account a in sb.SpringBattlePlayers.Where(x => !x.IsSpectator).Select(x => x.Account)) server.PublishAccountUpdate(a);
            }
            catch (Exception ex)
            {
                Trace.TraceError("error updating extension data: {0}", ex);
            }
        }