// Console input and output.
// Input is from the keyboard or serial port.
// Output is written to the screen and serial port.

#include "types.h"
#include "defs.h"
#include "param.h"
#include "traps.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "x86.h"

#define INPUT_BUF 128
// Proc title length + 3 for the console id + 2 for the left and right border + 2 for space between
#define MENUWIDTH 27
// Number of max consoles + 1 for bottom border and another 2 for a space between top and bottom
#define MENUHEIGHT (MAXVCONSOLES + 3);
// Vars to hold the current menu item and menu active bool
static int currentmenuitem = 0;
static int menuactive = 0;

struct kbdbuffer
{
    char buf[INPUT_BUF];
    uint r; // Read index
    uint w; // Write index
    uint e; // Edit index
};

struct kbdbuffer inputBuffer;

struct kbdbuffer *input = 0;

// Struct to hold the variables needed for a virtual console
struct vconsole
{
    uint consoleindex;
    struct spinlock lock;
    struct proc *processowner;
    ushort screenbuffer[SCRWIDTH * SCRHEIGHT];
    struct kbdbuffer keybuffer;
    int pos;
    int active;
    int inuse;
    // Variables for the title bar, this can be manual set and locked, if not the current process is used
    uint titlebgcol;
    char proctitle[20];
    int titlelocked;
    // Status for possible future use with the title bar to allow a proccess to set messages to print
    char status[30];
};

struct vconsole consoles[MAXVCONSOLES];
struct vconsole *currentconsole = 0;

#define C(x) ((x) - '@') // Control-x

struct vconsole* getvalidprocessconsoleptr(void);
void clearconsole(ushort *bufferin);
void loadscreenbuffer(ushort *bufferin);
void savescreenbuffer(ushort *bufferin);
void clearscreen(int prelocked);

static void consputc(int);

static int panicked = 0;

static struct
{
    struct spinlock lock;
    int locking;
} cons;

// Lock for all consoles (to lock when doing switching/creating?)
static struct
{
    struct spinlock lock;
    int locking;
} vcons;

// Static function for getting the current count of active consoles
static int getcurrentactiveconsolecount(void)
{
    int resultcount = 0;
    for (int i = 0; i < MAXVCONSOLES; i++)
    {
        if (consoles[i].active)
        {
            resultcount++;
        }
    }

    return resultcount;
}

// Function to get the menu index of an active console because they might be offset
static int getconsolemenuindex(struct vconsole* consolein)
{
    int resultcount = 0;
    for (int i = 0; i < MAXVCONSOLES; i++)
    {
        if (&consoles[i] == consolein && consoles[i].active)
        {            
            break;
        }
        else
        {
            resultcount++;
        }
    }

    return resultcount;
}

// Function for getting the console at the given menu index
static int getmenuindexconsole(int menuindex)
{
    int resultcount = 0;
    int resultconsoleindex = 0;
    for (int i = 0; i < MAXVCONSOLES; i++)
    {
        if (consoles[i].active)
        {   
            if (resultcount == menuindex)
            {                
                resultconsoleindex = consoles[i].consoleindex;
                break;
            }
            else
            {
                resultcount++;
            }
        }        
    }

    return resultconsoleindex;
}

// Function for getting a consoles index
int getconsoleindex(struct vconsole* consolein)
{
    return consolein->consoleindex;
}

// Split the printint so we can just get the char out in draw title
// itoa is the function in the c standard library (?) so i reused the name
// since it does something similar
// Saves to the provided char array and returns the number of characters for looping
static int itoa(int xx, int base, int sign, char* buf)
{
    static char digits[] = "0123456789abcdef";    
    int i;
    uint x;

    if (sign && (sign = xx < 0))
    {
        x = -xx;
    }
    else
    {
        x = xx;
    }

    i = 0;
    do
    {
        buf[i++] = digits[x % base];
    } while ((x /= base) != 0);

    if (sign)
    {
        buf[i++] = '-';
    }

    return i;
}

