#define LIMINE_HDD_FILE         "/Boot/Limine-BIOS-HDD.HH"
#define LIMINE_HDD_INSTALLER    "/System/Boot/LimineMHDIns.ZC"
#include LIMINE_HDD_FILE
#include LIMINE_HDD_INSTALLER

#define LIMINE_INSTALL_PROMPT   \
"Install $GREEN$Public Domain$FG$ ZealOS Boot Loader,\n" \
"or $RED$BSD-2 licensed$FG$ Limine Boot Loader?\n\n" \
"$DKGRAY$ZealOS Boot Loader is written in public-domain ZealC, but only supports BIOS mode.\n\n" \
"Limine supports UEFI and BIOS mode and multiple boot protocols, but is written in BSD-2 licensed C code, and cannot be modified within ZealOS.$FG$\n\n\n"

// TODO FIXME: refactor OSUpgrade code into optional Diff prompt to compare ISO<->HDD before Install.

CDirEntry OSFilesMGFind(CDirEntry *needle_entry, CDirEntry *haystack_list)
{
    while (haystack_list)
    {
        if (!StrCompare(needle_entry->name, haystack_list->name))
            return haystack_list;
        haystack_list = haystack_list->next;
    }

    return NULL;
}

U0 OSFilesMergeInner(CDirEntry *tmpde1, CDirEntry *tmpde2, I64 *_df_flags)
{
    CDirEntry   *tmpde;
    U8          *new;

    while (tmpde1 && !(*_df_flags & DF_ABORT_ALL_FILES))
    {
        tmpde = OSFilesMGFind(tmpde1, tmpde2);
        if (!tmpde)
        {
            "$BROWN$Does Not Exist:%s$FG$\n", tmpde1->full_name;
            new = StrNew(tmpde1->full_name);
            new[0] = tmpde2->full_name[0]; // shortcut to quickly get drive letter

            if (tmpde1->attr & RS_ATTR_DIR)
                CopyTree(tmpde1->full_name, new);
            else
            {
                Copy(tmpde1->full_name, new);
            }

            Free(new);
        }
        else
        {
            if (tmpde1->attr & RS_ATTR_DIR)
                OSFilesMergeInner(tmpde1->sub, tmpde->sub, _df_flags);
            else
            {
                if (FilesFindMatch(tmpde1->full_name, FILEMASK_TXT))
                {
                    "\n$LTRED$%s$FG$\n", tmpde->full_name;
                    "$LTGREEN$%s$FG$\n", tmpde1->full_name;
                    Diff(tmpde->full_name, tmpde1->full_name, _df_flags);
                }
            }
        }
        tmpde1 = tmpde1->next;
    }
}

U0 OSFilesMerge(U8 *dst_files_find_mask="/*", U8 *src_files_find_mask="/*", U8 *fu_flags=NULL)
{ // See Merge.
    I64          df_flags, fuf_flags = 0, ch;
    CDirEntry   *tmpde1 = NULL, *tmpde2 = NULL;

    FlagsScan(&fuf_flags, Define("ST_FILE_UTIL_FLAGS"), "+r");
    FlagsScan(&fuf_flags, Define("ST_FILE_UTIL_FLAGS"), fu_flags);
    if (fuf_flags & ~(FUG_FILES_FIND | FUF_DIFF))
        throw('FUF');
    PrintWarn("This is based strictly on file dates.\n");
    tmpde1 = FilesFind(src_files_find_mask, fuf_flags & FUG_FILES_FIND);
    tmpde2 = FilesFind(dst_files_find_mask, fuf_flags & FUG_FILES_FIND);

    "\n\nManual or Automatic Upgrade? (M/A): ";
    do
        ch = ToUpper(CharGet(, FALSE));
    while (ch != 'M' && ch != 'A');

    "%c\n", ch;

    if (ch == 'M')
    {
        PopUpOk("\n$LTGREEN$FILE2$FG$ is new changes.\n"
                "$LTRED$FILE1$FG$ is from existing install.");
        df_flags = 0;
        OSFilesMergeInner(tmpde1, tmpde2, &df_flags);
    }
    else
    {
        df_flags = DF_REMAINDER_ALL_FILE2 | DF_NO_MORE_PROMPTS_THIS_FILE | DF_KEEP_FLAGS;
        OSFilesMergeInner(tmpde1, tmpde2, &df_flags);
    }

    DirTreeDel(tmpde1);
    DirTreeDel(tmpde2);
}

