/*++

Copyright (c) 1992  Microsoft Corporation

Module Name:

    arcdisp.c

Abstract:

    This module provides code for managing screen output on an ARC-compliant
    system.

Author:

    John Vert (jvert) 6-Oct-1993

Revision History:

    John Vert (jvert) 6-Oct-1993
        Taken from old 1.0 splib sources

--*/
#include "setupldr.h"
#include "parseini.h"
#include "stdio.h"
#include "hdlsterm.h"

#ifdef i386
#include "bldrx86.h"
#endif

#if defined(_IA64_)
#include "bldria64.h"
#endif

#if defined(EFI)
#include "bootefi.h"
#endif


//
// The screen is divided into 3 areas: the header area, the status area,
// and the client area.  The header area basically always says "Windows NT
// Setup".  The status area is always 1 line high and displayed using a
// different attribute (black on gray).
//


#define     HEADER_HEIGHT       3
#define     MAX_STATUS          200   // allow up to 1600 horizontal res.

#define     SCREEN_SIZE         (ScreenWidth*ScreenHeight)

BOOLEAN StatusBarEnabled = TRUE;
ULONG ScreenWidth=80;
ULONG ScreenHeight=25;
ULONG ScreenX=0,ScreenY=0;
UCHAR CurAttribute = (ATT_FG_WHITE | ATT_BG_BLACK);
TCHAR MessageBuffer[1024];

extern BOOLEAN ShowProgressBar;

//
// private function prototypes
//
VOID
SlpDrawMenu(
    IN ULONG X,
    IN ULONG Y,
    IN ULONG TopItem,
    IN ULONG Height,
    IN PSL_MENU Menu
    );

VOID
SlpDrawMenuItem(
    IN ULONG X,
    IN ULONG Y,
    IN ULONG TopItem,
    IN ULONG Height,
    IN ULONG Item,
    IN PSL_MENU Menu
    );

VOID
SlpSizeMessage(
    IN PTCHAR Message,
    OUT PULONG Lines,
    OUT PULONG MaxLength,
    OUT ULONG LineLength[],
    OUT PTCHAR LineText[]
    );



PSL_MENU
SlCreateMenu(
    VOID
    )

/*++

Routine Description:

    Allocates and initializes a new menu structure.

Arguments:

    None

Return Value:

    Pointer to the new menu structure if successful.

    NULL on failure

--*/
{
    PSL_MENU p;

    p=BlAllocateHeap(sizeof(SL_MENU));
    if (p==NULL) {
        return(NULL);
    }
    p->ItemCount = 0;
    p->Width = 0;
    InitializeListHead(&p->ItemListHead);
    return(p);
}


BOOLEAN
SlGetMenuItemIndex(
    IN PSL_MENU Menu,
    IN PTCHAR Text,
    OUT PULONG Index
    )

/*++

Routine Description:

    Given the text of a menu item, returns the index of that item.

Arguments:

    Menu - Supplies the menu

    Text - Supplies the text to search for.

    Index - Returns the index of the text in the menu

Return Value:

    TRUE - Item was found.

    FALSE - Item was not found

--*/

{
    ULONG i;
    PSL_MENUITEM Item;

    //
    // Find first item to display
    //
    Item = CONTAINING_RECORD(Menu->ItemListHead.Flink,
                             SL_MENUITEM,
                             ListEntry);

    i=0;
    while ( Item != CONTAINING_RECORD(&Menu->ItemListHead,
                                      SL_MENUITEM,
                                      ListEntry)) {
        if (_tcsicmp(Item->Text,Text)==0) {
            *Index = i;
            return(TRUE);
        }

        Item = CONTAINING_RECORD(Item->ListEntry.Flink,
                                 SL_MENUITEM,
                                 ListEntry);
        ++i;

    }
    return(FALSE);
}


PVOID
SlGetMenuItem(
    IN PSL_MENU Menu,
    IN ULONG Item
    )

/*++

Routine Description:

    Given an item index, returns the data associated with that item.

Arguments:

    Menu - Supplies the menu structure.

    Item - Supplies the item index.

Return Value:

    The data associated with the given item.

--*/

{
    ULONG i;
    PSL_MENUITEM MenuItem;

    //
    // Find item to return
    //
    MenuItem = CONTAINING_RECORD(Menu->ItemListHead.Flink,
                                 SL_MENUITEM,
                                 ListEntry);

    for (i=0;i<Item;i++) {
        MenuItem = CONTAINING_RECORD(MenuItem->ListEntry.Flink,
                                     SL_MENUITEM,
                                     ListEntry);

#if DBG
        if (&MenuItem->ListEntry == &Menu->ItemListHead) {
            SlError(Item);
            return(NULL);
        }
#endif
    }
    return(MenuItem->Data);

}


ULONG
SlAddMenuItem(
    PSL_MENU Menu,
    PTCHAR Text,
    PVOID Data,
    ULONG Attributes
    )

/*++

Routine Description:

    Adds an item to the menu

Arguments:

    Menu - Supplies a pointer to the menu the item will be added to

    Text - Supplies the text to be displayed in the menu

    Data - Supplies a pointer to the data to be returned when the item
           is selected.

    Attributes - Supplies any attributes for the item.

Return Value:

    The Selection index if successful

    -1 on failure

--*/
{
    PSL_MENUITEM NewItem;
    ULONG Length;

    NewItem = BlAllocateHeap(sizeof(SL_MENUITEM));
    if (NewItem==NULL) {
        SlError(0);
        return((ULONG)-1);
    }
    InsertTailList(&Menu->ItemListHead, &NewItem->ListEntry);
    Menu->ItemCount += 1;

    NewItem->Text = Text;
    NewItem->Data = Data;
    NewItem->Attributes = Attributes;
    Length = _tcslen(Text);
    if (Length > Menu->Width) {
        Menu->Width = Length;
    }
    return(Menu->ItemCount - 1);
}