// Function for printing an integer as a string to the screen
static void printint(int xx, int base, int sign)
{
    int i = 0;
    char buf[16];
    i = itoa(xx, base, sign, buf);

    while (--i >= 0)
    {
        consputc(buf[i]);
    }
}

// Print to the console. only understands %d, %x, %p, %s.
void cprintf(char *fmt, ...)
{
    int i, c, locking;
    uint *argp;
    char *s;

    struct vconsole* inconsoleptr = getvalidprocessconsoleptr();
    
    locking = cons.locking;
    if (locking)
    {
        //acquire(&cons.lock);
        acquire(&inconsoleptr->lock);
    }

    if (fmt == 0)
    {
        panic("null fmt");
    }

    argp = (uint *)(void *)(&fmt + 1);
    for (i = 0; (c = fmt[i] & 0xff) != 0; i++)
    {
        if (c != '%')
        {
            consputc(c);
            continue;
        }
        c = fmt[++i] & 0xff;
        if (c == 0)
        {
            break;
        }
        switch (c)
        {
        case 'd':
            printint(*argp++, 10, 1);
            break;
        case 'x':
        case 'p':
            printint(*argp++, 16, 0);
            break;
        case 's':
            if ((s = (char *)*argp++) == 0)
            {
                s = "(null)";
            }
            for (; *s; s++)
            {
                consputc(*s);
            }
            break;
        case '%':
            consputc('%');
            break;
        default:
            // Print unknown % sequence to draw attention.
            consputc('%');
            consputc(c);
            break;
        }
    }

    if (locking)
    {
        //release(&cons.lock);
        release(&inconsoleptr->lock);
    }
}

// Function to provide the same string manipulation as cprint but saving to the given buffer so it can be used without printing to the screen
// sprintf is the c function that normally does this so i used the same name here
void sprintf(char* strbuffer, char *fmt, ...)
{
    int i, c, si;
    uint *argp;
    char *s;
    
    char buf[16];
    int x;

    if (fmt == 0)
    {
        panic("null fmt");
    }

    si = 0;
    argp = (uint *)(void *)(&fmt + 1);
    for (i = 0; (c = fmt[i] & 0xff) != 0; i++)
    {
        if (c != '%')
        {
            strbuffer[si] = c;
            si++;
            continue;
        }
        c = fmt[++i] & 0xff;
        if (c == 0)
        {
            break;
        }
        switch (c)
        {
        case 'd':
            // Clear any existing buf data
            memset(buf, 0, sizeof buf);            
            x = itoa(*argp++, 10, 1, buf);

            while (--x >= 0)
            {
                strbuffer[si] = buf[x];
                si++;
            }
            break;
        case 'x':
        case 'p':            
            // Clear any existing buf data
            memset(buf, 0, sizeof buf);
            x = itoa(*argp++, 16, 0, buf);

            while (--x >= 0)
            {
                strbuffer[si] = buf[x];
                si++;
            }
            break;
        case 's':
            if ((s = (char *)*argp++) == 0)
            {
                s = "(null)";
            }
            for (int sli = 0; sli < strlen(s); sli++)
            {
                strbuffer[si] = s[sli];
                si++;
            }
            break;
        case '%':
            strbuffer[i] = '%';
            si++;
            break;
        default:
            // Print unknown % sequence to draw attention.
            strbuffer[si] = '%';
            si++;
            strbuffer[si] = c;
            si++;
            break;
        }
    }
    // Add string terminator to the end
    strbuffer[si] = '\0';
}

void panic(char *s)
{
    int i;
    uint pcs[10];

    cli();
    cons.locking = 0;
    // use lapiccpunum so that we can call panic from mycpu()
    cprintf("lapicid %d: panic: ", lapicid());
    cprintf(s);
    cprintf("\n");
    getcallerpcs(&s, pcs);
    for (i = 0; i < 10; i++)
    {
        cprintf(" %p", pcs[i]);
    }
    panicked = 1; // freeze other CPU
    for (;;)
    {
        ;
    }
}

// function that checks and returns a valid vconsole pointer, since i was repeating the same checks i moved it
// into its own function to keep things looking clean
struct vconsole* getvalidprocessconsoleptr(void) 
{
    struct vconsole* inconsoleptr = 0;
    
