public static Event ParseJournalEntry(string line)
        {
            try
            {
                Match match = JsonRegex.Match(line);
                if (match.Success)
                {
                    IDictionary<string, object> data = Deserializtion.DeserializeData(line);

                    // Every event has a timestamp field
                    DateTime timestamp = DateTime.Now;
                    if (data.ContainsKey("timestamp"))
                    {
                        if (data["timestamp"] is DateTime)
                        {
                            timestamp = ((DateTime)data["timestamp"]).ToUniversalTime();
                        }
                        else
                        {
                            timestamp = DateTime.Parse((string)data["timestamp"]).ToUniversalTime();
                        }
                    }
                    else
                    {
                        Logging.Warn("Event without timestamp; using current time");
                    }

                    // Every event has an event field
                    if (!data.ContainsKey("event"))
                    {
                        Logging.Warn("Event without event field!");
                        return null;
                    }

                    bool handled = false;

                    Event journalEvent = null;
                    string edType = (string)data["event"];
                    switch (edType)
                    {
                        case "Docked":
                            {
                                object val;
                                data.TryGetValue("StarSystem", out val);
                                string systemName = (string)val;
                                data.TryGetValue("StationName", out val);
                                string stationName = (string)val;
                                data.TryGetValue("Allegiance", out val);
                                // FD sends "" rather than null; fix that here
                                if (((string)val) == "") { val = null; }
                                Superpower allegiance = Superpower.From((string)val);
                                data.TryGetValue("Faction", out val);
                                string faction = (string)val;
                                // Might be a superpower...
                                Superpower superpowerFaction = Superpower.From(faction);
                                faction = superpowerFaction != null ? superpowerFaction.name : faction;
                                data.TryGetValue("FactionState", out val);
                                State factionState = State.FromEDName((string)val);
                                data.TryGetValue("Economy", out val);
                                Economy economy = Economy.FromEDName((string)val);
                                data.TryGetValue("Government", out val);
                                Government government = Government.FromEDName((string)val);
                                data.TryGetValue("Security", out val);
                                SecurityLevel securityLevel = SecurityLevel.FromEDName((string)val);
                                journalEvent = new DockedEvent(timestamp, systemName, stationName, allegiance, faction, factionState, economy, government, securityLevel);
                            }
                            handled = true;
                            break;
                        case "Undocked":
                            {
                                object val;
                                data.TryGetValue("StationName", out val);
                                string stationName = (string)val;
                                journalEvent = new UndockedEvent(timestamp, stationName);
                            }
                            handled = true;
                            break;
                        case "Touchdown":
                            {
                                object val;
                                data.TryGetValue("Latitude", out val);
                                decimal latitude = (decimal)(double)val;
                                data.TryGetValue("Longitude", out val);
                                decimal longitude = (decimal)(double)val;
                                journalEvent = new TouchdownEvent(timestamp, longitude, latitude);
                            }
                            handled = true;
                            break;
                        case "Liftoff":
                            {
                                object val;
                                data.TryGetValue("Latitude", out val);
                                decimal latitude = (decimal)(double)val;
                                data.TryGetValue("Longitude", out val);
                                decimal longitude = (decimal)(double)val;
                                journalEvent = new LiftoffEvent(timestamp, longitude, latitude);
                            }
                            handled = true;
                            break;
                        case "SupercruiseEntry":
                            {
                                object val;
                                data.TryGetValue("StarSystem", out val);
                                string system = (string)val;
                                journalEvent = new EnteredSupercruiseEvent(timestamp, system);
                            }
                            handled = true;
                            break;
                        case "SupercruiseExit":
                            {
                                object val;
                                data.TryGetValue("StarSystem", out val);
                                string system = (string)val;
                                data.TryGetValue("Body", out val);
                                string body = (string)val;
                                data.TryGetValue("BodyType", out val);
                                string bodyType = (string)val;
                                journalEvent = new EnteredNormalSpaceEvent(timestamp, system, body, bodyType);
                            }
                            handled = true;
                            break;
                        case "FSDJump":
                            {
                                object val;

                                data.TryGetValue("StarSystem", out val);
                                string systemName = (string)val;
                                data.TryGetValue("StarPos", out val);
                                List<object> starPos = (List<object>)val;
                                decimal x = Math.Round((decimal)((double)starPos[0]) * 32) / (decimal)32.0;
                                decimal y = Math.Round((decimal)((double)starPos[1]) * 32) / (decimal)32.0;
                                decimal z = Math.Round((decimal)((double)starPos[2]) * 32) / (decimal)32.0;

                                data.TryGetValue("FuelUsed", out val);
                                decimal fuelUsed = (decimal)(double)val;

                                data.TryGetValue("FuelLevel", out val);
                                decimal fuelRemaining = (decimal)(double)val;

                                data.TryGetValue("Allegiance", out val);
                                // FD sends "" rather than null; fix that here
                                if (((string)val) == "") { val = null; }
                                Superpower allegiance = Superpower.From((string)val);
                                data.TryGetValue("Faction", out val);
                                string faction = (string)val;
                                // Might be a superpower...
                                Superpower superpowerFaction = Superpower.From(faction);
                                faction = superpowerFaction != null ? superpowerFaction.name : faction;
                                data.TryGetValue("FactionState", out val);
                                State factionState = State.FromEDName((string)val);
                                data.TryGetValue("Economy", out val);
                                Economy economy = Economy.FromEDName((string)val);
                                data.TryGetValue("Government", out val);
                                Government government = Government.FromEDName((string)val);
                                data.TryGetValue("Security", out val);
                                SecurityLevel security = SecurityLevel.FromEDName((string)val);

                                journalEvent = new JumpedEvent(timestamp, systemName, x, y, z, fuelUsed, fuelRemaining, allegiance, faction, factionState, economy, government, security);
                            }
                            handled = true;
                            break;
                        case "Location":
                            {
                                object val;

                                data.TryGetValue("StarSystem", out val);
                                string systemName = (string)val;

                                data.TryGetValue("StarPos", out val);
                                List<object> starPos = (List<object>)val;
                                decimal x = Math.Round((decimal)((double)starPos[0]) * 32) / (decimal)32.0;
                                decimal y = Math.Round((decimal)((double)starPos[1]) * 32) / (decimal)32.0;
                                decimal z = Math.Round((decimal)((double)starPos[2]) * 32) / (decimal)32.0;

                                data.TryGetValue("Body", out val);
                                string body = (string)val;
                                data.TryGetValue("BodyType", out val);
                                string bodyType = (string)val;
                                data.TryGetValue("Docked", out val);
                                bool docked = (bool)val;
                                data.TryGetValue("Allegiance", out val);
                                // FD sends "" rather than null; fix that here
                                if (((string)val) == "") { val = null; }
                                Superpower allegiance = Superpower.From((string)val);
                                data.TryGetValue("Faction", out val);
                                string faction = (string)val;
                                // Might be a superpower...
                                Superpower superpowerFaction = Superpower.From(faction);
                                faction = superpowerFaction != null ? superpowerFaction.name : faction;
                                data.TryGetValue("FactionState", out val);
                                State factionState = State.FromEDName((string)val);
                                data.TryGetValue("Economy", out val);
                                Economy economy = Economy.FromEDName((string)val);
                                data.TryGetValue("Government", out val);
                                Government government = Government.FromEDName((string)val);
                                data.TryGetValue("Security", out val);
                                SecurityLevel security = SecurityLevel.FromEDName((string)val);


                                journalEvent = new LocationEvent(timestamp, systemName, x, y, z, body, bodyType, docked, allegiance, faction, factionState, economy, government, security);
                            }
                            handled = true;
                            break;
                        case "Bounty":
                            {
                                object val;

                                data.TryGetValue("Target", out val);
                                string target = (string)val;

                                data.TryGetValue("VictimFaction", out val);
                                string victimFaction = (string)val;
                                // Might be a superpower...
                                Superpower superpowerFaction = Superpower.From(victimFaction);
                                victimFaction = superpowerFaction != null ? superpowerFaction.name : victimFaction;

                                long reward;
                                List<Reward> rewards = new List<Reward>();

                                if (data.ContainsKey("Reward"))
                                {
                                    // Old-style
                                    data.TryGetValue("Reward", out val);
                                    reward = (long)val;
                                    if (reward == 0)
                                    {
                                        // 0-credit reward; ignore
                                        break;
                                    }
                                    data.TryGetValue("Faction", out val);
                                    string factionName = (string)val;
                                    // Might be a superpower...
                                    superpowerFaction = Superpower.From(factionName);
                                    factionName = superpowerFaction != null ? superpowerFaction.name : factionName;
                                    rewards.Add(new Reward(factionName, reward));
                                }
                                else
                                {
                                    data.TryGetValue("TotalReward", out val);
                                    reward = (long)val;

                                    // Obtain list of rewards
                                    data.TryGetValue("Rewards", out val);
                                    List<object> rewardsData = (List<object>)val;
                                    if (rewardsData != null)
                                    {
                                        foreach (Dictionary<string, object> rewardData in rewardsData)
                                        {
                                            rewardData.TryGetValue("Faction", out val);
                                            string factionName = (string)val;
                                            // Might be a superpower...
                                            superpowerFaction = Superpower.From(factionName);
                                            factionName = superpowerFaction != null ? superpowerFaction.name : factionName;

                                            rewardData.TryGetValue("Reward", out val);
                                            long factionReward = (long)val;

                                            rewards.Add(new Reward(factionName, factionReward));
                                        }
                                    }
                                }

                                journalEvent = new BountyAwardedEvent(timestamp, target, victimFaction, reward, rewards);
                            }
                            handled = true;
                            break;
                        case "CapShipBond":
                        case "FactionKillBond":
                            {
                                object val;
                                data.TryGetValue("Faction", out val);
                                string awardingFaction = (string)val;
                                // Might be a superpower...
                                Superpower superpowerFaction = Superpower.From(awardingFaction);
                                awardingFaction = superpowerFaction != null ? superpowerFaction.name : awardingFaction;
                                data.TryGetValue("Reward", out val);
                                long reward = (long)val;
                                data.TryGetValue("VictimFaction", out val);
                                string victimFaction = (string)val;
                                journalEvent = new BondAwardedEvent(timestamp, awardingFaction, victimFaction, reward);
                            }
                            handled = true;
                            break;
                        case "CommitCrime":
                            {
                                object val;
                                data.TryGetValue("CrimeType", out val);
                                string crimetype = (string)val;
                                data.TryGetValue("Faction", out val);
                                string faction = (string)val;
                                // Might be a superpower...
                                Superpower superpowerFaction = Superpower.From(faction);
                                faction = superpowerFaction != null ? superpowerFaction.name : faction;
                                data.TryGetValue("Victim", out val);
                                string victim = (string)val;
                                // Might be a fine or a bounty
                                if (data.ContainsKey("Fine"))
                                {
                                    data.TryGetValue("Fine", out val);
                                    long fine = (long)val;
                                    journalEvent = new FineIncurredEvent(timestamp, crimetype, faction, victim, fine);
                                }
                                else
                                {
                                    data.TryGetValue("Bounty", out val);
                                    long bounty = (long)val;
                                    journalEvent = new BountyIncurredEvent(timestamp, crimetype, faction, victim, bounty);
                                }
                            }
                            handled = true;
                            break;
                        case "Promotion":
                            {
                                object val;
                                if (data.ContainsKey("Combat"))
                                {
                                    data.TryGetValue("Combat", out val);
                                    CombatRating rating = CombatRating.FromRank((int)(long)val);
                                    journalEvent = new CombatPromotionEvent(timestamp, rating);
                                    handled = true;
                                }
                                else if (data.ContainsKey("Trade"))
                                {
                                    data.TryGetValue("Trade", out val);
                                    TradeRating rating = TradeRating.FromRank((int)(long)val);
                                    journalEvent = new TradePromotionEvent(timestamp, rating);
                                    handled = true;
                                }
                                else if (data.ContainsKey("Explore"))
                                {
                                    data.TryGetValue("Explore", out val);
                                    ExplorationRating rating = ExplorationRating.FromRank((int)(long)val);
                                    journalEvent = new ExplorationPromotionEvent(timestamp, rating);
                                    handled = true;
                                }
                            }
                            break;
                        case "CollectCargo":
                            {
                                object val;
                                data.TryGetValue("Type", out val);
                                string commodityName = (string)val;
                                Commodity commodity = CommodityDefinitions.FromName(commodityName);
                                if (commodity == null)
                                {
                                    Logging.Error("Failed to map collectcargo type " + commodityName + " to commodity");
                                }
                                data.TryGetValue("Stolen", out val);
                                bool stolen = (bool)val;
                                journalEvent = new CommodityCollectedEvent(timestamp, commodity, stolen);
                                handled = true;
                            }
                            handled = true;
                            break;
                        case "EjectCargo":
                            {
                                object val;
                                data.TryGetValue("Type", out val);
                                string commodityName = (string)val;
                                Commodity commodity = CommodityDefinitions.FromName(commodityName);
                                if (commodity == null)
                                {
                                    Logging.Error("Failed to map ejectcargo type " + commodityName + " to commodity");
                                }
                                string cargo = (string)val;
                                data.TryGetValue("Count", out val);
                                int amount = (int)(long)val;
                                data.TryGetValue("Abandoned", out val);
                                bool abandoned = (bool)val;
                                journalEvent = new CommodityEjectedEvent(timestamp, commodity, amount, abandoned);
                                handled = true;
                            }
                            handled = true;
                            break;
                        case "CockpitBreached":
                            journalEvent = new CockpitBreachedEvent(timestamp);
                            handled = true;
                            break;
                        case "Scan":
                            {
                                object val;
                                // Common items
                                data.TryGetValue("BodyName", out val);
                                string name = (string)val;

                                data.TryGetValue("DistanceFromArrivalLS", out val);
                                decimal distancefromarrival = (decimal)(double)val;

                                data.TryGetValue("Radius", out val);
                                decimal radius = (decimal)(double)val;

                                data.TryGetValue("OrbitalPeriod", out val);
                                decimal? orbitalperiod = (decimal?)(double?)val;

                                data.TryGetValue("RotationPeriod", out val);
                                decimal rotationperiod = (decimal)(double)val;

                                data.TryGetValue("SemiMajorAxis", out val);
                                decimal? semimajoraxis = (decimal?)(double?)val;

                                data.TryGetValue("Eccentricity", out val);
                                decimal? eccentricity = (decimal?)(double?)val;

                                data.TryGetValue("OrbitalInclination", out val);
                                decimal? orbitalinclination = (decimal?)(double?)val;

                                data.TryGetValue("Periapsis", out val);
                                decimal? periapsis = (decimal?)(double?)val;

                                data.TryGetValue("Rings", out val);
                                List<object> ringsData = (List<object>)val;
                                List<Ring> rings = new List<Ring>();
                                if (ringsData != null)
                                {
                                    foreach (Dictionary<string, object> ringData in ringsData)
                                    {
                                        ringData.TryGetValue("Name", out val);
                                        string ringName = (string)val;

                                        ringData.TryGetValue("RingClass", out val);
                                        Composition ringComposition = Composition.FromEDName((string)val);

                                        ringData.TryGetValue("MassMT", out val);
                                        decimal ringMass = (decimal)(double)val;

                                        ringData.TryGetValue("InnerRad", out val);
                                        decimal ringInnerRadius = (decimal)(double)val;

                                        ringData.TryGetValue("OuterRad", out val);
                                        decimal ringOuterRadius = (decimal)(double)val;

                                        rings.Add(new Ring(ringName, ringComposition, ringMass, ringInnerRadius, ringOuterRadius));
                                    }
                                }


                                if (data.ContainsKey("StarType"))
                                {
                                    // Star
                                    data.TryGetValue("StarType", out val);
                                    string starType = (string)val;

                                    data.TryGetValue("StellarMass", out val);
                                    decimal stellarMass = (decimal)(double)val;

                                    data.TryGetValue("AbsoluteMagnitude", out val);
                                    decimal absoluteMagnitude = (decimal)(double)val;

                                    data.TryGetValue("Age_MY", out val);
                                    long age = (long)val * 1000000;

                                    data.TryGetValue("SurfaceTemperature", out val);
                                    decimal temperature = (decimal)(double)val;

                                    journalEvent = new StarScannedEvent(timestamp, name, starType, stellarMass, radius, absoluteMagnitude, age, temperature, distancefromarrival, orbitalperiod, rotationperiod, semimajoraxis, eccentricity, orbitalinclination, periapsis, rings);
                                    handled = true;
                                }
                                else
                                {
                                    // Body
                                    data.TryGetValue("TidalLock", out val);
                                    bool tidallyLocked = (bool)val;

                                    data.TryGetValue("PlanetClass", out val);
                                    string bodyClass = (string)val;

                                    // MKW: Gravity in the Journal is in m/s; must convert it to G
                                    data.TryGetValue("SurfaceGravity", out val);
                                    decimal gravity = Body.ms2g((decimal)(double)val);

                                    data.TryGetValue("SurfaceTemperature", out val);
                                    decimal temperature = (decimal)(double)val;

                                    data.TryGetValue("SurfacePressure", out val);
                                    decimal pressure = (decimal)(double)val;

                                    data.TryGetValue("Landable", out val);
                                    bool landable = (bool)val;

                                    data.TryGetValue("Materials", out val);
                                    IDictionary<string, object> materialsData = (IDictionary<string, object>)val;
                                    List<MaterialPresence> materials = new List<MaterialPresence>();
                                    if (materialsData != null)
                                    {
                                        foreach (KeyValuePair<string, object> kv in materialsData)
                                        {
                                            Material material = Material.FromEDName(kv.Key);
                                            if (material != null)
                                            {
                                                materials.Add(new MaterialPresence(material, (decimal)(double)kv.Value));
                                            }
                                        }
                                    }

                                    data.TryGetValue("TerraformState", out val);
                                    string terraformState = (string)val;

                                    // Atmosphere
                                    data.TryGetValue("Atmosphere", out val);
                                    string atmosphere = (string)val;

                                    // Volcanism
                                    data.TryGetValue("Volcanism", out val);
                                    string volcanism = (string)val;

                                    journalEvent = new BodyScannedEvent(timestamp, name, bodyClass, gravity, temperature, pressure, tidallyLocked, landable, atmosphere, volcanism, distancefromarrival, (decimal)orbitalperiod, rotationperiod, semimajoraxis, eccentricity, orbitalinclination, periapsis, rings, materials, terraformState);
                                    handled = true;
                                }
                            }
                            break;
                        case "ShipyardBuy":
                            {
                                object val;
                                // We don't have a ship ID at this point so use the ship type
                                data.TryGetValue("ShipType", out val);
                                string shipModel = (string)val;
                                Ship ship = findShip(null, shipModel);

                                data.TryGetValue("ShipPrice", out val);
                                long price = (long)val;

                                data.TryGetValue("StoreShipID", out val);
                                int? storedShipId = (val == null ? (int?)null : (int)(long)val);
                                data.TryGetValue("StoreOldShip", out val);
                                string storedShipModel = (string)val;
                                Ship storedShip = storedShipId == null ? null : findShip(storedShipId, storedShipModel);

                                data.TryGetValue("SellShipID", out val);
                                int? soldShipId = (val == null ? (int?)null : (int)(long)val);
                                data.TryGetValue("SellOldShip", out val);
                                string soldShipModel = (string)val;
                                Ship soldShip = soldShipId == null ? null : findShip(soldShipId, soldShipModel);

                                data.TryGetValue("SellPrice", out val);
                                long? soldPrice = (long?)val;
                                journalEvent = new ShipPurchasedEvent(timestamp, ship, price, soldShip, soldPrice, storedShip);
                            }
                            handled = true;
                            break;
                        case "ShipyardNew":
                            {
                                object val;
                                data.TryGetValue("NewShipID", out val);
                                int shipId = (int)(long)val;
                                data.TryGetValue("ShipType", out val);
                                string shipModel = (string)val;
                                Ship ship = findShip(shipId, shipModel);

                                journalEvent = new ShipDeliveredEvent(timestamp, ship);
                            }
                            handled = true;
                            break;
                        case "ShipyardSell":
                            {
                                object val;
                                data.TryGetValue("SellShipID", out val);
                                int shipId = (int)(long)val;
                                data.TryGetValue("ShipType", out val);
                                string shipModel = (string)val;
                                Ship ship = findShip(shipId, shipModel);
                                data.TryGetValue("ShipPrice", out val);
                                long price = (long)val;
                                journalEvent = new ShipSoldEvent(timestamp, ship, price);
                            }
                            handled = true;
                            break;
                        case "ShipyardSwap":
                            {
                                object val;

                                data.TryGetValue("ShipID", out val);
                                int shipId = (int)(long)val;
                                data.TryGetValue("ShipType", out val);
                                string shipModel = (string)val;
                                Ship ship = findShip(shipId, shipModel);

                                data.TryGetValue("StoreShipID", out val);
                                int? storedShipId = (val == null ? (int?)null : (int)(long)val);
                                data.TryGetValue("StoreOldShip", out val);
                                string storedShipModel = (string)val;
                                Ship storedShip = storedShipId == null ? null : findShip(storedShipId, storedShipModel);

                                data.TryGetValue("SellShipID", out val);
                                int? soldShipId = (val == null ? (int?)null : (int)(long)val);
                                data.TryGetValue("SellOldShip", out val);
                                string soldShipModel = (string)val;
                                Ship soldShip = soldShipId == null ? null : findShip(soldShipId, soldShipModel);

                                journalEvent = new ShipSwappedEvent(timestamp, ship, soldShip, storedShip);
                            }
                            handled = true;
                            break;
                        case "ShipyardTransfer":
                            {
                                object val;
                                data.TryGetValue("ShipID", out val);
                                int shipId = (int)(long)val;
                                data.TryGetValue("ShipType", out val);
                                string shipModel = (string)val;
                                Ship ship = findShip(shipId, shipModel);

                                data.TryGetValue("System", out val);
                                string system = (string)val;

                                data.TryGetValue("Distance", out val);
                                decimal distance = (decimal)(double)val;

                                data.TryGetValue("TransferPrice", out val);
                                long price = (long)val;

                                journalEvent = new ShipTransferInitiatedEvent(timestamp, ship, system, distance, price);

                                handled = true;
                            }
                            break;
                        case "LaunchSRV":
                            {
                                object val;
                                data.TryGetValue("Loadout", out val);
                                string loadout = (string)val;
                                journalEvent = new SRVLaunchedEvent(timestamp, loadout);
                            }
                            handled = true;
                            break;
                        case "DockSRV":
                            journalEvent = new SRVDockedEvent(timestamp);
                            handled = true;
                            break;
                        case "LaunchFighter":
                            {
                                object val;
                                data.TryGetValue("Loadout", out val);
                                string loadout = (string)val;
                                data.TryGetValue("PlayerControlled", out val);
                                bool playerControlled = (bool)val;
                                journalEvent = new FighterLaunchedEvent(timestamp, loadout, playerControlled);
                            }
                            handled = true;
                            break;
                        case "DockFighter":
                            journalEvent = new FighterDockedEvent(timestamp);
                            handled = true;
                            break;
                        case "VehicleSwitch":
                            {
                                object val;
                                data.TryGetValue("To", out val);
                                string to = (string)val;
                                if (to == "Fighter")
                                {
                                    journalEvent = new ControllingFighterEvent(timestamp);
                                    handled = true;
                                }
                                else if (to == "Mothership")
                                {
                                    journalEvent = new ControllingShipEvent(timestamp);
                                    handled = true;
                                }
                            }
                            break;
                        case "Interdicted":
                            {
                                object val;
                                data.TryGetValue("Submitted", out val);
                                bool submitted = (bool)val;
                                data.TryGetValue("Interdictor", out val);
                                string interdictor = (string)val;
                                data.TryGetValue("IsPlayer", out val);
                                bool iscommander = (bool)val;
                                data.TryGetValue("CombatRank", out val);
                                CombatRating rating = (val == null ? null : CombatRating.FromRank((int)(long)val));
                                data.TryGetValue("Faction", out val);
                                string faction = (string)val;
                                data.TryGetValue("Power", out val);
                                string power = (string)val;

                                journalEvent = new ShipInterdictedEvent(timestamp, true, submitted, iscommander, interdictor, rating, faction, power);
                                handled = true;
                            }
                            break;
                        case "EscapeInterdiction":
                            {
                                object val;
                                data.TryGetValue("Interdictor", out val);
                                string interdictor = (string)val;
                                data.TryGetValue("IsPlayer", out val);
                                bool iscommander = (bool)val;

                                journalEvent = new ShipInterdictedEvent(timestamp, false, false, iscommander, interdictor, null, null, null);
                                handled = true;
                            }
                            break;
                        case "Interdiction":
                            {
                                object val;
                                data.TryGetValue("Success", out val);
                                bool success = (bool)val;
                                data.TryGetValue("Interdicted", out val);
                                string interdictee = (string)val;
                                data.TryGetValue("IsPlayer", out val);
                                bool iscommander = (bool)val;
                                data.TryGetValue("CombatRank", out val);
                                CombatRating rating = (val == null ? null : CombatRating.FromRank((int)(long)val));
                                data.TryGetValue("Faction", out val);
                                string faction = (string)val;
                                data.TryGetValue("Power", out val);
                                string power = (string)val;

                                journalEvent = new ShipInterdictionEvent(timestamp, success, iscommander, interdictee, rating, faction, power);
                                handled = true;
                            }
                            break;
                        case "PVPKill":
                            {
                                object val;
                                data.TryGetValue("Victim", out val);
                                string victim = (string)val;
                                data.TryGetValue("CombatRank", out val);
                                CombatRating rating = (val == null ? null : CombatRating.FromRank((int)(long)val));

                                journalEvent = new KilledEvent(timestamp, victim, rating);
                                handled = true;
                            }
                            break;
                        case "MaterialCollected":
                            {
                                object val;
                                data.TryGetValue("Name", out val);
                                Material material = Material.FromEDName((string)val);
                                data.TryGetValue("Count", out val);
                                int amount = (int)(long)val;
                                journalEvent = new MaterialCollectedEvent(timestamp, material, amount);
                                handled = true;
                            }
                            break;
                        case "MaterialDiscarded":
                            {
                                object val;
                                data.TryGetValue("Name", out val);
                                Material material = Material.FromEDName((string)val);
                                data.TryGetValue("Count", out val);
                                int amount = (int)(long)val;
                                journalEvent = new MaterialDiscardedEvent(timestamp, material, amount);
                                handled = true;
                            }
                            break;
                        case "MaterialDiscovered":
                            {
                                object val;
                                data.TryGetValue("Name", out val);
                                Material material = Material.FromEDName((string)val);
                                journalEvent = new MaterialDiscoveredEvent(timestamp, material);
                                handled = true;
                            }
                            break;
                        case "ScientificResearch":
                            {
                                object val;
                                data.TryGetValue("Name", out val);
                                Material material = Material.FromEDName((string)val);
                                data.TryGetValue("Count", out val);
                                int amount = (int)(long)val;
                                journalEvent = new MaterialDonatedEvent(timestamp, material, amount);
                                handled = true;
                            }
                            break;
                        case "ReceiveText":
                            {
                                object val;
                                data.TryGetValue("From", out val);
                                string from = (string)val;

                                data.TryGetValue("Channel", out val);
                                string channel = (string)val;

                                if (!(from.StartsWith("$cmdr") || from.StartsWith("&")))
                                {
                                    // For now we log everything that isn't commander speech
                                    Logging.Report("NPC speech", line);
                                }
                                else
                                {
                                    from = from.Replace("$cmdr_decorate:#name=", "Commander ").Replace(";", "").Replace("&", "Commander ");
                                    data.TryGetValue("Message", out val);
                                    string message = (string)val;
                                    journalEvent = new MessageReceivedEvent(timestamp, from, true, channel, message);
                                }
                            }
                            handled = true;
                            break;
                        case "SendText":
                            {
                                object val;
                                data.TryGetValue("To", out val);
                                string to = (string)val;
                                to = to.Replace("$cmdr_decorate:#name=", "Commander ").Replace(";", "").Replace("&", "Commander ");
                                data.TryGetValue("Message", out val);
                                string message = (string)val;
                                journalEvent = new MessageSentEvent(timestamp, to, message);
                            }
                            handled = true;
                            break;
                        case "DockingRequested":
                            {
                                object val;
                                data.TryGetValue("StationName", out val);
                                string stationName = (string)val;
                                journalEvent = new DockingRequestedEvent(timestamp, stationName);
                            }
                            handled = true;
                            break;
                        case "DockingGranted":
                            {
                                object val;
                                data.TryGetValue("StationName", out val);
                                string stationName = (string)val;
                                data.TryGetValue("LandingPad", out val);
                                int landingPad = (int)(long)val;
                                journalEvent = new DockingGrantedEvent(timestamp, stationName, landingPad);
                            }
                            handled = true;
                            break;
                        case "DockingDenied":
                            {
                                object val;
                                data.TryGetValue("StationName", out val);
                                string stationName = (string)val;
                                data.TryGetValue("Reason", out val);
                                string reason = (string)val;
                                journalEvent = new DockingDeniedEvent(timestamp, stationName, reason);
                            }
                            handled = true;
                            break;
                        case "DockingCancelled":
                            {
                                object val;
                                data.TryGetValue("StationName", out val);
                                string stationName = (string)val;
                                journalEvent = new DockingCancelledEvent(timestamp, stationName);
                            }
                            handled = true;
                            break;
                        case "DockingTimeout":
                            {
                                object val;
                                data.TryGetValue("StationName", out val);
                                string stationName = (string)val;
                                journalEvent = new DockingTimedOutEvent(timestamp, stationName);
                            }
                            handled = true;
                            break;
                        case "MiningRefined":
                            {
                                object val;
                                data.TryGetValue("Type", out val);
                                string commodityName = (string)val;

                                Commodity commodity = CommodityDefinitions.FromName(commodityName);
                                if (commodity == null)
                                {
                                    Logging.Error("Failed to map commodityrefined type " + commodityName + " to commodity");
                                }
                                journalEvent = new CommodityRefinedEvent(timestamp, commodity);
                            }
                            handled = true;
                            break;
                        case "HeatWarning":
                            journalEvent = new HeatWarningEvent(timestamp);
                            handled = true;
                            break;
                        case "HeatDamage":
                            journalEvent = new HeatDamageEvent(timestamp);
                            handled = true;
                            break;
                        case "HullDamage":
                            {
                                object val;
                                data.TryGetValue("Health", out val);
                                decimal health = sensibleHealth((decimal)(((double)val) * 100));
                                journalEvent = new HullDamagedEvent(timestamp, health);
                            }
                            handled = true;
                            break;
                        case "ShieldState":
                            {
                                object val;
                                data.TryGetValue("ShieldsUp", out val);
                                bool shieldsUp = (bool)val;
                                if (shieldsUp == true)
                                {
                                    journalEvent = new ShieldsUpEvent(timestamp);
                                }
                                else
                                {
                                    journalEvent = new ShieldsDownEvent(timestamp);
                                }
                                handled = true;
                                break;
                            }
                        case "SelfDestruct":
                            journalEvent = new SelfDestructEvent(timestamp);
                            handled = true;
                            break;
                        case "Died":
                            {
                                object val;

                                List<string> names = new List<string>();
                                List<string> ships = new List<string>();
                                List<CombatRating> ratings = new List<CombatRating>();

                                if (data.ContainsKey("KillerName"))
                                {
                                    // Single killer
                                    data.TryGetValue("KillerName", out val);
                                    names.Add((string)val);
                                    data.TryGetValue("KillerShip", out val);
                                    ships.Add((string)val);
                                    data.TryGetValue("KillerRank", out val);
                                    ratings.Add(CombatRating.FromEDName((string)val));
                                }
                                if (data.ContainsKey("killers"))
                                {
                                    // Multiple killers
                                    data.TryGetValue("Killers", out val);
                                    List<object> killers = (List<object>)val;
                                    foreach (IDictionary<string, object> killer in killers)
                                    {
                                        killer.TryGetValue("Name", out val);
                                        names.Add((string)val);
                                        killer.TryGetValue("Ship", out val);
                                        ships.Add((string)val);
                                        killer.TryGetValue("Rank", out val);
                                        ratings.Add(CombatRating.FromEDName((string)val));
                                    }
                                }
                                journalEvent = new DiedEvent(timestamp, names, ships, ratings);
                                handled = true;
                            }
                            break;
                        case "BuyExplorationData":
                            {
                                object val;
                                data.TryGetValue("System", out val);
                                string system = (string)val;
                                data.TryGetValue("Cost", out val);
                                long price = (long)val;
                                journalEvent = new ExplorationDataPurchasedEvent(timestamp, system, price);
                                handled = true;
                                break;
                            }
                        case "SellExplorationData":
                            {
                                object val;
                                data.TryGetValue("Systems", out val);
                                List<string> systems = ((List<object>)val).Cast<string>().ToList();
                                data.TryGetValue("Discovered", out val);
                                List<string> firsts = ((List<object>)val).Cast<string>().ToList();
                                data.TryGetValue("BaseValue", out val);
                                decimal reward = (long)val;
                                data.TryGetValue("Bonus", out val);
                                decimal bonus = (long)val;
                                journalEvent = new ExplorationDataSoldEvent(timestamp, systems, firsts, reward, bonus);
                                handled = true;
                                break;
                            }
                        case "USSDrop":
                            {
                                object val;
                                data.TryGetValue("USSType", out val);
                                string source = (string)val;
                                data.TryGetValue("USSThreat", out val);
                                int threat = (int)(long)val;
                                journalEvent = new EnteredSignalSourceEvent(timestamp, source, threat);
                            }
                            handled = true;
                            break;
                        case "MarketBuy":
                            {
                                object val;
                                data.TryGetValue("Type", out val);
                                string commodityName = (string)val;
                                Commodity commodity = CommodityDefinitions.FromName(commodityName);
                                if (commodity == null)
                                {
                                    Logging.Error("Failed to map marketbuy type " + commodityName + " to commodity");
                                }
                                data.TryGetValue("Count", out val);
                                int amount = (int)(long)val;
                                data.TryGetValue("BuyPrice", out val);
                                long price = (long)val;
                                journalEvent = new CommodityPurchasedEvent(timestamp, commodity, amount, price);
                                handled = true;
                                break;
                            }
                        case "MarketSell":
                            {
                                object val;
                                data.TryGetValue("Type", out val);
                                string commodityName = (string)val;
                                Commodity commodity = CommodityDefinitions.FromName(commodityName);
                                if (commodity == null)
                                {
                                    Logging.Error("Failed to map marketsell type " + commodityName + " to commodity");
                                }
                                data.TryGetValue("Count", out val);
                                int amount = (int)(long)val;
                                data.TryGetValue("SellPrice", out val);
                                long price = (long)val;
                                data.TryGetValue("AvgPricePaid", out val);
                                long buyPrice = (long)val;
                                // We don't care about buy price, we care about profit per unit
                                long profit = price - buyPrice;
                                data.TryGetValue("IllegalGoods", out val);
                                bool illegal = (val == null ? false : (bool)val);
                                data.TryGetValue("StolenGoods", out val);
                                bool stolen = (val == null ? false : (bool)val);
                                data.TryGetValue("BlackMarket", out val);
                                bool blackmarket = (val == null ? false : (bool)val);
                                journalEvent = new CommoditySoldEvent(timestamp, commodity, amount, price, profit, illegal, stolen, blackmarket);
                                handled = true;
                                break;
                            }
                        case "LoadGame":
                            {
                                object val;
                                data.TryGetValue("Commander", out val);
                                string commander = (string)val;
                                data.TryGetValue("ShipID", out val);
                                int? shipId = (int?)(long?)val;
                                data.TryGetValue("Ship", out val);
                                string shipModel = (string)val;
                                Ship ship = findShip(shipId, shipModel);
                                data.TryGetValue("GameMode", out val);
                                GameMode mode = GameMode.FromEDName((string)val);
                                data.TryGetValue("Group", out val);
                                string group = (string)val;
                                data.TryGetValue("Credits", out val);
                                decimal credits = (long)val;
                                journalEvent = new CommanderContinuedEvent(timestamp, commander, ship, mode, group, credits);
                                handled = true;
                                break;
                            }
                        case "CrewHire":
                            {
                                object val;
                                data.TryGetValue("Name", out val);
                                string name = (string)val;
                                data.TryGetValue("Faction", out val);
                                string faction = (string)val;
                                // Might be a superpower...
                                Superpower superpowerFaction = Superpower.From(faction);
                                faction = superpowerFaction != null ? superpowerFaction.name : faction;
                                data.TryGetValue("Cost", out val);
                                long price = (long)val;
                                data.TryGetValue("CombatRank", out val);
                                CombatRating rating = CombatRating.FromRank((int)(long)val);
                                journalEvent = new CrewHiredEvent(timestamp, name, faction, price, rating);
                                handled = true;
                                break;
                            }
                        case "CrewFire":
                            {
                                object val;
                                data.TryGetValue("Name", out val);
                                string name = (string)val;
                                journalEvent = new CrewFiredEvent(timestamp, name);
                                handled = true;
                                break;
                            }
                        case "CrewAssign":
                            {
                                object val;
                                data.TryGetValue("Name", out val);
                                string name = (string)val;
                                data.TryGetValue("Role", out val);
                                string role = (string)val;
                                journalEvent = new CrewAssignedEvent(timestamp, name, role);
                                handled = true;
                                break;
                            }
                        case "BuyAmmo":
                            {
                                object val;
                                data.TryGetValue("Cost", out val);
                                long price = (long)val;
                                journalEvent = new ShipRestockedEvent(timestamp, price);
                                handled = true;
                                break;
                            }
                        case "BuyDrones":
                            {
                                object val;
                                data.TryGetValue("Count", out val);
                                int amount = (int)(long)val;
                                data.TryGetValue("BuyPrice", out val);
                                long price = (long)val;
                                journalEvent = new LimpetPurchasedEvent(timestamp, amount, price);
                                handled = true;
                                break;
                            }
                        case "SellDrones":
                            {
                                object val;
                                data.TryGetValue("Count", out val);
                                int amount = (int)(long)val;
                                data.TryGetValue("SellPrice", out val);
                                long price = (long)val;
                                journalEvent = new LimpetSoldEvent(timestamp, amount, price);
                                handled = true;
                                break;
                            }
                        case "ClearSavedGame":
                            {
                                object val;
                                data.TryGetValue("Name", out val);
                                string name = (string)val;
                                journalEvent = new ClearedSaveEvent(timestamp, name);
                                handled = true;
                                break;
                            }
                        case "NewCommander":
                            {
                                object val;
                                data.TryGetValue("Name", out val);
                                string name = (string)val;
                                data.TryGetValue("Package", out val);
                                string package = (string)val;
                                journalEvent = new CommanderStartedEvent(timestamp, name, package);
                                handled = true;
                                break;
                            }
                        case "Progress":
                            {
                                object val;
                                data.TryGetValue("Combat", out val);
                                decimal combat = (long)val;
                                data.TryGetValue("Trade", out val);
                                decimal trade = (long)val;
                                data.TryGetValue("Explore", out val);
                                decimal exploration = (long)val;
                                data.TryGetValue("CQC", out val);
                                decimal cqc = (long)val;
                                data.TryGetValue("Empire", out val);
                                decimal empire = (long)val;
                                data.TryGetValue("Federation", out val);
                                decimal federation = (long)val;

                                journalEvent = new CommanderProgressEvent(timestamp, combat, trade, exploration, cqc, empire, federation);
                                handled = true;
                                break;
                            }
                        case "Rank":
                            {
                                object val;
                                data.TryGetValue("Combat", out val);
                                CombatRating combat = CombatRating.FromRank((int)((long)val));
                                data.TryGetValue("Trade", out val);
                                TradeRating trade = TradeRating.FromRank((int)((long)val));
                                data.TryGetValue("Explore", out val);
                                ExplorationRating exploration = ExplorationRating.FromRank((int)((long)val));
                                data.TryGetValue("CQC", out val);
                                CQCRating cqc = CQCRating.FromRank((int)((long)val));
                                data.TryGetValue("Empire", out val);
                                EmpireRating empire = EmpireRating.FromRank((int)((long)val));
                                data.TryGetValue("Federation", out val);
                                FederationRating federation = FederationRating.FromRank((int)((long)val));

                                journalEvent = new CommanderRatingsEvent(timestamp, combat, trade, exploration, cqc, empire, federation);
                                handled = true;
                                break;
                            }
                        case "Screenshot":
                            {
                                object val;
                                data.TryGetValue("Filename", out val);
                                string filename = (string)val;
                                data.TryGetValue("Width", out val);
                                int width = (int)(long)val;
                                data.TryGetValue("Height", out val);
                                int height = (int)(long)val;
                                data.TryGetValue("System", out val);
                                string system = (string)val;
                                data.TryGetValue("Body", out val);
                                string body = (string)val;

                                journalEvent = new ScreenshotEvent(timestamp, filename, width, height, system, body);
                                handled = true;
                                break;
                            }
                        case "BuyTradeData":
                            {
                                object val;
                                data.TryGetValue("System", out val);
                                string system = (string)val;
                                data.TryGetValue("Cost", out val);
                                long price = (long)val;

                                journalEvent = new TradeDataPurchasedEvent(timestamp, system, price);
                                handled = true;
                                break;
                            }
                        case "PayFines":
                            {
                                object val;
                                data.TryGetValue("Amount", out val);
                                long amount = (long)val;

                                journalEvent = new FinePaidEvent(timestamp, amount, false);
                                handled = true;
                                break;
                            }
                        case "PayLegacyFines":
                            {
                                object val;
                                data.TryGetValue("Amount", out val);
                                long amount = (long)val;

                                journalEvent = new FinePaidEvent(timestamp, amount, true);
                                handled = true;
                                break;
                            }
                        case "RefuelPartial":
                            {
                                object val;
                                data.TryGetValue("Amount", out val);
                                decimal amount = (decimal)(double)val;
                                data.TryGetValue("Cost", out val);
                                long price = (long)val;

                                journalEvent = new ShipRefuelledEvent(timestamp, price, amount);
                                handled = true;
                                break;
                            }
                        case "RefuelAll":
                            {
                                object val;
                                data.TryGetValue("Amount", out val);
                                decimal amount = (decimal)(double)val;
                                data.TryGetValue("Cost", out val);
                                long price = (long)val;

                                journalEvent = new ShipRefuelledEvent(timestamp, price, amount);
                                handled = true;
                                break;
                            }
                        //case "RedeemVoucher":
                        //    {
                        //        object val;
                        //        data.TryGetValue("Amount", out val);
                        //        decimal amount = (decimal)(double)val;
                        //        data.TryGetValue("Cost", out val);
                        //        long price = (long)val;

                        //        journalEvent = new ShipRefuelledEvent(timestamp, price, amount);
                        //        handled = true;
                        //        break;
                        //    }
                        case "CommunityGoalJoin":
                            {
                                object val;
                                data.TryGetValue("Name", out val);
                                string name = (string)val;
                                data.TryGetValue("System", out val);
                                string system = (string)val;

                                journalEvent = new MissionAcceptedEvent(timestamp, null, name, system, null, true, null);
                                handled = true;
                                break;
                            }
                        case "CommunityGoalReward":
                            {
                                object val;
                                data.TryGetValue("Name", out val);
                                string name = (string)val;
                                data.TryGetValue("System", out val);
                                string system = (string)val;
                                data.TryGetValue("Reward", out val);
                                long reward = (val == null ? 0 : (long)val);

                                journalEvent = new MissionCompletedEvent(timestamp, null, name, system, true, reward, 0);
                                handled = true;
                                break;
                            }
                        case "MissionAccepted":
                            {
                                object val;
                                data.TryGetValue("MissionID", out val);
                                long missionid = (long)val;
                                data.TryGetValue("Name", out val);
                                string name = (string)val;
                                data.TryGetValue("Faction", out val);
                                string faction = (string)val;
                                // Could be a superpower...
                                Superpower superpowerFaction = Superpower.From(faction);
                                faction = superpowerFaction != null ? superpowerFaction.name : faction;

                                data.TryGetValue("Expiry", out val);
                                DateTime? expiry = (val == null ? (DateTime?)null : (DateTime)val);

                                journalEvent = new MissionAcceptedEvent(timestamp, missionid, name, null, faction, false, expiry);
                                handled = true;
                                break;
                            }
                        case "MissionCompleted":
                            {
                                object val;
                                data.TryGetValue("MissionID", out val);
                                long missionid = (long)val;
                                data.TryGetValue("Name", out val);
                                string name = (string)val;
                                data.TryGetValue("Reward", out val);
                                long reward = (val == null ? 0 : (long)val);
                                data.TryGetValue("Donation", out val);
                                long donation = (val == null ? 0 : (long)val);

                                journalEvent = new MissionCompletedEvent(timestamp, missionid, name, null, false, reward, donation);
                                handled = true;
                                break;
                            }
                        case "MissionAbandoned":
                            {
                                object val;
                                data.TryGetValue("MissionID", out val);
                                long missionid = (long)val;
                                data.TryGetValue("Name", out val);
                                string name = (string)val;
                                journalEvent = new MissionAbandonedEvent(timestamp, missionid, name);
                                handled = true;
                                break;
                            }
                        case "Repair":
                            {
                                object val;
                                data.TryGetValue("Item", out val);
                                string item = (string)val;
                                data.TryGetValue("Cost", out val);
                                long price = (long)val;
                                journalEvent = new ShipRepairedEvent(timestamp, item, price);
                                handled = true;
                                break;
                            }
                        case "RepairAll":
                            {
                                object val;
                                data.TryGetValue("Cost", out val);
                                long price = (long)val;
                                journalEvent = new ShipRepairedEvent(timestamp, null, price);
                                handled = true;
                                break;
                            }
                        case "RebootRepair":
                            {
                                object val;
                                data.TryGetValue("Modules", out val);
                                List<object> modulesJson = (List<object>)val;

                                List<string> modules = new List<string>();
                                foreach (string module in modulesJson)
                                {
                                    modules.Add(module);
                                }
                                journalEvent = new ShipRebootedEvent(timestamp, modules);
                                handled = true;
                                break;
                            }
                        case "Synthesis":
                            {
                                object val;
                                data.TryGetValue("Name", out val);
                                string synthesis = (string)val;

                                data.TryGetValue("Materials", out val);
                                Dictionary<string, object> materialsData = (Dictionary<string, object>)val;
                                List<MaterialAmount> materials = new List<MaterialAmount>();
                                if (materialsData != null)
                                {
                                    foreach (KeyValuePair<string, object> materialData in materialsData)
                                    {
                                        Material material = Material.FromEDName(materialData.Key);
                                        materials.Add(new MaterialAmount(material, (int)(long)materialData.Value));
                                    }
                                }

                                journalEvent = new SynthesisedEvent(timestamp, synthesis, materials);
                                handled = true;
                                break;
                            }
                    }

                    if (journalEvent != null)
                    {
                        journalEvent.raw = line;
                    }

                    if (handled)
                    {
                        if (journalEvent == null)
                        {
                            Logging.Debug("Handled event");
                        }
                        else
                        {
                            Logging.Debug("Handled event: " + JsonConvert.SerializeObject(journalEvent));
                        }
                    }
                    else
                    {
                        Logging.Debug("Unhandled event: " + line);
                    }
                    return journalEvent;
                }
            }
            catch (Exception ex)
            {
                Logging.Warn("Failed to parse line: " + ex.ToString());
                Logging.Error("Exception whilst parsing line", line);
            }
            return null;
        }
        private bool eventDocked(DockedEvent theEvent)
        {
            updateCurrentSystem(theEvent.system);

            if (CurrentStation != null && CurrentStation.name == theEvent.station)
            {
                // We are already at this station; nothing to do
                Logging.Debug("Already at station " + theEvent.station);
                return false;
            }

            // Update the station
            Logging.Debug("Now at station " + theEvent.station);
            Station station = CurrentStarSystem.stations.Find(s => s.name == theEvent.station);
            if (station == null)
            {
                // This station is unknown to us, might not be in EDDB or we might not have connectivity.  Use a placeholder
                station = new Station();
                station.name = theEvent.station;
                station.systemname = theEvent.system;
            }

            // Information from the event might be more current than that from EDDB so use it in preference
            station.state = theEvent.factionstate;
            station.faction = theEvent.faction;
            station.government = theEvent.government;
            station.allegiance = theEvent.allegiance;

            CurrentStation = station;

            // Now call refreshProfile() to obtain the outfitting and commodity information
            refreshProfile();

            return true;
        }
 private void handleDockedEvent(DockedEvent theEvent)
 {
     // When we dock we have access to commodity and outfitting information
     sendCommodityInformation();
     sendOutfittingInformation();
 }