#pragma comment(lib, "user32")
#pragma comment(lib, "shell32")
#pragma comment(lib, "comctl32")
// #pragma comment(lib, "shlwapi")
#include <shellapi.h>
#include <shlobj_core.h> // SHGetFolderPathW
#include <commctrl.h>    // ImageList_GetIcon

f64 GetUnixTimestamp ();
s64 GetUnixTimestampNanoseconds ();

enum class Wait_For_Result: s32 {
  SUCCESS = 0,
  TIMEOUT = 1,
  ERROR = 2 // can't use ERROR because of Windows.h *sigh*
};

internal void semaphore_init (Semaphore* sem, s32 initial_value = 0);
internal void semaphore_destroy (Semaphore* sem);
internal void signal (Semaphore* sem);
internal Wait_For_Result wait_for (Semaphore* sem, s32 milliseconds = -1);

internal void condition_variable_init (Condition_Variable* cv);
internal void condition_variable_destroy (Condition_Variable* cv);
internal void wait (Condition_Variable* cv, Mutex* mutex, s32 wait_time_ms = -1);
internal void wake (Condition_Variable* cv);
internal void wake_all (Condition_Variable* cv);

struct Thread; // #hack forward declares to get this to compile.
typedef s64 (*Thread_Proc)(Thread* thread); 

internal bool thread_init (Thread* thread, Thread_Proc proc, string thread_name="");
internal void thread_deinit (Thread* thread, bool zero_thread=false);
internal void thread_start (Thread* thread, void* thread_data = nullptr);
internal bool thread_is_done (Thread* thread, s32 milliseconds=0);

typedef u32 OS_Error_Code;
internal string get_error_string (OS_Error_Code error_code);

internal bool file_is_valid (File file);
internal File file_open (string file_path, bool for_writing=false, bool keep_existing_content=false, bool log_errors=false);
internal void file_close (File* file);
internal bool file_read (File file, void* data, s64 bytes_to_read_count, s64* bytes_read_count=nullptr);
internal bool file_length (File file, s64* length);
internal bool file_length (string file_path, s64* length);
internal s64 file_current_position (File file);
internal bool file_set_position (File file, s64 position);
internal ArrayView<u8> read_entire_file (File file, bool add_null_terminator);
internal ArrayView<u8> read_entire_file (string file_path, bool add_null_terminator=false, bool log_errors=false);

// use to_byte_view to convert ArrayView<non-u8> to ArrayView<u8>
internal bool file_write (File* file, void* data, s64 length);
internal bool write_entire_file (string file_path, void* file_data, s64 count);
internal bool write_entire_file (string file_path, ArrayView<u8> file_data);

internal bool file_exists (string file_path);

// file_write
// write_entire_file...

// #TODO #fs File System Operations
// file_move, file_delete

// #window_creation
typedef HWND Window_Type;
typedef HICON Window_Icon;

struct Window_Dimensions {
  s32 window_x;
  s32 window_y;
  s32 window_width;
  s32 window_height;
};

struct Window_Info {
  Window_Type window;
  Window_Dimensions initial_dimensions; // for resetting.
  Window_Icon icon;
  Window_Icon icon_minimized;
  
  bool is_main_window;
  bool tray_icon_added;
  bool minimized_to_tray;
  // Platform-Specific (Win32)
  HMENU tray_icon_menu;
  HDC hdc;
  // Likely will only be used for main window:
  NOTIFYICONDATAW nid;
};

// #move: Monitor - platform-independent hardware monitor information: 
struct Monitor {
  s64 left;
  s64 top;
  s64 right;
  s64 bottom;
  bool primary;
  bool present;
#if OS_WINDOWS
  MONITORINFO monitor_info;
#endif
};

enum class Win32_Drive_Type: s32 {
  Unknown     = 0,
  No_Root_Dir = 1,
  Removable   = 2,
  Fixed       = 3,
  Remote      = 4,
  cdrom       = 5,
  ramdisk     = 6
};

enum class File_System: s32 {
  Unknown = 0,
  MusaFS  = 1,
  exFAT   = 2,
  Ext2    = 65,
  Ext3    = 66,
  Ext4    = 67,
  Btrfs   = 79,
  XFS     = 86,
  ZFS     = 91,
  NTFS    = 128, // Windows
  ReFS    = 130, // Windows
  AFS     = 256, // Apple File System
  F2FS    = 1024,
  // Legacy systems:
  FAT32   = -1,
  // FAT16  :: -2;
  // FAT12  :: -3;
};