    // Check if a process has actually been created otherwise use the base console
    if (myproc() == 0x0)
    {
        inconsoleptr = currentconsole;
        if (inconsoleptr == 0)
        {
            // fall back to the base console if the process and currentconsole has no console assigned for some reason
            inconsoleptr = &consoles[0];
        }
    }
    else
    {
        inconsoleptr = myproc()->consoleptr;
        if (inconsoleptr == 0)
        {
            // fall back to the console set in currentconsole 
            inconsoleptr = currentconsole;
            if (inconsoleptr == 0)
            {
                // fall back to the base console if the process and currentconsole has no console assigned for some reason
                inconsoleptr = &consoles[0];
            }
        }
    }
    
    return inconsoleptr;
}

#define BACKSPACE 0x100
#define CRTPORT 0x3d4
#define TITLEOFF (SCRWIDTH * 1) // Size of the offset we need for the title bar

static ushort *crt = (ushort *)P2V(0xb8000); // CGA memory

// Function for drawing the menu onto the screen
static void drawmenu(void)
{
    struct vconsole* inconsoleptr = getvalidprocessconsoleptr();
    // Rows = the number of active consoles + 1 for the padding at the top, 1 for the padding at the bottom and 1 for the border
    int rows = getcurrentactiveconsolecount() + 3;
    int pos = TITLEOFF;
    uint c = 0;
    // Small buffer to hold the menu memory
    char menulines[MAXVCONSOLES][24];

    // Setup our menu item line strings
    int menuindex = 0;
    for (int i = 0; i < MAXVCONSOLES; i++)
    {
        if (consoles[i].active)
        {            
            memset(menulines[menuindex], 0, sizeof(menulines[i][0]) * sizeof(menulines[i]));
            sprintf(menulines[menuindex], "%d: %s", consoles[i].consoleindex, consoles[i].proctitle);
            if (consoles[i].inuse)
            {
                currentmenuitem = menuindex;
            }
            menuindex++;
        }
    }

    // Draw the rows for the menu
    for (int y = 0; y < rows; y++)
    {    
        pos = TITLEOFF + (y * SCRWIDTH);    
        for (int x = 0; x < MENUWIDTH; x++)
        {
            // Top line and the 2nd to last line are padding, we just draw spaces and the first and last x are the border character
            if (y == 0 || y == (rows - 2))
            {
                if (x == 0 || x == (MENUWIDTH - 1))
                {
                    c = 0xB2 | inconsoleptr->titlebgcol;
                }                
                else
                {
                    c = 0x0720;
                }
            }            
            // The last row is the bottom border, so we can just fill it with the border character
            else if (y == (rows - 1))
            {
                c = 0xB2 | inconsoleptr->titlebgcol;
            }
            // Otherwise we draw the border chars and padding space with the console menu item string between
            else
            {
                if (x == 0 || x == (MENUWIDTH - 1))
                {
                    c = 0xB2 | inconsoleptr->titlebgcol;
                }  
                else if (x == 1 || x == (MENUWIDTH - 2))
                {
                    c = 0x0720;
                }
                else
                {
                    c = menulines[y - 1][x - 2];
                    c = (c & 0xff);
                    if (currentmenuitem == (y - 1))
                    {
                        c = c | 0xCF00;
                    }
                    else
                    {
                        c = c | 0x0700;
                    }
                }
                
            }
            crt[pos] = c;
            pos++;
        }        
    }

    menuactive = 1;
}

