using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Runtime.InteropServices;

namespace EDPlayerJournal.Entries;

/// <summary>
/// Base class for a single entry within the player journal. If no specific sub class is available
/// this class gives basic information, such as EventType or date when it happened. It also allows
/// base classes access to the underlying JSON object. Base classes should be named after the event
/// type that they map + Entry. So "FSDJump" event is handled by FSDJumpEntry.
/// </summary>
public class Entry {
    private static readonly Dictionary<string, Type> classes = new Dictionary<string, Type> {
        { Events.ApproachSettlement, typeof(ApproachSettlementEntry) },
        { Events.Bounty, typeof(BountyEntry) },
        { Events.CapShipBond, typeof(CapShipBondEntry) },
        { Events.CarrierJump, typeof(CarrierJump) },
        { Events.Commander, typeof(CommanderEntry) },
        { Events.CommitCrime, typeof(CommitCrimeEntry) },
        { Events.Died, typeof(DiedEntry) },
        { Events.Disembark, typeof(DisembarkEntry) },
        { Events.Docked, typeof(DockedEntry) },
        { Events.DropshipDeploy, typeof(DropshipDeployEntry) },
        { Events.Embark, typeof(EmbarkEntry) },
        { Events.FactionKillBond, typeof(FactionKillBondEntry) },
        { Events.FileHeader, typeof(FileHeaderEntry) },
        { Events.FSDJump, typeof(FSDJumpEntry) },
        { Events.HullDamage, typeof(HullDamageEntry) },
        { Events.LoadGame, typeof(LoadGameEntry) },
        { Events.Location, typeof(LocationEntry) },
        { Events.MarketBuy, typeof(MarketBuyEntry) },
        { Events.MarketSell, typeof(MarketSellEntry) },
        { Events.MissionAbandoned, typeof(MissionAbandonedEntry) },
        { Events.MissionAccepted, typeof(MissionAcceptedEntry) },
        { Events.MissionCompleted, typeof(MissionCompletedEntry) },
        { Events.MissionFailed, typeof(MissionFailedEntry) },
        { Events.MissionRedirected, typeof(MissionRedirectedEntry) },
        { Events.Missions, typeof(MissionsEntry) },
        { Events.MultiSellExplorationData, typeof(MultiSellExplorationDataEntry) },
        { Events.Music, typeof(MusicEntry) },
        { Events.ReceiveText, typeof(ReceiveTextEntry) },
        { Events.RedeemVoucher, typeof(RedeemVoucherEntry) },
        { Events.SearchAndRescue, typeof(SearchAndRescueEntry) },
        { Events.SellExplorationData, typeof(SellExplorationDataEntry) },
        { Events.SellMicroResources, typeof(SellMicroResourcesEntry) },
        { Events.SellOrganicData, typeof(SellOrganicDataEntry) },
        { Events.ShieldState, typeof(ShieldStateEntry) },
        { Events.ShipTargeted, typeof(ShipTargetedEntry) },
        { Events.SupercruiseDestinationDrop, typeof(SupercruiseDestinationDropEntry) },
        { Events.SupercruiseEntry, typeof(SupercruiseEntryEntry) },
        { Events.SupercruiseExit, typeof(SupercruiseExitEntry) },
        { Events.UnderAttack, typeof(UnderAttackEntry) },
    };

    private string? eventtype = null;
    private string? datetime = null;
    private DateTime timestamp;

    private string? jsonstr = null;
    protected JObject? json = null;

    public Entry() {
    }

    public static Entry? Parse(string journalline) {
        using (JsonReader reader = new JsonTextReader(new StringReader(journalline))) {
            reader.DateParseHandling = DateParseHandling.None;
            JObject? json = null;
            try {
                json = JObject.Load(reader);
            } catch (Exception e) {
                throw new InvalidJournalEntryException(
                    "invalid JSON journal entry: " + journalline,
                    e
                    );
            }
            if (json == null) {
                return null;
            }
            return Parse(json);
        }
    }

    public static Entry? Parse(JObject json) {
        string? event_name = json.Value<string?>("event");

        if (event_name == null) {
            return null;
        }

        classes.TryGetValue(event_name, out Type? classhandler);
        if (classhandler == null) {
            // No specific handler available so use base class
            classhandler = typeof(Entry);
        }

        Entry? obj = (Entry?)Activator.CreateInstance(classhandler);
        if (obj == null) {
            return null;
        }

        obj.InternalInitialise(json);
        obj.Initialise();

        return obj;
    }

    private void InternalInitialise(JObject jobject) {
        this.json = jobject;
        this.jsonstr = json.ToString(formatting: Formatting.None);

        this.eventtype = json.Value<string>("event");
        // Do not change this or JSON parser will conver the string to
        // datetime object and call .ToString() on it which changes the
        // date and time format
        this.datetime = json.GetValue("timestamp")?.ToString();

        if (!string.IsNullOrEmpty(this.datetime)) {
            this.timestamp = DateTime.Parse(this.datetime, null, System.Globalization.DateTimeStyles.RoundtripKind);
        }
    }

    protected virtual void Initialise() {
    }

    public bool Is(string eventtype) {
        if (eventtype == null || this.eventtype == null) {
            return false;
        }

        return String.Equals(this.eventtype, eventtype, StringComparison.OrdinalIgnoreCase);
    }

    public string? Event {
        get { return eventtype; }
    }

    public DateTime Timestamp {
        get { return timestamp; }
    }

    public JObject JSON {
        get { return this.json ?? new JObject(); }
    }

    public override string ToString() {
        return jsonstr ?? "";
    }
}