string to_string (Win32_Drive_Type type) {
  switch (type) {
    case Win32_Drive_Type::Unknown:     { return "Unknown";     } break;
    case Win32_Drive_Type::No_Root_Dir: { return "No_Root_Dir"; } break;
    case Win32_Drive_Type::Removable:   { return "Removable";   } break;
    case Win32_Drive_Type::Fixed:       { return "Fixed";       } break;
    case Win32_Drive_Type::Remote:      { return "Remote";      } break;
    case Win32_Drive_Type::cdrom:       { return "cdrom";       } break;
    case Win32_Drive_Type::ramdisk:     { return "ramdisk";     } break;
  }
  
  Assert(false);
  return "Unknown";
}

string to_string (File_System fs) {
  switch (fs) {
    case File_System::Unknown: { return "Unknown"; } break;
    case File_System::MusaFS:  { return "MusaFS";  } break;
    case File_System::exFAT:   { return "exFAT";   } break;
    case File_System::Ext2:    { return "Ext2";    } break;
    case File_System::Ext3:    { return "Ext3";    } break;
    case File_System::Ext4:    { return "Ext4";    } break;
    case File_System::Btrfs:   { return "Btrfs";   } break;
    case File_System::XFS:     { return "XFS";     } break;
    case File_System::ZFS:     { return "ZFS";     } break;
    case File_System::NTFS:    { return "NTFS";    } break;
    case File_System::ReFS:    { return "ReFS";    } break;
    case File_System::AFS:     { return "AFS";     } break;
    case File_System::F2FS:    { return "F2FS";    } break;
    case File_System::FAT32:   { return "FAT32";   } break;
  }
  
  Assert(false);
  return "Unknown";
}

File_System Win32_filesystem_from_string (string s) {
  string s_copy = to_lower_copy(s);

  if (s_copy == "ntfs")  { return File_System::NTFS; }
  if (s_copy == "refs")  { return File_System::ReFS; }
  if (s_copy == "fat")   { return File_System::FAT32; }
  if (s_copy == "exfat") { return File_System::exFAT; }
  
  Assert(false); 
  return File_System::Unknown;
}

struct Win32_Drive {
  string label;
  string volume_name;
  Win32_Drive_Type type;
  File_System file_system;
  s64 full_size;
  s64 free_space;
  u32 serial_number;
  u32 max_component_length;
  u32 file_system_flags;
  bool is_present;
};

// internal void serialize_win32_drive (Serializer* s, Win32_Drive* drive);
// internal void deserialize_win32_drive (Deserializer* s, Win32_Drive* drive);
internal Win32_Drive* copy_win32_drive (Win32_Drive* drive);

struct Win32_Icon {
  string path;
  s32 width;
  s32 height;
  ArrayView<u8> bitmap;
};

typedef Win32_Icon  Icon;
typedef Win32_Drive OS_Drive;

internal void os_log_error ();

bool os_window_is_minimized (Window_Type window);

bool os_main_window_is_minimized ();

bool os_create_window (string new_window_name, Window_Type parent=nullptr, bool center_window=true, bool open_on_largest_monitor=true, bool display_window=true, void* wnd_proc_override=nullptr);

Window_Info get_main_window ();
Window_Info* get_main_window_pointer ();

string os_get_machine_name ();

// #NTFS#MFT 
// Reference: https://handmade.network/forums/articles/t/7002-tutorial_parsing_the_mft

#pragma pack(push,1)
struct NTFS_BootSector {
    u8     jump[3]; 
    u8     name[8];
    u16    bytesPerSector;
    u8     sectorsPerCluster;
    u16    reservedSectors;
    u8     unused0[3];
    u16    unused1;
    u8     media;
    u16    unused2;
    u16    sectorsPerTrack;
    u16    headsPerCylinder;
    u32    hiddenSectors;
    u32    unused3;
    u32    unused4;
    u64    totalSectors;
    u64    mftStart;
    u64    mftMirrorStart;
    u32    clustersPerFileRecord;
    u32    clustersPerIndexBlock;
    u64    serialNumber;
    u32    checksum;
    u8     bootloader[426];
    u16    bootSignature;
};