// Function that handles the moving of the highlight bar for the menu
static void navigatemenu(int dir)
{
    int pos = 0;    
    int newindex = currentmenuitem + dir;
    int totalactiveconsoles = getcurrentactiveconsolecount();

    if (newindex < 0)
    {
        newindex = 0;
    }
    else if (newindex >= totalactiveconsoles)
    {
        newindex = totalactiveconsoles - 1;
    }

    // make sure the new index isnt just the same as the current selected menu item, we then change the background bits around 
    // which saves us from redrawing the menu every time the selected item changes
    if (newindex != currentmenuitem)
    {
        // change the background bits of the last selected item to black
        pos = TITLEOFF + ((currentmenuitem + 1) * SCRWIDTH);
        for (int x = 2; x < MENUWIDTH - 2; x++)
        {
            crt[pos + x] = (crt[pos + x] & 0x00FF) | 0x0700;
        }

        // change the background bits of the new selected item to the highlight color
        pos = TITLEOFF + ((newindex + 1) * SCRWIDTH);
        for (int x = 2; x < MENUWIDTH - 2; x++)
        {
            crt[pos + x] = crt[pos + x] | 0xCF00;
        }

        currentmenuitem = newindex;    
    }
}

// Function that closes the menu and reloads the buffer into screen memory
static void closemenu(void)
{    
    loadscreenbuffer(currentconsole->screenbuffer);

    int pos = currentconsole->pos;

    outb(CRTPORT, 14);
    outb(CRTPORT + 1, pos >> 8);
    outb(CRTPORT, 15);
    outb(CRTPORT + 1, pos);
    menuactive = 0;
}

// Function for drawing the title bar of the current console
static void drawtitle(void)
{
    struct vconsole* inconsoleptr = getvalidprocessconsoleptr();
           
    int pos = 0;
    char c = ' ';
    // Setup the title bar strings to print
    char menutext[] = "(M)enu";
    char consoletitletext[10]; // + (inconsoleptr->consoleindex + 1);
    sprintf(consoletitletext, "Console %d", inconsoleptr->consoleindex);
                
    // Loop through the top row and write the current character to the screen buffer
    for (pos = 0; pos < SCRWIDTH; pos++)
    {
        int offset = 0;
        // Print the menu text
        if (pos < 6) {
            c = menutext[pos - offset];
        }
        // Print the console title
        else if (pos >= 10 && pos < 20)
        {
            offset = 10;
            c = consoletitletext[pos - offset];
        }
        // Print the process/title name
        else if (pos >= 25 && pos < 45)
        {
            offset = 25;                
            c = inconsoleptr->proctitle[pos - offset];                
        }
        // Print the status (not current in use)
        else if (pos >= 50 && pos < 80)
        {
            offset = 50;
            c = inconsoleptr->status[pos - offset];
        }
        // Otherwise print a space character so we still get the background
        else
        {
            c = ' ';
        }

        // Print the actual character with the given bg and fg color settings
        if (inconsoleptr->inuse)
        {     
            crt[pos] = (c & 0xff) | inconsoleptr->titlebgcol;
        }        
        inconsoleptr->screenbuffer[pos] = (c & 0xff) | inconsoleptr->titlebgcol;
    }
}

