#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include "../include/limine.h"

#include "prompt/lex.h"
#include "prompt/file.h"
void print_tok(struct token* tok);

#include "acpi.h"
#include "ahci.h"
#include "alloc.h"
#include "apic.h"
#include "common.h"
#include "fat32.h"
#include "print.h"
#include "lib/stdio.h"

// credit owed to https://wiki.osdev.org/Limine_Bare_Bones for the limine setup
// code

__attribute__((
    used,
    section(".limine_requests"))) static volatile u64 limine_base_revision[] =
    LIMINE_BASE_REVISION(4);

__attribute__((
    used,
    section(
        ".limine_requests"))) static volatile struct limine_framebuffer_request
    fb_req = {.id = LIMINE_FRAMEBUFFER_REQUEST_ID, .revision = 0};

__attribute__((
    used,
    section(".limine_requests"))) static volatile struct limine_hhdm_request
    hhdm_req = {.id = LIMINE_HHDM_REQUEST_ID, .revision = 0};

__attribute__((
    used,
    section(".limine_requests"))) static volatile struct limine_rsdp_request
    rsdp_req = {.id = LIMINE_RSDP_REQUEST_ID, .revision = 0};

u64 hhdm_offset;

__attribute__((used, section(".limine_requests_start"))) static volatile u64
    limine_requests_start_marker[] = LIMINE_REQUESTS_START_MARKER;

__attribute__((used, section(".limine_requests_end"))) static volatile u64
    limine_requests_end_marker[] = LIMINE_REQUESTS_END_MARKER;

void *memcpy(void *restrict dst, const void *src, size_t n) {
  u8 *restrict pdst = (u8 *restrict)dst;
  const u8 *restrict psrc = (const u8 *restrict)src;

  for (size_t i = 0; i < n; i++) {
    pdst[i] = psrc[i];
  }

  return dst;
}

void *memset(void *s, int c, size_t n) {
  u8 *p = (u8 *)s;

  for (size_t i = 0; i < n; i++) {
    p[i] = (u8)c;
  }

  return s;
}

void *memmove(void *dst, const void *src, size_t n) {
  u8 *restrict pdst = (u8 *restrict)dst;
  const u8 *restrict psrc = (const u8 *restrict)src;

  if (src > dst) {
    for (size_t i = 0; i < n; i++) {
      pdst[i] = psrc[i];
    }
  } else if (src < dst) {
    for (size_t i = n; i > 0; i--) {
      pdst[i - 1] = psrc[i - 1];
    }
  }

  return dst;
}

int memcmp(const void *s1, const void *s2, size_t n) {
  const u8 *p1 = (const u8 *)s1;
  const u8 *p2 = (const u8 *)s2;

  for (size_t i = 0; i < n; i++) {
    if (p1[i] != p2[i]) {
      return p1[i] < p2[i] ? -1 : 1;
    }
  }

  return 0;
}

void hang() {
  for (;;) {
    asm("hlt");
  }
}

struct limine_framebuffer *fb;

void draw() {
  for (size_t y = 0; y < fb->height; y++) {
    for (size_t x = 0; x < fb->width; x++) {
      volatile u32 *fb_ptr = fb->address;
      int tone = ((x ^ y) & 0xFF);
      fb_ptr[y * fb->width + x] = (tone << 16);
    }
  }
}

extern void init_idt(void);

extern u32 fg;
extern u32 bg;

void enable_ps2_keyboard_interrupts(void) {
  // Read PS/2 configuration byte
  outb(0x64, 0x20);
  while (!(inb(0x64) & 0x01))
    ; // Wait for data
  u8 config = inb(0x60);

  // Enable keyboard interrupt (bit 0) and clock (clear bit 4)
  config |= 0x01;  // Enable first port interrupt
  config &= ~0x10; // Enable first port clock

  // Write config back
  outb(0x64, 0x60);
  outb(0x60, config);

  // Enable keyboard port
  outb(0x64, 0xAE);
}


void ls(int port, int indent, fat32_vol_t* vol, u32 cluster) {
  u32 cluster_size = vol->sectors_per_cluster * TODO_MAKE_ME_DYNAMIC_BLOCK_SIZE;

  u8* lsbuff = alloc(4096);
  do {
    fat32_read_cluster(port, vol, cluster, lsbuff);
    fat32_dirent_t* dir = (fat32_dirent_t*) lsbuff;
    u32 cnt = cluster_size / sizeof(fat32_dirent_t);
    for(size_t i = 0; i < cnt; i++) {
      if(dir[i].name[0] == 0x00) {
        return;
      }

      for(int i = 0; i < indent; i++) {
        printf("----");
      }

      printf("[%d] ", i);

      if(dir[i].name[0] == 0xE5) {
        printf("[DELETED]\n");
        continue;
      }

      if((dir[i].attr & 0x3F) == ATTR_LFN) {
        printf("Long File Name\n", i);
        continue;
      }
      if(dir[i].attr & ATTR_VOL_ID) {
        printf("Volume ID\n");
        continue;
      }


      for(size_t c = 0; c < 8 && dir[i].name[c] != ' '; c++) {
        put(dir[i].name[c]);
      }
      if(dir[i].ext[0] && dir[i].ext[0] != ' ') {
        put('.');
        for(size_t c = 0; c < 8 && dir[i].ext[c] != ' '; c++) {
          put(dir[i].ext[c]);
        }
      }
      put('\n');
      
      if(dir[i].attr & ATTR_DIR) {
        if(dir[i].name[0] != '.')
          ls(port, indent + 1, vol, dir[i].cluster_low + ((u32)(dir[i].cluster_high) << 16));
      }
    }

    cluster = fat32_next(port, vol, cluster);
  } while(!fat32_is_eof(cluster));

  printf("All Done\n");
}