struct NTFS_FileRecordHeader { // https://flatcap.github.io/linux-ntfs/ntfs/concepts/file_record.html
    u32    magic;
    u16    updateSequenceOffset;  // Offset to the Update Sequence
    u16    updateSequenceSize;    // Size in words of Update Sequence (S)
    u64    logSequence;           // $LogFile Sequence Number (LSN): This is changed every time the record is modified.
    u16    sequenceNumber;        // Number of times this mft record has been reused. N.B. The increment (skipping zero) is done when the file is deleted. N.B. If this is set to zero it is left as zero.
    u16    hardLinkCount;         // Number of hard links, i.e. the number of directory entries referencing this record.
    u16    firstAttributeOffset;  // Offset to the first attribute
    u16    inUse : 1;             // 0x01
    u16    isDirectory : 1;       // 0x02
                                  // 0x04 record is an extension (set for records in the $Extend directory)
                                  // 0x08 special index present (set for non-directory records containing an index: $Secure, $ObjID, $Quota, $Reparse)
    u32    usedSize;              // Real size is a count of how many bytes of a record are actually used. 
    u32    allocatedSize;         // Allocated size the file record takes on disk (multiple of cluster size).
    u64    fileReference;         // Base MFT record - File reference to the base FILE record
      // When it is not zero, it is an MFT reference pointing to the base MFT record to which this record belongs. The Base Record contains information about the Extension Record. This information is stored in an ATTRIBUTE_LIST attribute.
    u16    nextAttributeID;       // The Attribute Id that will be assigned to the next Attribute added to this MFT Record. (Incremented each time it is used, Every time the MFT Record is reused this Id is set to zero, The first instance number is always 0.)
    u16    unused;                // [added in XP] Align to 4 byte boundary
    u32    recordNumber;          // [added in XP] Number of this MFT Record
};

struct NTFS_AttributeHeader {
    u32    attributeType; // see https://flatcap.github.io/linux-ntfs/ntfs/attributes/index.html
    u32    length;
    u8     nonResident;
    u8     nameLength;
    u16    nameOffset;
    u16    flags;
    u16    attributeID;
};

struct NTFS_ResidentAttributeHeader : NTFS_AttributeHeader {
    u32    attributeLength;
    u16    attributeOffset;
    u8     indexed;
    u8     unused;
};

struct NTFS_FileNameAttributeHeader : NTFS_ResidentAttributeHeader {
  //u64    fileReferenceNumber; // A reference consists of a 48-bit index into the mft and a 16-bit sequence number used to detect stale references.
    u64    parentRecordNumber : 48; // low 48 bits
    u64    sequenceNumber : 16;
    u64    creationTime;
    u64    modificationTime;
    u64    metadataModificationTime;
    u64    readTime;
    u64    allocatedSize;         // This information is not reliable - use the $DATA attribute's unnamed stream for the correct file sizes.
    u64    realSize;              // This information is not reliable - use the $DATA attribute's unnamed stream for the correct file sizes.
    u32    flags;
    u32    repase;
    u8     fileNameLength;
    u8     namespaceType;
    u16    fileName[1];
};

struct NTFS_FileStandardInformationHeader : NTFS_AttributeHeader {
  // source: https://flatcap.github.io/linux-ntfs/ntfs/attributes/standard_information.html
  u64    creationTime;         // file creation time
  u64    modificationTime;     // changed when file contents are modified. 
  u64    metadataModificationTime; // changed when MFT record is modified. 
  u64    readTime;             // last read
  u32    filePermissions;      // DOS file permissions.
  u32    maximumNumberOfVersions;  
  u32    versionNumber;
  u32    classID;              // [added in Windows 2000]
  u32    ownerID;              // [added in Windows 2000]
  u32    securityID;           // [added in Windows 2000]
  u64    quotaCharged;         // [added in Windows 2000]
  u64    updateSequenceNumber; // [added in Windows 2000]
};

struct NTFS_NonResidentAttributeHeader : NTFS_AttributeHeader {
    // source: https://flatcap.github.io/linux-ntfs/ntfs/concepts/attribute_header.html
    // see: Non-Resident, No Name, and Non-Resident, Named
    u64    firstCluster;       // Starting VCN
    u64    lastCluster;        // Last VCN
    u16    dataRunsOffset;     // Offset to the data runs. If not named, this is always 0x40.
    u16    compressionUnit;    // Compression unit size (if file is compressed), 0 implies uncompressed
    u32    unused;             // Padding.
    u64    attributeAllocated; // allocated size of the attribute (NOT THE FILE) This is the attribute size rounded up to the cluster size.
    u64    attributeSize;      // Logical "real" size of the attribute (NOT THE FILE)
    u64    streamDataSize;     // Initialized data size of the stream (Compressed data size / actual valid bytes)
};

struct NTFS_RunHeader {
  u8 lengthFieldBytes : 4;
  u8 offsetFieldBytes : 4;
};
#pragma pack(pop)

struct NTFS_MFT_Internal {
  ArrayView<u8> mft_file;
  ArrayView<u8> mft_buffer;
  HANDLE handle;
  s64 bytes_accessed;
  s64 object_count;
  string drive_label;
};

constexpr s64 NTFS_MFT_File_Record_Size = 1024; // File Entry Block
constexpr s64 NTFS_MFT_Files_Per_Buffer = 65536;