// Function for printing a character to the screen and/or the current console buffer
static void cgaputc(int c)
{
    int pos;
    struct vconsole* inconsoleptr = getvalidprocessconsoleptr();
    
    ushort *currentbuffer = inconsoleptr->screenbuffer;

    // if the console is inuse we get the hardware cursor position so we can use it
    if (inconsoleptr->inuse)
    {
        // Cursor position: col + 80*row.
        outb(CRTPORT, 14);
        pos = inb(CRTPORT + 1) << 8;
        outb(CRTPORT, 15);
        pos |= inb(CRTPORT + 1);
    }
    // If the console is not in use we use the position saved to the console struct
    else
    {
        pos = inconsoleptr->pos;
    }

    if (c == '\n')
    {
        pos += SCRWIDTH - pos % SCRWIDTH;
    }
    else if (c == BACKSPACE)
    {
        // Check that the pos is not in the title offset so they cant remove it
        if (pos > (TITLEOFF))
        {
            currentbuffer[pos] = 0; // Clear the character from the buffer
            --pos;
        }
    }
    else
    {
        // If the console is inuse print the character to the screen
        if (inconsoleptr->inuse)
        {
            crt[pos] = (c & 0xff) | 0x0700; // black on white
        }
        // Save the current character to the console buffer
        currentbuffer[pos] = (c & 0xff) | 0x0700; // black on white
        pos++;
    }
    // Check that the pos is not in the title area or greater than the screen size
    if (pos < TITLEOFF || pos > SCRHEIGHT * SCRWIDTH)
    {
        panic("pos under/overflow");
    }

    // Handle the overflow scrolling for the screen and buffer
    if ((pos / 80) >= 24)
    { // Scroll up.
        memmove(currentbuffer + TITLEOFF, currentbuffer + (SCRWIDTH + TITLEOFF), sizeof(crt[0]) * (SCRHEIGHT - 1) * SCRWIDTH);
        pos -= 80;
        memset(currentbuffer + pos, 0, sizeof(crt[0]) * (SCRHEIGHT * SCRWIDTH - pos));
        if (inconsoleptr->inuse)
        {
            // Check if the menu is active, if so we want to offset the scrolling start pos so we dont overwrite the menu in screen memory
            int menuoffset = 0;
            if (menuactive)
            {
                int menuitems = getcurrentactiveconsolecount() + 3;
                for (int y = 0; y < menuitems; y++)
                {
                    memmove(crt + menuoffset + TITLEOFF + MENUWIDTH, crt + menuoffset + (SCRWIDTH + TITLEOFF) + MENUWIDTH, sizeof(crt[0]) * (SCRWIDTH - MENUWIDTH));
                    menuoffset += SCRWIDTH;
                }
            }
            memmove(crt + TITLEOFF + menuoffset, crt + (SCRWIDTH + TITLEOFF) + menuoffset, (sizeof(crt[0]) * (SCRHEIGHT - 1) * SCRWIDTH) - menuoffset);
            memset(crt + pos, 0, sizeof(crt[0]) * (SCRHEIGHT * SCRWIDTH - pos));
        }
    }

    // Set the consoles pos to the new pos, if the console was in use also save the hardware cursor pos
    inconsoleptr->pos = pos;

    if (inconsoleptr->inuse)
    {
        outb(CRTPORT, 14);
        outb(CRTPORT + 1, pos >> 8);
        outb(CRTPORT, 15);
        outb(CRTPORT + 1, pos);
        crt[pos] = ' ' | 0x0700;
    }
}

void consputc(int c)
{
    if (panicked)
    {
        cli();
        for (;;)
        {
            ;
        }
    }

    if (c == BACKSPACE)
    {
        uartputc('\b');
        uartputc(' ');
        uartputc('\b');
    }
    else
    {
        uartputc(c);
    }
    cgaputc(c);
}

int consoleget(void)
{
    //struct vconsole* inconsoleptr = getvalidprocessconsoleptr();    
    int c;
    
    acquire(&cons.lock);
    
    while ((c = kbdgetc()) <= 0)
    {
        if (c == 0)
        {
            c = kbdgetc();
        }
    }

    release(&cons.lock);   

    return c;
}