ULONG
SlDisplayMenu(
    IN ULONG HeaderId,
    IN PSL_MENU Menu,
    IN OUT PULONG Selection
    )

/*++

Routine Description:

    Displays a menu and allows the user to pick a selection

Arguments:

    HeaderId - Supplies the message ID of the prompt header
                to be displayed above the menu.

    Menu - Supplies a pointer to the menu to be displayed

    Selection - Supplies the index of the default item.
                Returns the index of the selected item.

Return Value:

    Key that terminated the menu display.

--*/
{
    LONG X, Y;
    ULONG Height;
    ULONG Width;
    ULONG TopItem;
    ULONG c;
    ULONG PreviousSelection;
    ULONG Sel;
    PTCHAR Header;
    ULONG HeaderLines;
    ULONG MaxHeaderLength;
    PTCHAR HeaderText[20];
    ULONG HeaderLength[20];
    ULONG MaxMenuHeight;
    ULONG i;
    ULONG Count;

    Header = BlFindMessage(HeaderId);

    SlpSizeMessage(Header,
                   &HeaderLines,
                   &MaxHeaderLength,
                   HeaderLength,
                   HeaderText);

    X = (ScreenWidth-MaxHeaderLength)/2;
    for (i=0;i<HeaderLines;i++) {
        SlPositionCursor(X,i+4);
        ArcWrite(ARC_CONSOLE_OUTPUT,HeaderText[i],HeaderLength[i]*sizeof(TCHAR),&Count);
    }

    if (MaxHeaderLength > ScreenWidth) {
        MaxHeaderLength = ScreenWidth;
    }

    Width = Menu->Width+4;
    if (Width > ScreenWidth) {
        Width=ScreenWidth;
    }

    MaxMenuHeight = ScreenHeight-(HeaderLines+13);

    Height = Menu->ItemCount+2;
    if (Height > MaxMenuHeight) {
        Height = MaxMenuHeight;
    }

    X = ((ScreenWidth - Width)/2 > 0) ? (ScreenWidth-Width)/2 : 0;
    Y = (MaxMenuHeight - Height)/2 + HeaderLines + 4;

    TopItem = 0;
    Sel = *Selection;
    //
    // Make sure default item is in view;
    //
    if (Sel >= Height - 2) {
        TopItem = Sel - Height + 3;
    }

    SlpDrawMenu(X,Y,
                TopItem,
                Height,
                Menu);

    //
    // highlight default selection
    //
    SlSetCurrentAttribute(INVATT);
    SlpDrawMenuItem(X,Y,
                TopItem,
                Height,
                Sel,
                Menu);
    SlSetCurrentAttribute(DEFATT);
    SlFlushConsoleBuffer();
    do {
        c = SlGetChar();
        PreviousSelection = Sel;
        SlpDrawMenuItem(X, Y,
                    TopItem,
                    Height,
                    Sel,
                    Menu);

        switch (c) {
            case SL_KEY_UP:
                if(Sel > 0) {
                    Sel--;
                }
                break;

            case SL_KEY_DOWN:
                if(Sel < Menu->ItemCount - 1) {
                    Sel++;
                }
                break;

            case SL_KEY_HOME:
                Sel = 0;
                break;

            case SL_KEY_END:
                Sel = Menu->ItemCount - 1;
                break;

            case SL_KEY_PAGEUP:
                if (Menu->ItemCount > Height) {
                    if (Sel > Height) {
                        Sel -= Height;
                    } else {
                        Sel = 0;
                    }
                }
                break;

            case SL_KEY_PAGEDOWN:
                if (Menu->ItemCount > Height) {
                    Sel += Height;
                    if (Sel >= Menu->ItemCount) {
                        Sel = Menu->ItemCount - 1;
                    }
                }
                break;

            case SL_KEY_F1:
            case SL_KEY_F3:
            case ASCI_CR:
            case ASCI_ESC:
                *Selection = Sel;
                return(c);

        }

        if (Sel < TopItem) {
            TopItem = Sel;
            SlpDrawMenu(X, Y,
                        TopItem,
                        Height,
                        Menu);
        } else if (Sel > TopItem+Height-3) {
            TopItem = Sel - Height + 3;
            SlpDrawMenu(X, Y,
                        TopItem,
                        Height,
                        Menu);
        }
        //
        // highlight default selection
        //
        SlSetCurrentAttribute(INVATT);
        SlpDrawMenuItem(X,Y,
                    TopItem,
                    Height,
                    Sel,
                    Menu);
        SlSetCurrentAttribute(DEFATT);


    } while ( TRUE );

}



VOID
SlpDrawMenu(
    IN ULONG X,
    IN ULONG Y,
    IN ULONG TopItem,
    IN ULONG Height,
    IN PSL_MENU Menu
    )

/*++

Routine Description:

    Displays the menu on the screen

Arguments:

    X - Supplies X coordinate of upper-left corner of menu

    Y - Supplies Y coordinate of upper-left corner of menu

    TopItem - Supplies index of item at the top of the menu

    Height - Supplies the height of the menu

    Menu - Supplies the menu to be displayed

Return Value:

    None.

--*/