U0 OSMerge(U8 dst_drv, U8 src_drv=':')
{
    U8 *dst = MStrPrint("%C:/", dst_drv);
    U8 *src = MStrPrint("%C:/", src_drv);

    CopyTree(dst, "B:/");
    Del("B:/Misc/Bible.TXT");       // Deleting from B:/ prevents causing hang when merging large .TXT files,
    Del("B:/Misc/Clementine.TXT");  // they will instead be copied over from boot drive in OSMergeInner

    Del("B:/Boot/Limine.CFG"); // Delete to make merge output ignore (copy) this, since it gets regenerated later

    DocMax;

    "$PB$$PB$$LTCYAN$Beginning Upgrade (running OSFilesMerge) ...$FG$\n\n";

    OSFilesMerge("B:/", src, "+d");

    "\n\n$LTCYAN$Upgrade merge completed."
    "\nChanges listed above, scroll up to see all."
    "\n\nPress ESC when you are ready to finish Upgrade (write merged files to disk).$FG$\n\n";
    View;
    DocBottom;

    CopyTree("B:/", dst);

    ExePrint("Del(\"%C:/Boot/Limine-BIOS-CD.BIN\");", dst_drv);
    ExePrint("Del(\"%C:/Boot/Limine-UEFI-CD.BIN\");", dst_drv);
    ExePrint("Del(\"%C:/Boot/Limine.CFG\");", dst_drv);
    ExePrint("Del(\"%C:/boot.catalog\");", dst_drv);

}

U0 OSUpgrade()
{
    I64      drv_let, ch;
    U8      *st, *port_st;
    CTask   *task;

    task = User;
    TaskWait(task);
    task->border_src    = BDS_CONST;
    task->border_attr   = LTGRAY << 4 + DriveTextAttrGet(':') & 15;
    task->text_attr     = LTGRAY << 4 + BLUE;
    task->win_inhibit   = WIG_TASK_DEFAULT - WIF_SELF_BORDER;
    WinHorz(Fs->win_left, Fs->win_right, task);
    WinVert(Fs->win_top,  (Fs->win_top + Fs->win_bottom) >> 2 - 1, task);
    WinVert(task->win_bottom + 3, Fs->win_bottom);
    WinToTop(Fs);

    XTalk(task, "Mount;\nC\n");

    "\n\nSelect the port of the ATA drive with an existing installation.\n\n";

    "Hard Drive Port: ";
    while (TRUE)
    {
        port_st = StrGet;
        if ((0 <= Str2I64(port_st) < AHCI_MAX_PORTS) &&
            ((&blkdev.ahci_hba->ports[Str2I64(port_st)])->signature == AHCI_PxSIG_ATA))
        {
            break;
        }
        Free(port_st);
    }

    XTalkWait(task, "%s\n\n", port_st);

    DriveRep;
    do
    {
        st = StrGet("\nInstallation Partition Letter: ");
        if (*st)
            drv_let = Letter2Letter(*st);
        else
            drv_let = 0;
        Free(st);
    }
    while (!('A' <= drv_let <= 'Z'));

    '\n';

    OSMerge(drv_let); // src_drv needed?

    XTalkWait(task, "BootHDIns('%C');\n\nB\n0x20000\nC\n%s\n\n", drv_let, port_st);
    XTalkWait(task, "\n\n"); //skip through Disk Cache, Options

    if (!sys_is_uefi_booted)
    {
        "$RED$Install Master Boot Loader?$FG$";
        if (YorN)
        {
            "\n\n";
            if (FileFind(LIMINE_HDD_FILE))
            {
                "" LIMINE_INSTALL_PROMPT;
                "(Z/L): ";

                do
                    ch = ToUpper(CharGet(, FALSE));
                while (ch != 'Z' && ch != 'L');

                "%c\n", ch;

                if (ch == 'Z')
                {
                    BootMHDIns(drv_let);
                    "\n(Generating optional UEFI-mode Limine.CFG...)\n";
                    LimineCFGMake(drv_let);
                }
                else
                {
                    LimineMHDIns(drv_let);
                }
                "\n\n";
            }
            else
                BootMHDIns(drv_let);
        }
    }
    else
        LimineCFGMake(drv_let);

    WinVert(task->win_top, Fs->win_bottom);
    Kill(task);
}

U0 InstallDrive(U8 drv_let)
{
    U8 *st;

    while (!DriveCheck(blkdev.let_to_drive[drv_let - 'A'], FALSE))
        Sleep(1);

    Sleep(1000);
    '.';
    Sleep(1000);
    '.';
    Sleep(1000);
    '.';
    Sleep(1000);

    ExePrint("CopyTree(\"::/\",\"%C:/\");",         drv_let);

    ExePrint("Del(\"%C:/Boot/Limine-BIOS-CD.BIN\");", drv_let);
    ExePrint("Del(\"%C:/Boot/Limine-UEFI-CD.BIN\");", drv_let);
    ExePrint("Del(\"%C:/Boot/Limine.CFG\");", drv_let);
    ExePrint("Del(\"%C:/boot.catalog\");", drv_let);

    ExePrint("DirMake(\"%C:/Tmp\");",               drv_let);
    ExePrint("DirMake(\"%C:/Tmp/ScreenShots\");",   drv_let);
    ExePrint("DirMake(\"%C:/Home\");",              drv_let);

    st = MStrPrint("%C:/Home/DoDistro.ZC", drv_let);
    if (!FileFind(st))
        Copy("::/Misc/DoDistro.ZC", st);
    Free(st);

    st = MStrPrint("%C:/Home/MakeHome.ZC", drv_let);
    if (!FileFind(st))
        Copy("::/MakeHome.ZC", st);
    Free(st);
}

