using System;

using Cosmos.Core;
using Cosmos.Debug.Kernel;
using Cosmos.HAL.BlockDevice;

namespace Cosmos.HAL
{
  public static class Global
  {
    public static readonly Debugger mDebugger = new Debugger("HAL", "Global");

    //static public PIT PIT = new PIT();
    // Must be static init, other static inits rely on it not being null
    public static TextScreenBase TextScreen = new TextScreen();

    public static PCI Pci;

    private static void InitAta(Ata.ControllerIdEnum aControllerID,
                                Ata.BusPositionEnum aBusPosition)
    {
      var xIO = aControllerID == Ata.ControllerIdEnum.Primary
                  ? Core.Global.BaseIOGroups.ATA1
                  : Core.Global.BaseIOGroups.ATA2;
      var xATA = new AtaPio(xIO, aControllerID, aBusPosition);
      if (xATA.DriveType == AtaPio.SpecLevel.Null)
      {
        return;
      }
      if (xATA.DriveType == AtaPio.SpecLevel.ATA)
      {
        BlockDevice.BlockDevice.Devices.Add(xATA);
        Ata.AtaDebugger.Send("ATA device with speclevel ATA found.");
      }
      else
      {
        //Ata.AtaDebugger.Send("ATA device with spec level " + (int)xATA.DriveType +
        //                     " found, which is not supported!");
        return;
      }
      var xMbrData = new byte[512];
      xATA.ReadBlock(0UL, 1U, xMbrData);
      var xMBR = new MBR(xMbrData);

      if (xMBR.EBRLocation != 0)
      {
        //EBR Detected
        var xEbrData = new byte[512];
        xATA.ReadBlock(xMBR.EBRLocation, 1U, xEbrData);
        var xEBR = new EBR(xEbrData);

        for (int i = 0; i < xEBR.Partitions.Count; i++)
        {
          //var xPart = xEBR.Partitions[i];
          //var xPartDevice = new BlockDevice.Partition(xATA, xPart.StartSector, xPart.SectorCount);
          //BlockDevice.BlockDevice.Devices.Add(xPartDevice);
        }
      }

      // TODO Change this to foreach when foreach is supported
      Ata.AtaDebugger.Send("Number of MBR partitions found:");
      Ata.AtaDebugger.SendNumber(xMBR.Partitions.Count);
      for (int i = 0; i < xMBR.Partitions.Count; i++)
      {
        var xPart = xMBR.Partitions[i];
        if (xPart == null)
        {
          Console.WriteLine("Null partition found at idx: " + i);
        }
        else
        {
          var xPartDevice = new Partition(xATA, xPart.StartSector, xPart.SectorCount);
          BlockDevice.BlockDevice.Devices.Add(xPartDevice);
          Console.WriteLine("Found partition at idx" + i);
        }
      }
    }

    // Init devices that are "static"/mostly static. These are devices
    // that all PCs are expected to have. Keyboards, screens, ATA hard drives etc.
    // Despite them being static, some discovery is required. For example, to see if
    // a hard drive is connected or not and if so what type.
    internal static void InitStaticDevices()
    {
      //TextScreen = new TextScreen();
      mDebugger.Send("CLS");
      //TODO: Since this is FCL, its "common". Otherwise it should be
      // system level and not accessible from Core. Need to think about this
      // for the future.
      mDebugger.Send("Finding PCI Devices");
      //PCI.Setup();
    }

    static public void Init(TextScreenBase textScreen)
    {
      if (textScreen != null)
      {
        TextScreen = textScreen;
      }

      mDebugger.Send("Before Core.Global.Init");
      Core.Global.Init();
      mDebugger.Send("Static Devices");
      InitStaticDevices();
      mDebugger.Send("PCI Devices");
      InitPciDevices();
      mDebugger.Send("ACPI Init");
      StartACPI();
      mDebugger.Send("Done initializing Cosmos.HAL.Global");

      mDebugger.Send("ATA Primary Master");
      InitAta(Ata.ControllerIdEnum.Primary, Ata.BusPositionEnum.Master);

      //TODO Need to change code to detect if ATA controllers are present or not. How to do this? via PCI enum?
      // They do show up in PCI space as well as the fixed space.
      // Or is it always here, and was our compiler stack corruption issue?
      mDebugger.Send("ATA Secondary Master");
      InitAta(Ata.ControllerIdEnum.Secondary, Ata.BusPositionEnum.Master);
      //InitAta(BlockDevice.Ata.ControllerIdEnum.Secondary, BlockDevice.Ata.BusPositionEnum.Slave);
    }

    internal static void InitPciDevices()
    {
      //TODO Redo this - Global init should be other.
      // Move PCI detection to hardware? Or leave it in core? Is Core PC specific, or deeper?
      // If we let hardware do it, we need to protect it from being used by System.
      // Probably belongs in hardware, and core is more specific stuff like CPU, memory, etc.
      //Core.PCI.OnPCIDeviceFound = PCIDeviceFound;

      //TODO: Since this is FCL, its "common". Otherwise it should be
      // system level and not accessible from Core. Need to think about this
      // for the future.
      Console.WriteLine("Finding PCI Devices");
      PCI.Setup();
    }

    public static void StartACPI()
    {
      Console.WriteLine("Starting ACPI");
      ACPI.Start();
    }

    public static void EnableInterrupts()
    {
      CPU.EnableInterrupts();
    }

    public static bool InterruptsEnabled => CPU.mInterruptsEnabled;
  }
}