void consoleintr(int (*getc)(void))
{
    int c;
    int doprocdump = 0;
    int doconsoleswitch = 0;
    int switchto = -1;
    int doconsolehome = 0;

    struct vconsole* inconsoleptr = getvalidprocessconsoleptr();

    acquire(&cons.lock);    
    struct kbdbuffer* consolekbdbuffer = &currentconsole->keybuffer;
    
    while ((c = getc()) >= 0)
    {
        switch (c)
        {         
        // Hotkeys for dealing with the menu
        // Close the menu
        case 27:
            if (menuactive)
            {                
                closemenu();
            }
            break;
        // Menu nav to the previous item
        // Up arrow (found in kbd.h)
        case 0xE2:
            if (menuactive)
            {                
                navigatemenu(-1);
            }
            break;
        // Menu nav to the next item
        // Down arrow (found in kbd.h)
        case 0xE3:
            if (menuactive)
            {                
                navigatemenu(1);
            }
            break;
        case C('P'): // Process listing.
            // procdump() locks cons.lock indirectly; invoke later
            doprocdump = 1;
            break;
        case C('U'): // Kill line.
            while (consolekbdbuffer->e != consolekbdbuffer->w &&
                   consolekbdbuffer->buf[(consolekbdbuffer->e - 1) % INPUT_BUF] != '\n')
            {
                consolekbdbuffer->e--;
                consputc(BACKSPACE);
            }
            break;
        case C('H'):
        case '\x7f': // Backspace
            if (consolekbdbuffer->e != consolekbdbuffer->w)
            {
                consolekbdbuffer->e--;
                consputc(BACKSPACE);
            }
            break;
        // Hotkey to switch to the next console
        case C('T'):
            doconsoleswitch = 1;
            break;
        // Hotkey to switch to the home console
        case C('K'):
            doconsolehome = 1;
            break;
        // Hotkey open the menu
        case C('M'):
            if (!menuactive)
            {
                drawmenu();
            }
            else 
            {
                closemenu();
            }
            break;
        // If enter is hit and the menu is active then we want to override the normal behaviour
        case 10:
            if (menuactive)
            {                
                closemenu();
                switchto = currentmenuitem;
                doconsoleswitch = 1;
                // The menu has been sorted we can break from the switch
                break;
            }
        // if the menu was not active we want the enter to be handled in the default case
        default:
            if (!menuactive)
            {
                if (inconsoleptr->inuse)
                {
                    if (c != 0 && consolekbdbuffer->e - consolekbdbuffer->r < INPUT_BUF)
                    {
                        c = (c == '\r') ? '\n' : c;
                        consolekbdbuffer->buf[consolekbdbuffer->e++ % INPUT_BUF] = c;
                        consputc(c);
                        if (c == '\n' || c == C('D') || consolekbdbuffer->e == consolekbdbuffer->r + INPUT_BUF)
                        {
                            consolekbdbuffer->w = consolekbdbuffer->e;
                            wakeup(&(consolekbdbuffer->r));
                        }
                    }
                }
            }
            break;
        }
    }
    release(&cons.lock);    

    if (doprocdump && !menuactive)
    {
        procdump(); // now call procdump() wo. cons.lock held
    }
    // Handle the home console hotkey
    if (doconsolehome)
    {
        if (currentconsole->consoleindex != 0)
        {
            switchtoconsole(getbaseconsoleptr());
        }
    }
    // Handle the console switch hotkey
    if (doconsoleswitch)
    {
        struct vconsole* toconsole = 0;
        acquire(&vcons.lock);
        acquire(&cons.lock);
        if (switchto == -1)
        {            
            for (int i = (currentconsole->consoleindex  + 1); i < MAXVCONSOLES; i++)
            {
                if (consoles[i].active)
                {
                    toconsole = &consoles[i];
                    break;
                }
            }
            if (toconsole == 0)
            {
                toconsole = getbaseconsoleptr();
            }
        }
        else
        {
            toconsole = &consoles[getmenuindexconsole(switchto)];
        }
        release(&cons.lock);
        release(&vcons.lock);
        if (!toconsole->inuse)
        {
            switchtoconsole(toconsole);
        }
    }
}

int consoleread(struct inode *ip, char *dst, int n)
{
    uint target;
    int c;
    struct vconsole* inconsoleptr = getvalidprocessconsoleptr();
    struct kbdbuffer* consolekbdbuffer = &inconsoleptr->keybuffer;

    iunlock(ip);
    target = n;    
    acquire(&inconsoleptr->lock);
    while (n > 0)
    {   
            while (consolekbdbuffer->r == consolekbdbuffer->w)
            {
                if (myproc()->killed)
                {
                    release(&inconsoleptr->lock);
                    ilock(ip);
                    return -1;
                }
                sleep(&(consolekbdbuffer->r), &inconsoleptr->lock);
            }
            c = consolekbdbuffer->buf[consolekbdbuffer->r++ % INPUT_BUF];
            if (c == C('D'))
            { // EOF
                if (n < target)
                {
                    // Save ^D for next time, to make sure
                    // caller gets a 0-byte result.
                    consolekbdbuffer->r--;
                }
                break;
            }
            *dst++ = c;
            --n;
            if (c == '\n')
            {
                break;
            }        
    }    
    release(&inconsoleptr->lock);
    ilock(ip);

    return target - n;
}