Bool VMPartDisk(CTask *task, I64 ata_port)
{
    if (ata_port > -1)
    {
        XTalkWait(task, "DiskPart(,0.5,0.5);\nC\n%d\nY", ata_port); // DOUBLE CHECK INFILE
        return TRUE;
    }
    else
        return FALSE;
}

U0 VMInstallDrive(CTask *task, U8 drv_let, I64 ata_port, I64 atapi_port)
{// DOUBLE CHECK INFILE
    InstallDrive(drv_let);
    XTalkWait(task, "BootHDIns('%C');\n\nB\n0x20000\n", drv_let);
    if (ata_port > -1)
        XTalkWait(task, "C\n%d\n", ata_port);
    if (atapi_port > -1)
        XTalkWait(task, "T%d\n", atapi_port);
    XTalkWait(task, "\n\n\n\n"); //Exit Drives, skip Disk Cache and Options
}

U0 VMInstallWiz()
{
    CTask       *task;
    I64          i, atapi_port = -1, ata_port = -1, ch;
    CAHCIPort   *port;

    task = User;
    TaskWait(task);
    task->border_src    = BDS_CONST;
    task->border_attr   = LTGRAY << 4 + DriveTextAttrGet(':') & 15;
    task->text_attr     = LTGRAY << 4 + BLUE;
    task->win_inhibit   = WIG_TASK_DEFAULT - WIF_SELF_BORDER;
    WinHorz(Fs->win_left, Fs->win_right, task);
    WinVert(Fs->win_top,  (Fs->win_top + Fs->win_bottom) >> 2 - 1, task);
    WinVert(task->win_bottom + 3, Fs->win_bottom);
    WinToTop(Fs);

    SATARep;
    for (i = 0; i < AHCI_MAX_PORTS; i++)
    {
        if (PCIBt(&blkdev.ahci_hba->ports_implemented, i))
        {
            port = &blkdev.ahci_hba->ports[i];
            if (port->signature == AHCI_PxSIG_ATA)
            {
                ata_port = i;
                break;
            }
        }
    }
    for (i = 0; i < AHCI_MAX_PORTS; i++)
    {
        if (PCIBt(&blkdev.ahci_hba->ports_implemented, i))
        {
            port = &blkdev.ahci_hba->ports[i];
            if (port->signature == AHCI_PxSIG_ATAPI)
            {
                atapi_port = i;
                break;
            }
        }
    }

    if (VMPartDisk(task, ata_port))
    {
        VMInstallDrive(task, 'C', ata_port, atapi_port);
        VMInstallDrive(task, 'D', ata_port, atapi_port);
        if (!sys_is_uefi_booted)
        {
            if (FileFind(LIMINE_HDD_FILE))
            {
                "\n\n"
                "" LIMINE_INSTALL_PROMPT;
                "(Z/L): ";

                do
                    ch = ToUpper(CharGet(, FALSE));
                while (ch != 'Z' && ch != 'L');

                "%c\n", ch;

                if (ch == 'Z')
                {
                    BootMHDIns('C');
                    "\n(Generating optional UEFI-mode Limine.CFG...)\n";
                    LimineCFGMake('C');
                }
                else
                {
                    LimineMHDIns('C');
                }
                "\n\n";
            }
            else
                BootMHDIns('C');
        }
        else
            LimineCFGMake('C');

    }

    WinVert(task->win_top, Fs->win_bottom);
    Kill(task);
}