NTFS_MFT_Internal* new_ntfs_mft_internal () { // call with temp
  NTFS_MFT_Internal* mft = New<NTFS_MFT_Internal>(true);
  mft->mft_file   = ArrayView<u8>(NTFS_MFT_File_Record_Size);
  mft->mft_buffer = ArrayView<u8>(NTFS_MFT_File_Record_Size * NTFS_MFT_Files_Per_Buffer); // 64 MB
  
  return mft;
}

enum class NTFS_File_Flags: u16 {
  is_compressed       = 0x0001, // (attribute->flags)
  is_encrypted        = 0x0002, // (attribute->flags)
  is_sparse           = 0x0004, // (attribute->flags)
  // [DOS File Permissions] fileInfoAttribute->filePermissions 
  read_only_file      = 0x0008,
  hidden              = 0x0010,
  system              = 0x0020,
  archive             = 0x0040,
  device              = 0x0080,
  normal              = 0x0100,
  temporary           = 0x0200,
  sparse              = 0x0400, // redundant
  reparse_point       = 0x0800,
  compressed          = 0x1000,  // redundant
  offline             = 0x2000,
  not_content_indexed = 0x4000,
  encrypted           = 0x8000, // redundant
};

force_inline NTFS_File_Flags operator | (NTFS_File_Flags a, NTFS_File_Flags b) {
  return (NTFS_File_Flags)(((u16)a) | ((u16)b));
}

force_inline NTFS_File_Flags operator & (NTFS_File_Flags a, NTFS_File_Flags b) {
  return (NTFS_File_Flags)(((u16)a) & ((u16)b));
}

force_inline NTFS_File_Flags& operator |= (NTFS_File_Flags& a, NTFS_File_Flags b) {
  a = a | b;
  return a;
}

force_inline NTFS_File_Flags operator ~ (NTFS_File_Flags a) {
  return (NTFS_File_Flags)(~((u16)a));
}

// #TODO: I probably don't need this struct at all 
struct NTFS_File {
  u64  parent_id;
  u64  record_id;
  u16* name_data;
  u64  file_modtime; // FILETIME
  u64  file_size;
  u8   name_count;
  bool is_directory;
  NTFS_File_Flags flags;
};

force_inline void add_file_permissions (NTFS_File* file, u32 permissions) {
  if (permissions & 0x0001 == 0x0001) {
    file->flags |= NTFS_File_Flags::read_only_file;
  }
  if (permissions & 0x0002 == 0x0002) {
    file->flags |= NTFS_File_Flags::hidden;
  }
  if (permissions & 0x0004 == 0x0004) {
    file->flags |= NTFS_File_Flags::system;
  }
  if (permissions & 0x0020 == 0x0020) {
    file->flags |= NTFS_File_Flags::archive;
  }
  if (permissions & 0x0040 == 0x0040) {
    file->flags |= NTFS_File_Flags::device;
  }
  if (permissions & 0x0080 == 0x0080) {
    file->flags |= NTFS_File_Flags::normal;
  }
  if (permissions & 0x0100 == 0x0100) {
    file->flags |= NTFS_File_Flags::temporary;
  }
  if (permissions & 0x0200 == 0x0200) {
    file->flags |= NTFS_File_Flags::sparse;
  }
  if (permissions & 0x0400 == 0x0400) {
    file->flags |= NTFS_File_Flags::reparse_point;
  }
  if (permissions & 0x0800 == 0x0800) {
    file->flags |= NTFS_File_Flags::compressed;
  }
  if (permissions & 0x1000 == 0x1000) {
    file->flags |= NTFS_File_Flags::offline;
  }
  if (permissions & 0x2000 == 0x2000) {
    file->flags |= NTFS_File_Flags::not_content_indexed;
  }
  if (permissions & 0x4000 == 0x4000) {
    file->flags |= NTFS_File_Flags::encrypted;
  }
}

force_inline void add_flags (NTFS_File* file, u16 attribute_flags) {
  if (attribute_flags & 0x0001 == 0x0001) {
    file->flags |= NTFS_File_Flags::is_compressed;
  }
  if (attribute_flags & 0x4000 == 0x4000) {
    file->flags |= NTFS_File_Flags::is_encrypted;
  }
  if (attribute_flags & 0x8000 == 0x8000) {
    file->flags |= NTFS_File_Flags::is_sparse;
  }
}

// #NTFS#MFT #API
// bool ntfs_mft_initialize (); // sets up thread group
// bool ntfs_mft_enumerate_drive (Win32_Drive* drive);
// bool ntfs_mft_enumeration_is_done ();
// void ntfs_mft_finish (); 

// #NTFS#MFT#USNJrnl
// struct NTFS_USN_Journal {
//   bool no_permission;
//   HANDLE hVol;
//   ArrayView<USN_Journal_Change> changes;
// };