int consolewrite(struct inode *ip, char *buf, int n)
{
    int i;
    struct vconsole* inconsoleptr = getvalidprocessconsoleptr();

    iunlock(ip);    
    acquire(&inconsoleptr->lock);
    for (i = 0; i < n; i++)
    {
        consputc(buf[i] & 0xff);
    }                    
    release(&inconsoleptr->lock);
    ilock(ip);

    return n;
}

// Function to flood a buffer with A-Z characters looping (for testing initially)
void testfillbuffer(ushort *bufferin)
{
    ushort firstchar = 65;
    ushort lastchar = 90;
    ushort currentchar = firstchar;

    for (int i = 0; i < (SCRHEIGHT * SCRWIDTH); i++)
    {
        if (currentchar > lastchar)
        {
            currentchar = firstchar;
        }

        bufferin[i] = (currentchar & 0xff) | 0x0700;

        currentchar++;
    }
}

// Function to get a pointer to the base console
struct vconsole* getbaseconsoleptr(void)
{
    return &consoles[0];
}

// Function to clear a given buffer using its pointer/address
void clearconsole(ushort *bufferin)
{
    // Flood the screen buffer with blank spaces
    memset(bufferin, 0, sizeof(bufferin[0]) * SCRHEIGHT * SCRWIDTH);    
}

// Function for loading an entire buffer into the screen memory using its pointer/address
void loadscreenbuffer(ushort *bufferin)
{
    // Copy the memory from the console buffer to the crt buffer
    memmove(crt, bufferin, sizeof(bufferin[0]) * SCRHEIGHT * SCRWIDTH);
}

// Function for saving the current screen memory into a given buffer
void savescreenbuffer(ushort *bufferin)
{
    // Copy the memory from the console buffer to the crt buffer
    memmove(bufferin, crt, sizeof(crt[0]) * SCRHEIGHT * SCRWIDTH);
}

// Function for clearing the current screen/buffer
void clearscreen(int prelocked)
{   
    // Check if a process has actually been created otherwise use the base console    
    struct vconsole* inconsoleptr = getvalidprocessconsoleptr();

    if (!prelocked)
    {
        acquire(&cons.lock);
    }
    int pos = TITLEOFF;
    clearconsole(inconsoleptr->screenbuffer);        
    inconsoleptr->pos = pos;
    
    // If the console is in use load the blank buffer into screen memory and set the cursor position
    if (inconsoleptr->inuse)
    {        
        loadscreenbuffer(inconsoleptr->screenbuffer);                        
        
        outb(CRTPORT, 14);
        outb(CRTPORT + 1, pos >> 8);
        outb(CRTPORT, 15);
        outb(CRTPORT + 1, pos);
    }    

    // Draw the title bar onto the console
    drawtitle();
    if (!prelocked)
    {
        release(&cons.lock);    
    }
}

void consoleinit(void)
{
    initlock(&cons.lock, "console");
    initlock(&vcons.lock, "vconglobal");

    // Init all of the virtual console buffers
    for (int i = 0; i < MAXVCONSOLES; i++) 
    {
        consoles[i].consoleindex = i;
        consoles[i].processowner = 0;
        consoles[i].pos = 0;        
        consoles[i].active = 0;
        consoles[i].inuse = 0;
        consoles[i].titlebgcol = 0x4F00;
        
        char lockname[8];
        sprintf(lockname, "vconsole%d", i);
        initlock(&consoles[i].lock, lockname);
    }

    // Initialise pointer to point to our console input buffer
    currentconsole = &consoles[0];
    currentconsole->active = 1;
    currentconsole->inuse = 1;    
    input = &currentconsole->keybuffer;

    devsw[CONSOLE].write = consolewrite;
    devsw[CONSOLE].read = consoleread;
    cons.locking = 1;

    // Clear the screen and print the welcome message
    clearscreen(0);
    cprintf("Welcome! you are currently in the base console\n");

    ioapicenable(IRQ_KBD, 0);
}

// Function for "creating" a new console, in reality it sets the first non active console to active and setups the struct
// if none are free it returns a nullptr/zero for the calling function to handle
struct vconsole* newconsole(char* title, int bgpreset)
{
    struct vconsole* result = 0;