U0 RegularInstallWiz()
{
    I64      drv_let;
    U8      *st, *port_st;
    I64      ch;
    CTask   *task;

    task = User;
    TaskWait(task);
    task->border_src    = BDS_CONST;
    task->border_attr   = LTGRAY << 4 + DriveTextAttrGet(':') & 15;
    task->text_attr     = LTGRAY << 4 + BLUE;
    task->win_inhibit   = WIG_TASK_DEFAULT - WIF_SELF_BORDER;
    WinHorz(Fs->win_left, Fs->win_right, task);
    WinVert(Fs->win_top,  (Fs->win_top + Fs->win_bottom) >> 2 - 1, task);
    WinVert(task->win_bottom + 3, Fs->win_bottom);
    WinToTop(Fs);

    XTalk(task, "Mount;\nC\n");

    "\nSelect the port of the ATA drive to install on.\n";

    "Hard Drive Port: ";
    while (TRUE)
    {
        port_st = StrGet;
        if ((0 <= Str2I64(port_st) < AHCI_MAX_PORTS) &&
            ((&blkdev.ahci_hba->ports[Str2I64(port_st)])->signature == AHCI_PxSIG_ATA))
        {
            break;
        }
        Free(port_st);
    }

    XTalkWait(task, "%s\n\n", port_st);

    DriveRep;
    do
    {
        st = StrGet("\nDestination Partition Letter: ");
        if (*st)
            drv_let = Letter2Letter(*st);
        else
            drv_let = 0;
        Free(st);
    }
    while (!('A' <= drv_let <= 'Z'));

    '\n';

    "$RED$Format %C Partition?$FG$\n", drv_let;
    if (YorN)
    {
        '\n';
        do
        {
            "$PURPLE$1$FG$) Use FAT32\n"
            "$PURPLE$2$FG$) Use RedSea\n"
            "\nFile System Type: ";
            ch = CharGet;
            '\n';
        }
        while (!('1' <= ch <= '2'));

        if (ch == '1')
            Format(drv_let,, FALSE, FSt_FAT32);
        else
            Format(drv_let,, FALSE, FSt_REDSEA);
    }
    InstallDrive(drv_let);


    XTalkWait(task, "BootHDIns('%C');\n\nB\n0x20000\nC\n%s\n\n", drv_let, port_st);
    XTalkWait(task, "\n\n"); //skip through Disk Cache, Options
    if (!sys_is_uefi_booted)
    {
        "$RED$Install Master Boot loader?$FG$";
        if (YorN)
        {
            "\n\n";
            if (FileFind(LIMINE_HDD_FILE))
            {
                "" LIMINE_INSTALL_PROMPT;
                "(Z/L): ";

                do
                    ch = ToUpper(CharGet(, FALSE));
                while (ch != 'Z' && ch != 'L');


                "%c\n", ch;

                if (ch == 'Z')
                {
                    BootMHDIns(drv_let);
                    "\n(Generating optional UEFI-mode Limine.CFG...)\n";
                    LimineCFGMake(drv_let); // ensures we don't leave the LiveCD's Limine.CFG on the HDD
                }
                else
                {
                    LimineMHDIns(drv_let);
                }
                "\n\n";
            }
            else
                BootMHDIns(drv_let);
        }
    }
    else
        LimineCFGMake(drv_let);

    WinVert(task->win_top, Fs->win_bottom);
    Kill(task);
}

U0 DoInstructions()
{
    CTask *task = User;

    AutoComplete;
    WinToTop(Fs);
    WinTileVert;
    XTalk(task, "Ed(\"::/Doc/Install.DD\");\n");
}

Bool DoInstall(Bool prompt_reboot)
{
    I64                  res = FALSE, vm_install = TRUE, ch;
    CSMBIOSSystemInfo   *sys_info = SMBIOSStructGet(SMBIOSt_SYSTEM);
    U8                  *company = SMBIOSStr(sys_info, sys_info->manufacturer);

    if (StrCompare(company, "VMware, Inc.") && StrCompare(company, "innotek GmbH") && StrCompare(company, "QEMU"))
    {
        "\n\n\n\n\nAre you installing inside VMware, QEMU, VirtualBox or a similar virtual machine? ";
        vm_install = YorN;
    }
    DocBottom;
    if (vm_install)
    {
        "\n\nUpgrade an existing install,"
        "\nor create new Installation? (U/I): ";
        do
            ch = ToUpper(CharGet(, FALSE));
        while (ch != 'U' && ch != 'I');

        "%c\n", ch;

        if (ch == 'U')
            OSUpgrade;
        else
            VMInstallWiz();

        res = TRUE;
    }
    else
    {
        "\n\nThis wizard works if you have a partition ready. You can partition the drive or BootHDIns() "
                "with more options if you do it by hand, not using this wizard.\n\n"
                "Continue Install Wizard ";
        if (YorN)
        {
            "\n\nUpgrade an existing install,"
            "\nor create new Installation? (U/I): ";
            do
                ch = ToUpper(CharGet(, FALSE));
            while (ch != 'U' && ch != 'I');

            "%c\n", ch;

            if (ch == 'I')
            {
                RegularInstallWiz();
                res = TRUE;
            }
            else
            {
                OSUpgrade;
                res = TRUE;
            }
        }
        else
            prompt_reboot = FALSE;
    }
    if (prompt_reboot)
    {
        "Reboot Now ";
        if (YorN)
        {
            DiscEject(':');
            Reboot;
        };
    }
    return res;
}

Bool OSInstall(Bool prompt_reboot=TRUE)
{
    DoInstructions;

    return DoInstall(prompt_reboot);
}

#if __CMD_LINE__
OSInstall(TRUE);
#endif