{
    ULONG i;
    PSL_MENUITEM Item;
    ULONG Count;
    TCHAR Output[80];
    ULONG Length;
    ULONG MenuWidth;

    MenuWidth = Menu->Width+4;
    Output[0]=GetGraphicsChar(GraphicsCharDoubleRightDoubleDown);
    for (i=1;i<MenuWidth-1;i++) {
        Output[i]=GetGraphicsChar(GraphicsCharDoubleHorizontal);
    }
    Output[MenuWidth-1]=GetGraphicsChar(GraphicsCharDoubleLeftDoubleDown);
    SlPositionCursor(X,Y);
    ArcWrite(ARC_CONSOLE_OUTPUT,Output,MenuWidth*sizeof(TCHAR),&Count);
    //
    // Find first item to display
    //
    Item = CONTAINING_RECORD(Menu->ItemListHead.Flink,
                             SL_MENUITEM,
                             ListEntry);

    for (i=0;i<TopItem;i++) {
        Item = CONTAINING_RECORD(Item->ListEntry.Flink,
                                 SL_MENUITEM,
                                 ListEntry);
    }

    //
    // Display items
    //
    Output[0]=
    Output[MenuWidth-1]=GetGraphicsChar(GraphicsCharDoubleVertical);
    for (i=Y+1;i<Y+Height-1;i++) {
#ifdef UNICODE
        for (Count = 0 ; Count < MenuWidth-2;Count++) {
            Output[1+Count] = TEXT(' ');
        }
#else
        RtlFillMemory(Output+1,(MenuWidth-2)*sizeof(TCHAR),' ');
#endif
        SlPositionCursor(X, i);

        if (&Item->ListEntry != &Menu->ItemListHead) {
            Length = _tcslen(Item->Text);
            RtlCopyMemory(Output+2,Item->Text,Length*sizeof(TCHAR));
            Item = CONTAINING_RECORD(Item->ListEntry.Flink,
                                     SL_MENUITEM,
                                     ListEntry);
        }
        ArcWrite(ARC_CONSOLE_OUTPUT,Output,MenuWidth*sizeof(TCHAR),&Count);
    }
    Output[0]=GetGraphicsChar(GraphicsCharDoubleRightDoubleUp);
    for (i=1;i<MenuWidth-1;i++) {
        Output[i]=GetGraphicsChar(GraphicsCharDoubleHorizontal);
    }
    Output[MenuWidth-1]=GetGraphicsChar(GraphicsCharDoubleLeftDoubleUp);
    SlPositionCursor(X,Y+Height-1);
    ArcWrite(ARC_CONSOLE_OUTPUT,Output,MenuWidth*sizeof(TCHAR),&Count);
}


VOID
SlpDrawMenuItem(
    IN ULONG X,
    IN ULONG Y,
    IN ULONG TopItem,
    IN ULONG Height,
    IN ULONG Item,
    IN PSL_MENU Menu
    )

/*++

Routine Description:

    Redraws the given item

Arguments:

    X - Supplies X coordinate of upper-left corner of menu

    Y - Supplies Y coordinate of upper-left corner of menu

    TopItem - Supplies index of item at the top of the menu

    Height - Supplies the height of the menu

    Item - Supplies the index of the item to be redrawn

    Menu - Supplies the menu to be displayed

Return Value:

    None.

--*/

{
    ULONG i;
    PSL_MENUITEM MenuItem;
    ULONG Count;
    TCHAR Width[80];

    //
    // Find item to display
    //
    MenuItem = CONTAINING_RECORD(Menu->ItemListHead.Flink,
                                 SL_MENUITEM,
                                 ListEntry);

    for (i=0;i<Item;i++) {
        MenuItem = CONTAINING_RECORD(MenuItem->ListEntry.Flink,
                                     SL_MENUITEM,
                                     ListEntry);

#if DBG
        if (&MenuItem->ListEntry == &Menu->ItemListHead) {
            SlError(Item);
        }
#endif
    }

#ifdef UNICODE
        for (Count = 0 ; Count < Menu->Width;Count++) {
            Width[Count] = TEXT(' ');
        }
#else
        RtlFillMemory(Width,(Menu->Width)*sizeof(TCHAR),' ');
#endif


    
    RtlCopyMemory(
            Width,
            MenuItem->Text,
            _tcslen(MenuItem->Text)*sizeof(TCHAR) );
    SlPositionCursor(X+2, Y+(Item-TopItem)+1);
    ArcWrite(ARC_CONSOLE_OUTPUT,Width,Menu->Width*sizeof(TCHAR),&Count);
}



VOID
SlInitDisplay(
    VOID
    )

/*++

Routine Description:

    Clears the screen and does some initialization of global variables based
    on the ARC display information.

Arguments:

    None

Return Value:

    None.

--*/