    acquire(&vcons.lock);
    for (int i = 1; i < MAXVCONSOLES; i++)
    {
        if (!consoles[i].active)
        {
            consoles[i].processowner = myproc();
            consoles[i].titlebgcol = bgpreset;

            if (strlen(title) > 0)
            {
                // Clear any text in the proc title incase this was a pre used console
                memset(consoles[i].proctitle, 0, sizeof(consoles[i].proctitle[0]) * sizeof(consoles[i].proctitle));
                safestrcpy(consoles[i].proctitle, title, sizeof(consoles[i].proctitle));
                consoles[i].titlelocked = 1;
            }
            else
            {
                consoles[i].titlelocked = 0;
            }

            result = &consoles[i];
            break;
        }
    }
    release(&vcons.lock);

    return result;
}

// Function to "release" all consoles from inuse, this is to stop the chance of one not clearing and causing an issue with 2 being inuse
void releaseallconsoles(void) 
{
    for (int i = 0; i < MAXVCONSOLES; i++)
    {
        consoles[i].inuse = 0;
    }
}

// Function for switching to another console using the given pointer
int switchtoconsole(struct vconsole* consoleptr)
{
    int pos;
        
    acquire(&vcons.lock);          
    // Release all consoles before setting the new one to inuse  
    releaseallconsoles();
    currentconsole = consoleptr;
    currentconsole->inuse = 1;
    
    acquire(&cons.lock);
    // Load the next console into screen memory
    loadscreenbuffer(currentconsole->screenbuffer);
    
    // Checks if the console was active, if not its most likely the first switch to this so we clear and print the welcome message
    if (!currentconsole->active)
    {
        clearscreen(1);        
        cprintf("Welcome to Console: %d\n", currentconsole->consoleindex);
        currentconsole->active = 1;
    }
    else
    {        
        // If it was already active we just set the cursor position to the one saved in the console struct
        pos = currentconsole->pos;

        outb(CRTPORT, 14);
        outb(CRTPORT + 1, pos >> 8);
        outb(CRTPORT, 15);
        outb(CRTPORT + 1, pos);
    }

    // Remove the menu if it was active and set the console as the current menu item  
    menuactive = 0;
    currentmenuitem = getconsolemenuindex(currentconsole);
    release(&cons.lock);
    release(&vcons.lock);            
    return 0;
}

// Function for closing the current console
int closeconsole(void)
{    
    struct vconsole* consoleptr = myproc()->consoleptr;
    
    acquire(&vcons.lock);
    // Check if the process exiting is the consoles parent process we clear the buffer and set the active to 0
    if (myproc() == consoleptr->processowner)
    {
        clearconsole(consoleptr->screenbuffer);
        consoleptr->active = 0;

        // If the console was in use to switch back to the base console
        if (consoleptr->inuse)
        {
            consoleptr->inuse = 0;
            release(&vcons.lock);
            switchtoconsole(&consoles[0]);
            return 1;
        }
    }
    release(&vcons.lock);
    return 0;
}

// Function for getting the console index of the current inuse console
int getcurrentconsoleindex(void)
{
    acquire(&vcons.lock);
    for (int i = 0; i < MAXVCONSOLES; i++) 
    {
        if (consoles[i].inuse)
        {
            release(&vcons.lock);
            return consoles[i].consoleindex;
        }
    }
    release(&vcons.lock);
    return -1;
}

// Function for setting the title of a console for the title bar and menu
void setconsoleproctitle(struct vconsole* consoleptr, char* newtitle)
{
    if (!consoleptr->titlelocked)
    {
        acquire(&vcons.lock);
        //Clear the current buffer
        memset(consoleptr->proctitle, 0, sizeof(consoleptr->proctitle[0]) * sizeof(consoleptr->proctitle));
        //Copy the new title into the console title buffer
        safestrcpy(consoleptr->proctitle, newtitle, sizeof(consoleptr->proctitle));
        
        //Redraw the title since its been updated now
        drawtitle();
        
        release(&vcons.lock);
    }
}