void kmain() {
  if (LIMINE_BASE_REVISION_SUPPORTED(limine_base_revision) == false) {
    hang();
  }

  if (fb_req.response == NULL || fb_req.response->framebuffer_count < 1) {
    hang();
  }

  fb = fb_req.response->framebuffers[0];

  draw();
  print_init(fb);
  init_idt();
  fg = 0xFFFFFF;
  bg = 0x000000;

  if (!hhdm_req.response) {
    print("Failed to get higher half direct mapping\n.");
    hang();
  }

  hhdm_offset = hhdm_req.response->offset;

  if (!rsdp_req.response) {
    print("Failed to find rsdp\n.");
    hang();
  }

  alloc_init();

  rsdp = (struct acpi_rsdp_header *)(rsdp_req.response->address);

  print("Found RSDP @ 0x");
  print64((u64)rsdp);
  put('\n');

  print("With signature: ");
  printn((char *)rsdp->signature, sizeof(rsdp->signature));

  print(" v");
  print8(rsdp->revision);
  print("\nRSDT at 0x");
  struct acpi_rsdt *rsdt =
      (struct acpi_rsdt *)((rsdp->revision ? rsdp->ptr64 : rsdp->ptr32) +
                           hhdm_offset);
  print64((u64)rsdt);
  print("\nSDT Signature: ");

  printn((const char *)rsdt->signature, sizeof(rsdt->signature));
  print("\nAPIC Local Base: ");

  /* I should check cpuid to ensure its supported but it will
   * be for my machines ;^) */
  /* APIC stuff */
  apic_enable();
  print("APIC Enabled\n");
  ioapic_init();

  asm volatile("sti");


  ahci_init();

  i8 port = -1;
  for(u8 i = 0; i < 32; i++) {
    if(ahci_port_active(i)) {
      // assume we want the first port
      if(port < 0)
        port = i;
      printf("Port %d is active!\n", i);
    }
  }

  if(port < 0) {
    printf("Couldn't find a port to read the FS from :(\n");
    hang();
  }
  

  fat32_mbr_t* mbr = alloc(1<<12);
  ahci_read(port, 0, 1, mbr);

  i8 bootpart = -1;
  fat32_mbr_partition_t bpart;
  printf("Fat32 Partitions:\n");
  for(size_t i = 0; i < 4; i++) {
    fat32_mbr_partition_t* part = &mbr->partitions[i];
    u32 lba_start = part->lba_start;
    u32 lba_end = lba_start + part->sector_count;

    if(bootpart < 0 && part->status == 0x80) {
      bootpart = i;
      bpart = *part;
    }
    
    printf("\t%s, %d Sectors [0x%x-0x%x]\n",
      (part->status == 0x80)? "[BOOTABLE]" : "[UNBOOTABLE]",
      part->sector_count,
      lba_start, lba_end);
  }

  if(bootpart < 0) {
    printf("Failed to find boot partition :(\n");
  }


  // reuse buffer
  fat32_bpb_t* bpb = (fat32_bpb_t*) mbr;
  mbr = NULL;

  // hardcode for now :(
  u32 part_lba = 2048;
  ahci_read(port, part_lba, 1, bpb);
  
  fat32_vol_t vol = vol_from_bpb(bpb, part_lba);

  u64 root_lba = fat32_cluster_to_lba(&vol, vol.root_cluster);
  printf("root_lba=%u\n", (u32)root_lba);

  u8 *raw = alloc(4096);
  ahci_read(port, root_lba, vol.sectors_per_cluster, raw);
  printf("raw: ");
  for(int i = 0; i < 64; i++)
      printf("%x ", raw[i]);
  printf("\n");

  ls(port, 0, &vol, vol.root_cluster);

  //dealloc((void*) bpb, 1<<12);

// #define LINES 40
//   size_t max_len = MAX_LINE_LEN;
//   char* lines[LINES];

//   size_t n_lines = 0;
//   for(n_lines = 0; n_lines < LINES; n_lines++) {
//     char* line = getline_alloc(max_len);
//     if(!line)
//       break;
//     if(line[0] == 'F' && line[1] == 'I' && line[2] == '\n')
//       break;
//     lines[n_lines] = line;
//   }

//   const char* name = "stdin";
//   struct file input = {
//     .row = 0,
//     .col = 0,
//     .rows = n_lines,
//     .lines = (char**)lines,
//     .name = name,
//     .tokens = NULL,
//     .tail = &input.tokens,
//   };

//   int res = lex(&input);

//   print("Number of Errors: ");
//   print8(res);

//   printf("\n# of Tokens: %d\n", input.tokens->len);
//   struct token* token = input.tokens;

//   for(;token; token = token->next) {
//     print_tok(token);
//   }

  hang();
}