{
    PARC_DISPLAY_STATUS DisplayStatus;

#ifndef EFI
    //
    // Check to see if this version of the ARC firmware is revision 2 or above.
    //
    // If not, we default to 80x25
    //
    if ((SYSTEM_BLOCK->Version > 1) ||
        ((SYSTEM_BLOCK->Version == 1) && (SYSTEM_BLOCK->Revision >= 2))) {

        //
        // Additional checks are required on 1.2 firmware, since some
        // 1.2 firmware does not implement ArcGetDisplayStatus
        //
        if ((SYSTEM_BLOCK->FirmwareVectorLength > (ULONG)GetDisplayStatusRoutine*sizeof(PVOID)) &&
            (SYSTEM_BLOCK->FirmwareVector[GetDisplayStatusRoutine] != NULL)) {
            DisplayStatus = ArcGetDisplayStatus(ARC_CONSOLE_OUTPUT);

            ScreenWidth = DisplayStatus->CursorMaxXPosition;
            ScreenHeight = DisplayStatus->CursorMaxYPosition;
        }
    }

#ifdef ARCI386
    SlPrint(ASCI_CSI_OUT "2J"); // Clears Screen
    
    SlSetCurrentAttribute(DEFATT);
//
//  This is a patch to setup VGA colors in the text port.
//  Otherwise screen colors and attributes are not the same as a PC
//
    // Write all the attributes to the textport
    {
    int row;
    TCHAR    text[MAX_STATUS+1];
    ULONG   Count,Length;
    _stprintf(text,TEXT("                                                                                "));
    //           012345678901234567890123456789012345678901234567890123456789012345678901234567890
    //                     1         2         3         4         5         6         7         8      
    for (row=0;  row< 48; row++)    // compensate for the long textport 48 versus 24 (VGA)
        ArcWrite(ARC_CONSOLE_OUTPUT,text,strlen(&text[0]),&Count);
    }
    // Position cursor at Top Left of screen
    SlPositionCursor(0,0);
#endif

#endif
    
#ifdef EFI
    //
    // On EFI, we won't be redirecting because setupldr.efi is an
    // EFI application.  If that's the case, then we can't use
    // BlIsTerminalConnected().  Instead, we're going to look
    // at the values in the LoaderRedirectionInformation structure
    // to see if we're really redirecting.
    //
    if( LoaderRedirectionInformation.PortAddress != 0 ) {
#else
    if (BlIsTerminalConnected()) {
#endif        
        ScreenHeight = HEADLESS_SCREEN_HEIGHT;
    }


    SlSetCurrentAttribute(DEFATT);
    SlClearDisplay();
}


VOID
SlPrint(
    IN PTCHAR FormatString,
    ...
    )
{
    va_list arglist;
    TCHAR    text[MAX_STATUS+1];
    ULONG   Count,Length;
    ULONG   MaxWidth = ScreenWidth - 2;

    if (MaxWidth > MAX_STATUS) {
        MaxWidth = MAX_STATUS;
    }

    va_start(arglist,FormatString);
    Length = _vsntprintf(text,MaxWidth*sizeof(TCHAR),FormatString,arglist);
    text[MaxWidth] = TEXT('\0');

    ArcWrite(ARC_CONSOLE_OUTPUT,text,Length,&Count);
    va_end(arglist);
}


VOID
SlClearDisplay(
    VOID
    )

/*++

Routine Description:

    Clears the entire display, including header, client area, and status line.

Arguments:

    None

Return Value:

    None.

--*/

{
#if 1    
    BlClearScreen();
#else

    SlPositionCursor(0,0);

#ifdef EFI
    BlEfiClearToEndOfDisplay();
#else
    ARC_DISPLAY_CLEAR_TO_EOD();
#endif    

    if (!ShowProgressBar) {
        SlWriteStatusText(TEXT(""));
    }

#endif
}

ARC_STATUS
SlClearClientArea(
    VOID
    )

/*++

Routine Description:

    Clears the client area of the screen.  Does not disturb the header or
    status areas.

Arguments:

    None.

Return Value:

    always ESUCCESS

--*/

{
    USHORT i;

    for(i=HEADER_HEIGHT; i<ScreenHeight-1; i++) {
        SlPositionCursor(0,i);
        SlClearToEol();
    }

    //
    // home cursor
    //

    SlPositionCursor(0,0);
    return(ESUCCESS);
}


ARC_STATUS
SlClearToEol(
    VOID
    )
{
#ifdef EFI
    
    BlEfiClearToEndOfLine();
#else
    ARC_DISPLAY_CLEAR_TO_EOL();
#endif
    return(ESUCCESS);
}


VOID
SlGetCursorPosition(
    OUT unsigned *x,
    OUT unsigned *y
    )
{
    *x = ScreenX;
    *y = ScreenY;
}


ARC_STATUS
SlPositionCursor(
    IN unsigned x,
    IN unsigned y
    )
{
    //
    // clip to screen
    //

    if(x>=ScreenWidth) {
        x = ScreenWidth-1;
    }

    if(y>=ScreenHeight) {
        y = ScreenHeight-1;
    }

    ScreenX = x;
    ScreenY = y;

#ifdef EFI
    BlEfiPositionCursor(x, y );
#else
    ARC_DISPLAY_POSITION_CURSOR(x, y);
#endif

    
    return(ESUCCESS);
}


ARC_STATUS
SlWriteString(
    IN _PTUCHAR s
    )
{
    _PTUCHAR p = s,q;
    BOOLEAN done = FALSE;
    ULONG len,count;

    do {
        q = p;
        while((*q != TEXT('\0')) && (*q != TEXT('\n'))) {
            q++;
        }
        if(*q == TEXT('\0')) {
            done = TRUE;
        } else {
            *q = TEXT('\0');
        }
        len = (ULONG)(q - p);

        ArcWrite(ARC_CONSOLE_OUTPUT,p,len*sizeof(TCHAR),&count);

        ScreenX += len;

        if(!done) {
            ArcWrite(ARC_CONSOLE_OUTPUT,TEXT("\r\n"),2*sizeof(TCHAR),&count);
            ScreenX = 0;
            ScreenY++;
            if(ScreenY == ScreenHeight) {
                ScreenY = ScreenHeight-1;
            }
            *q = TEXT('\n');
        }
        p = q + 1;
    } while(!done);

    return(ESUCCESS);
}


VOID
SlSetCurrentAttribute(
    IN UCHAR Attribute
    )
{
    CurAttribute = Attribute;
#ifdef EFI
    BlEfiSetAttribute( CurAttribute );
#else

    SlPrint(ASCI_CSI_OUT);

    if (BlIsTerminalConnected() && 
        ((Attribute == DEFSTATTR) ||
         (Attribute == INVATT))) {
        SlPrint(TEXT("7"));
    } else {
        SlPrint(TEXT("0"));
    }

    SlPrint(TEXT(";%u;%um"), (Attribute &  0x7) + 30, ((Attribute >> 4) & 7) + 40);

    if(Attribute & ATT_FG_INTENSE) {
        SlPrint(ASCI_CSI_OUT TEXT("1m"));
    }
#endif
}


VOID
SlWriteHeaderText(
    IN ULONG MsgId
    )

/*++

Routine Description:

    Updates the header on the screen with a given string

Arguments:

    MsgId - Supplies the message ID of the new string to be displayed.  This should
            be just one line long.  If it is 0, the header is cleared.

Return Value:

    None.

--*/
{
    int i;

    for(i=HEADER_HEIGHT-1; i>=0; i--) {
        SlPositionCursor(0,i);
        SlClearToEol();
    }

    if (MsgId != 0) {
        SlWriteString(BlFindMessage(MsgId));
    }
}

//
// Stores the current status text.  The size is the screen width, plus the
// terminating nul char.
//
TCHAR StatusText[MAX_STATUS];

UCHAR StatusAttribute = DEFSTATTR;

VOID
SlSetStatusAttribute(
    IN UCHAR Attribute
    )
{
    StatusAttribute = Attribute;
}


BOOLEAN
SlGetStatusBarStatus(
    VOID
    )
/*++

Routine Description:

    Determines if status bar is enabled or not
    
Arguments:

    None.

Return Value:

    TRUE or FALSE
--*/    
{
    return StatusBarEnabled;
}

VOID
SlEnableStatusBar(
    IN  BOOLEAN Enable
    )
/*++

Routine Description:

    Enables or disables the status bar
    
Arguments:

    Enabled - Enable/Disable = TRUE/FALSE

Return Value:

    None.

--*/    
{
    StatusBarEnabled = Enable;
}

VOID
SlWriteStatusText(
    IN PTCHAR Text
    )

/*++

Routine Description:

    Updates the status area on the screen with a given string

Arguments:

    Text - Supplies the new text for the status area.

Return Value:

    None.

--*/
{
    UCHAR AttributeSave = CurAttribute;
    PTCHAR p;
    ULONG Count;
#ifdef EFI
    ULONG MaxWidth = ScreenWidth - 3;
#else
    ULONG MaxWidth = ScreenWidth - 2;
#endif

    //
    // Nop, if status bar is disabled
    //
    if (!StatusBarEnabled) {
        return;
    }

    //
    // if we're writing to a terminal, we don't want to write into the lower
    // right corner as this would make us scroll.
    //
    if (BlTerminalConnected) {
        MaxWidth -= 1;
    }

    if (MaxWidth > MAX_STATUS) {
        MaxWidth = MAX_STATUS;
    }

#ifdef UNICODE
    for (Count = 0 ; Count < sizeof(StatusText)/sizeof(TCHAR);Count++) {
        StatusText[Count] = TEXT(' ');
    }
#else
    RtlFillMemory(StatusText,sizeof(StatusText),' ');
#endif

    //
    // Strip cr/lf as we copy the status text into the status text buffer.
    //
    p = StatusText;
    Count = 0;
    while((Count < MaxWidth) && *Text) {
        if((*Text != TEXT('\r')) && (*Text != TEXT('\n'))) {
            *p++ = *Text;
            Count++;
        }
        Text++;
    }

    SlSetCurrentAttribute(StatusAttribute);
    SlPositionCursor(0,ScreenHeight-1);
    ArcWrite(ARC_CONSOLE_OUTPUT,TEXT("  "),2*sizeof(TCHAR),&Count);
    SlPositionCursor(2,ScreenHeight-1);
    ArcWrite(ARC_CONSOLE_OUTPUT,StatusText,MaxWidth*sizeof(TCHAR),&Count);
    SlSetCurrentAttribute(AttributeSave);
    SlPositionCursor(0,5);
}


VOID
SlGetStatusText(
    OUT PTCHAR Text
    )
{
    _tcscpy(Text,StatusText);
}



#if DBG
VOID
SlWriteDbgText(
    IN PTCHAR text
    )
{
    UCHAR SavedAttribute = CurAttribute;

    SlPositionCursor(0,0);
    CurAttribute = ATT_FG_YELLOW | ATT_BG_RED | ATT_FG_INTENSE;

    SlClearToEol();
    SlWriteString(text);

    CurAttribute = SavedAttribute;
}
#endif


VOID
SlFlushConsoleBuffer(
    VOID
    )

/*++

Routine Description:

    This routine flushes the console buffer, so that we don't have any
    pre-existing keypresses in the buffer when we prompt the user to
    'press any key to continue.'

Arguments:

    NONE

Return Value:

    NONE

--*/

{
    UCHAR c;
    ULONG count;

    while(ArcGetReadStatus(ARC_CONSOLE_INPUT) == ESUCCESS) {
        ArcRead(ARC_CONSOLE_INPUT, &c, 1, &count);
    }
}


ULONG
SlGetChar(
    VOID
    )
{
    UCHAR c;
    ULONG count;

    ArcRead(ARC_CONSOLE_INPUT,&c,1,&count);

    if(c == ASCI_CSI_IN) {
        if (ArcGetReadStatus(ARC_CONSOLE_INPUT) != ESUCCESS) {
            //
            // Just a single escape - return it
            //
            return (ASCI_ESC);
        }

        ArcRead(ARC_CONSOLE_INPUT,&c,1,&count);

        switch(c) {

        //
        // see ntos\fw\mips\fwsignal.c!TranslateScanCode() for these codes.
        // Additional codes that might be useful someday:
        // left=C, right=D, insert=@, delete=P
        //

        case 'A':                   // up arrow
            return(SL_KEY_UP);

        case 'B':                   // down arrow
            return(SL_KEY_DOWN);

        case 'H':                   // home
            return(SL_KEY_HOME);

        case 'K':                   // end
            return(SL_KEY_END);

        case '?':                   // page up
            return(SL_KEY_PAGEUP);

        case '/':                   // page down
            return(SL_KEY_PAGEDOWN);

        case 'O':                   // function keys

            ArcRead(ARC_CONSOLE_INPUT,&c,1,&count);

            //
            // F1=P, F2=Q, F3=w, F4 =x, F5 =t, F6 =u
            // F7=q, F8=r, F9=p, F10=m, F11=A, F12=B
            //
            // Note: as of 12/15/92, f11 and f12 were commented out in the
            // firmware sources so are probably never returned.
            //

            switch(c) {

            case 'P':
                return(SL_KEY_F1);

            case 'Q':
                return(SL_KEY_F2);

            case 'w':
                return(SL_KEY_F3);

            case 't':
                return(SL_KEY_F5);

            case 'u':
                return(SL_KEY_F6);

            case 'q':
                return(SL_KEY_F7);

            case 'm':
            case 'M':
                return  SL_KEY_F10;

            case 'A':
                return(SL_KEY_F11);

            case 'B':
                return(SL_KEY_F12);

            default:
                return(0);
            }

        default:
            return(0);
        }

    } else {
        if(c == ASCI_LF) {
            c = ASCI_CR;
        }
        return((ULONG)c);
    }
}


BOOLEAN
SlPromptForDisk(
    IN PTCHAR  DiskName,
    IN BOOLEAN IsCancellable
    )

/*++

Routine Description:

    This routine prompts a user to insert a given diskette #, or to abort the
    Setup process.

    The status line will be erased.

Arguments:

    DiskName - Supplies the name of the disk to be inserted.

    IsCancellable - Supplies flag indicating whether prompt may be cancelled.

Return Value:

    TRUE - The user has pressed OK

    FALSE - The user has pressed CANCEL

--*/

{
    ULONG msg;
    ULONG y;
    ULONG Key;
    PTCHAR StatusText;
    PTCHAR PleaseWait;
    ULONG i;
    TCHAR  DiskNameDisplayed[81];
    BOOLEAN Repaint=TRUE;

    SlWriteStatusText(TEXT(""));

    if(IsCancellable) {
        msg = SL_NEXT_DISK_PROMPT_CANCELLABLE;
    } else {
        msg = SL_NEXT_DISK_PROMPT;
    }
    StatusText = BlFindMessage(msg);
    if(StatusText == NULL) {
        SlError(msg);
        return(FALSE);
    }

    PleaseWait = BlFindMessage(SL_PLEASE_WAIT);
    if(PleaseWait == NULL) {
        SlError(SL_PLEASE_WAIT);
        return(FALSE);
    }

    //
    // Get first line of DiskName and save it in DiskNameDisplayed (limit to 80 chars)
    //
    for(i = 0;
        ((i < 80) && DiskName[i] && (DiskName[i] != TEXT('\r')) && (DiskName[i] != TEXT('\n')));
        i++)
    {
        DiskNameDisplayed[i] = DiskName[i];
    }
    DiskNameDisplayed[i] = TEXT('\0');

    do {
        if (Repaint) {
            SlClearClientArea();
            y = SlDisplayMessageBox(SL_MSG_INSERT_DISK);
            SlPositionCursor((ScreenWidth-i)/2,y+2);
            SlWriteString(DiskNameDisplayed);
            SlWriteStatusText(StatusText);
        }
        Repaint = FALSE;
        SlFlushConsoleBuffer();
        Key = SlGetChar();

        if (Key == ASCI_CR) {
            SlClearClientArea();
            SlWriteStatusText(PleaseWait);
            return(TRUE);
        } else if (Key == SL_KEY_F3) {
            SlConfirmExit();
            Repaint=TRUE;
        } else if((Key == ASCI_ESC) && IsCancellable) {
            SlWriteStatusText(TEXT(""));
            SlClearClientArea();
            return FALSE;
        }
    } while ( TRUE );
}


VOID
SlConfirmExit(
    VOID
    )

/*++

Routine Description:

    Routine to be called when user presses F3.  Confirms that he really wants
    to exit by popping up a dialog.  DOES NOT RETURN if user chooses to exit.

Arguments:

    None.

Return Value:

    None.

--*/

{
    ULONG c;

    //
    // if we use too much stack space the heap and stack can overlap and we can run into corruption problems
    // without any "stack overflow" exceptions; making large strings static helps prevent this
    //
    static TCHAR OldStatus[MAX_STATUS];
    PTCHAR StatusText;

    SlGetStatusText(OldStatus);

    SlClearClientArea();

    SlSetCurrentAttribute(DEFDLGATT);

    SlDisplayMessageBox(SL_MSG_EXIT_DIALOG);

    SlSetCurrentAttribute(DEFATT);

    SlFlushConsoleBuffer();

    while(1) {
        c = SlGetChar();
        if(c == ASCI_CR) {
            SlWriteStatusText(OldStatus);
            return;
        }
        if(c == SL_KEY_F3) {
            StatusText = BlFindMessage(SL_REBOOT_PROMPT);
            SlClearClientArea();
#ifdef i386
            SlDisplayMessageBox(SL_SCRN_TEXTSETUP_EXITED);
#else
            SlDisplayMessageBox(SL_SCRN_TEXTSETUP_EXITED_ARC);
#endif
            SlWriteStatusText(StatusText);

            SlFlushConsoleBuffer();
            while(SlGetChar() != ASCI_CR);
            ArcRestart();
        }
    }
}



VOID
SlFriendlyError(
    IN ULONG uStatus,
    IN PCHAR pchBadFile,
    IN ULONG uLine,
    IN PCHAR pchCodeFile
    )

/*++

Routine Description:

    This is called when an error occurs.  It puts up a
    message box, displays an informative message, and allows
    the user to continue.  It is intended to give friendlier
    messages than the SlError macro, in the cases where SlError
    gets passed ARC error codes.

    The status text line will be erased.

Arguments:

    uStatus     - ARC error code

    pchBadFile  - Name of file causing error (Must be given for handled
                  ARC codes.  Optional for unhandled codes.)

    uLine       - Line # in source code file where error occurred (only
                  used for unhandled codes.)

    pchCodeFile - Name of souce code file where error occurred (only
                  used for unhandled codes.)

Return Value:

    None.

--*/

{
    ULONG uMsg;
    PTSTR pBadFile;
    PTSTR pCodeFile;
#ifdef UNICODE
    WCHAR BadFileW[64];
    WCHAR CodeFileW[200];
    ANSI_STRING aString;
    UNICODE_STRING uString;

    RtlInitString( &aString, pchBadFile );
    uString.Buffer = BadFileW;
    uString.MaximumLength = sizeof(BadFileW);
    RtlAnsiStringToUnicodeString( &uString, &aString, FALSE );

    RtlInitString( &aString, pchCodeFile );
    uString.Buffer = CodeFileW;
    uString.MaximumLength = sizeof(CodeFileW);
    RtlAnsiStringToUnicodeString( &uString, &aString, FALSE );

    if (pchBadFile) {
        pBadFile = BadFileW;
    } else {
        pBadFile = NULL;
    }

    pCodeFile = CodeFileW;

#else
    pBadFile = pchBadFile;
    pCodeFile = pchCodeFile;
#endif

    SlClearClientArea();    
    switch(uStatus) {
       case EBADF:
       case EINVAL:  // image corrupt
          uMsg = SL_WARNING_IMG_CORRUPT;
          break;

       case EIO:     // i/o error
          uMsg = SL_WARNING_IOERR;
          break;

       case ENOENT:  // file not found
          uMsg = SL_WARNING_NOFILE;
          break;

       case ENOMEM:  // insufficient memory
          uMsg = SL_WARNING_NOMEM;
          break;

       case EACCES: // unrecognized file system
           uMsg = SL_WARNING_BAD_FILESYS;
           break;

       default:      // then get SlError() behavior (with optional bad file name)
          if(pBadFile) {  // include error-causing file's name
              SlMessageBox(
                  SL_WARNING_ERROR_WFILE,
                  pBadFile,
                  uStatus,
                  uLine,
                  pCodeFile
                  );
          } else {
              SlMessageBox(
                  SL_WARNING_ERROR,
                  uStatus,
                  uLine,
                  pCodeFile
                  );
          }
          return;
    }
    SlMessageBox(
        uMsg,
        pBadFile
        );
}

VOID
SlMessageBox(
    IN ULONG MessageId,
    ...
    )

/*++

Routine Description:

    This is called when an error occurs.  It puts up a
    message box, displays an informative message, and allows
    the user to continue.

    The status text line will be erased.

Arguments:

    MessageId - Supplies ID of message box to be presented.

    any sprintf-compatible arguments to be inserted in the
    message box.

Return Value:

    None.

--*/

{
    va_list args;

    SlClearClientArea();
    va_start(args, MessageId);
    SlGenericMessageBox(MessageId, &args, NULL, NULL, NULL, NULL, TRUE);
    va_end(args);
    
    SlFlushConsoleBuffer();
    SlGetChar();
}


ULONG
SlDisplayMessageBox(
    IN ULONG MessageId,
    ...
    )

/*++

Routine Description:

    Just puts a message box up on the screen and returns.

    The status text line will be erased.

Arguments:

    MessageId - Supplies ID of message box to be presented.

    any sprintf-compatible arguments to be inserted in the
    message box.

Return Value:

    Y position of top line of message box

--*/

{
    ULONG y;
    va_list args;

    va_start(args, MessageId);
    SlGenericMessageBox(MessageId, &args, NULL, NULL, &y, NULL, TRUE);
    va_end(args);

    return(y);
}


VOID
SlFatalError(
    IN ULONG MessageId,
    ...
    )

/*++

Routine Description:

    This is called when a fatal error occurs.  It clears the client
    area, puts up a message box, displays the fatal error message, and
    allows the user to press a key to reboot.

    The status text line will be erased.

Arguments:

    MessageId - Supplies ID of message box to be presented.

    any sprintf-compatible arguments to be inserted in the
    message box.

Return Value:

    Does not return.

--*/

{
    va_list args;
    ULONG x,y;
    PTCHAR Text;

    SlClearClientArea();

    Text = BlFindMessage(MessageId);
    if(Text) {

        va_start(args, MessageId);

        _vsntprintf(MessageBuffer, sizeof(MessageBuffer), Text, args);

        //
        // Add a blank line, then concatenate the 'Can't continue' text.
        //
        _tcscat(MessageBuffer, TEXT("\r\n"));

        Text = BlFindMessage(SL_CANT_CONTINUE);
        if(Text) {
            _tcscat(MessageBuffer, Text);
        }

        Text = BlAllocateHeap((_tcslen(MessageBuffer)+1) * sizeof(TCHAR));
        _tcscpy(Text, MessageBuffer);

        //
        // Note that MessageId and args won't be used, since we're
        // passing in our Text pointer.
        //
        SlGenericMessageBox(MessageId, &args, Text, &x, NULL, &y, TRUE);

        va_end(args);

    } else {
        SlError(MessageId);
    }

    SlFlushConsoleBuffer();

    SlGetChar();

    ArcRestart();
    
}


VOID
SlGenericMessageBox(
    IN     ULONG   MessageId, OPTIONAL
    IN     va_list *args,     OPTIONAL
    IN     PTCHAR   Message,  OPTIONAL
    IN OUT PULONG  xLeft,     OPTIONAL
    IN OUT PULONG  yTop,      OPTIONAL
    OUT    PULONG  yBottom,   OPTIONAL
    IN     BOOLEAN bCenterMsg
    )

/*++

Routine Description:

    Formats and displays a message box.  The longest line in the string
    of characters will be centered on the screen if bCenterMsg is TRUE.

    The status text line will be erased.

Arguments:

    NOTE:  Either the MessageId/args pair or the Message string must be
           specified. Message string will be used if non-NULL.

    MessageId - Supplies the MessageId that will be looked up to provide
                a NULL-terminated string of characters.
                Each \r\n delimited string will be displayed on its own line.

    args - Supplies the argument list that will be passed to vsprintf.

    Message - Supplies the actual text of the message to be displayed

    xLeft - If bCenterMsg is FALSE, then xLeft is used for the starting x
            coordinate of the message (if specified, otherwise, x = 1).
            Also, if specified, it receives the x coordinate of the left edge
            of all lines that were displayed.

    yTop -  If bCenterMsg is FALSE, then yTop is used for the starting y
            coordinate of the message (if specified, otherwise, y = 3).
            Also, if specified, receives the y coordinate of the top line where
            the message box was displayed.

    yBottom - if specified, receives the y coordinate of the bottom line of
              the message box.

    bCenterMsg - if TRUE, center message on the screen.

Return Value:

    None.

--*/

{
    PTCHAR p;
    ULONG NumLines;
    ULONG MaxLength;
    ULONG x;
    ULONG y;
    ULONG i;
    PTCHAR Line[20];
    ULONG LineLength[20];
    ULONG Count;

    if(!Message) {    // then look up the message
        p=BlFindMessage(MessageId);
        if (p==NULL) {
            SlError(MessageId);
            x=3;
            y=ScreenHeight/2;
            NumLines=0;
        } else {
            _vsntprintf(MessageBuffer,sizeof(MessageBuffer),p,*args);
            Message = MessageBuffer;
        }
    } else {
        //
        // Just make p non-NULL, so we'll know it's OK to continue.
        //
        p = Message;
    }

    if(p) {

        SlWriteStatusText(TEXT(""));  // Clear status bar

        SlpSizeMessage(Message,
                       &NumLines,
                       &MaxLength,
                       LineLength,
                       Line);

        if (MaxLength > ScreenWidth) {
            MaxLength = ScreenWidth;
        }

        if(bCenterMsg) {
            x = (ScreenWidth-MaxLength)/2;
            y = (ScreenHeight-NumLines)/2;
        } else {
            if(xLeft) {
                x = *xLeft;
            } else {
                x = 1;
            }

            if(yTop) {
                y = *yTop;
            } else {
                y = 3;
            }
        }
    }

    for (i=0; i<NumLines; i++) {
        SlPositionCursor(x, y+i);
        ArcWrite(ARC_CONSOLE_OUTPUT,Line[i],LineLength[i]*sizeof(TCHAR),&Count);
    }

    if(xLeft) {
        *xLeft = x;
    }

    if(yTop) {
        *yTop = y;
    }

    if(yBottom) {
        *yBottom = NumLines ? y+NumLines-1 : 0;
    }
}


VOID
SlpSizeMessage(
    IN  PTCHAR Message,
    OUT PULONG Lines,
    OUT PULONG MaxLength,
    OUT ULONG LineLength[],
    OUT PTCHAR LineText[]
    )

/*++

Routine Description:

    This routine walks down a message and determines the number of
    lines and the maximum line length.

Arguments:

    Message - Supplies a pointer to a null-terminated message

    Lines - Returns the number of lines

    MaxLength - Returns the length of the longest line.

    LineLength - Supplies a pointer to an array of ULONGs
                 Returns a filled in array containing the
                 length of each line.

    LineText - Supplies a pointer to an array of PCHARs
               Returns a filled in array containing a
               pointer to the start of each line.

Return Value:

    None.

--*/

{
    PTCHAR p;
    ULONG NumLines;
    ULONG Length;

    p = Message;
    NumLines = 0;
    *MaxLength = 0;
    Length = 0;

    //
    // walk through the string, determining the number of lines
    // and the length of the longest line.
    //
    LineText[0]=p;
    while (*p != TEXT('\0')) {
        if ((*p == TEXT('\r')) && (*(p+1) == TEXT('\n'))) {
            //
            // End of a line.
            //

            if (Length > *MaxLength) {
                *MaxLength = Length;
            }
            LineLength[NumLines] = Length;
            ++NumLines;
            Length = 0;
            p += 2;
            LineText[NumLines] = p;

        } else {
            ++Length;
            ++p;

            if (*p == TEXT('\0')) {

                //
                // End of the message.
                //

                if (Length > *MaxLength) {
                    *MaxLength = Length;
                }
                LineLength[NumLines] = Length;
                ++NumLines;
            }
        }
    }

    *Lines = NumLines;

}

VOID
SlNoMemError(
    IN ULONG Line,
    IN PCHAR File
    )

/*++

Routine Description:

    Stub error routine for linking with boot\lib\parseini.c.  Just passes arguments thru.

Arguments:

    Line - Line number within the file the error occurred on.

    File - File name where the error occurred.

Return Value:

    None.

--*/
{
     SlFatalError(SL_NO_MEMORY, Line, SlCopyStringAT(File));
}

VOID
SlBadInfLineError(
    IN ULONG Line,
    IN PCHAR INFFile
    )

/*++

Routine Description:

    Stub error routine for linking with boot\lib\parseini.c.  Just passes arguments thru.

Arguments:

    Line - Line number within the inf file the error occurred on.
    
    INFFile - Supplies a pointer to the INF filename.

Return Value:

    None.

--*/
{
    SlFatalError(SL_BAD_INF_LINE, Line, SlCopyStringAT(INFFile));
}

VOID
SlErrorBox(
    IN ULONG MessageId,
    IN ULONG Line,
    IN PCHAR File
    )

/*++

Routine Description:

    Stub error routine for linking with boot\lib\parseini.c.  Just passes arguments thru.

Arguments:

    MessageId - Id of the message to display.

    Line - Line number within the file the error occurred on.

    File - File name where the error occurred.

Return Value:

    None.

--*/
{
    PTSTR pFile;
#ifdef UNICODE    
    WCHAR FileW[200];
    ANSI_STRING aString;
    UNICODE_STRING uString;

    RtlInitString( &aString, File );
    uString.Buffer = FileW;
    uString.MaximumLength = sizeof(FileW);
    RtlAnsiStringToUnicodeString( &uString, &aString, FALSE );
    
    pFile = FileW;

#else
    pFile = File;    
#endif


     SlMessageBox(SL_WARNING_ERROR, MessageId, Line, pFile);
}