/*++

Copyright (c) 1998  Intel Corporation

Module Name:

    conio.c
    
Abstract:

    Shell Environment driver



Revision History

--*/

#include "shelle.h"

/* 
 * 
 */

#define MAX_HISTORY     20

#define INPUT_LINE_SIGNATURE     EFI_SIGNATURE_32('i','s','i','g')

typedef struct {
    UINTN           Signature;
    LIST_ENTRY      Link;
    CHAR16          Buffer[MAX_CMDLINE];
} INPUT_LINE;


/* 
 *  Globals
 */


static BOOLEAN      SEnvInsertMode;
static LIST_ENTRY   SEnvLineHistory;
static UINTN        SEnvNoHistory;


/* 
 * 
 */

VOID
SEnvConIoInitDosKey (
    VOID
    )
{
    InitializeListHead (&SEnvLineHistory);
    SEnvInsertMode = FALSE;
    SEnvNoHistory = 0;
}


/* 
 *  Functions used to access the console interface via a file handle
 *  Used if the console is not being redirected to a file
 */

EFI_STATUS
SEnvConIoOpen (
    IN struct _EFI_FILE_HANDLE  *File,
    OUT struct _EFI_FILE_HANDLE **NewHandle,
    IN CHAR16                   *FileName,
    IN UINT64                   OpenMode,
    IN UINT64                   Attributes
    )
{
    return EFI_NOT_FOUND;
}

EFI_STATUS
SEnvConIoNop (
    IN struct _EFI_FILE_HANDLE  *File
    )
{
    return EFI_SUCCESS;
}

EFI_STATUS
SEnvConIoGetPosition (
    IN struct _EFI_FILE_HANDLE  *File,
    OUT UINT64                  *Position
    )
{
    return EFI_UNSUPPORTED;
}

EFI_STATUS
SEnvConIoSetPosition (
    IN struct _EFI_FILE_HANDLE  *File,
    OUT UINT64                  Position
    )
{
    return EFI_UNSUPPORTED;
}

EFI_STATUS
SEnvConIoGetInfo (
    IN struct _EFI_FILE_HANDLE  *File,
    IN EFI_GUID                 *InformationType,
    IN OUT UINTN                *BufferSize,
    OUT VOID                    *Buffer
    )
{
    return EFI_UNSUPPORTED;
}

EFI_STATUS
SEnvConIoSetInfo (
    IN struct _EFI_FILE_HANDLE  *File,
    IN EFI_GUID                 *InformationType,
    IN UINTN                    BufferSize,
    OUT VOID                    *Buffer
    )
{
    return EFI_UNSUPPORTED;
}


EFI_STATUS
SEnvConIoWrite (
    IN struct _EFI_FILE_HANDLE  *File,
    IN OUT UINTN                *BufferSize,
    IN VOID                     *Buffer
    )
{
    Print (L"%.*s", *BufferSize, Buffer);
    return EFI_SUCCESS;
}

EFI_STATUS
SEnvErrIoWrite (
    IN struct _EFI_FILE_HANDLE  *File,
    IN OUT UINTN                *BufferSize,
    IN VOID                     *Buffer
    )
{
    IPrint (ST->StdErr, L"%.*s", *BufferSize, Buffer);
    return EFI_SUCCESS;
}

EFI_STATUS
SEnvErrIoRead (
    IN struct _EFI_FILE_HANDLE  *File,
    IN OUT UINTN                *BufferSize,
    IN VOID                     *Buffer
    )
{
    return EFI_UNSUPPORTED;
}


VOID
SEnvPrintHistory(
    VOID
    )
{
    LIST_ENTRY      *Link;
    INPUT_LINE      *Line;
    UINTN           Index;

    Print (L"\n");
    Index = 0;
    for (Link=SEnvLineHistory.Flink; Link != &SEnvLineHistory; Link=Link->Flink) {
        Index += 1;
        Line = CR(Link, INPUT_LINE, Link, INPUT_LINE_SIGNATURE);
        Print (L"%2d. %s\n", Index, Line->Buffer);
    }
}


EFI_STATUS
SEnvConIoRead (
    IN struct _EFI_FILE_HANDLE  *File,
    IN OUT UINTN                *BufferSize,
    IN VOID                     *Buffer
    )
{
    CHAR16                          *Str;
    BOOLEAN                         Done;
    UINTN                           Column, Row;
    UINTN                           Update, Delete;
    UINTN                           Len, StrPos, MaxStr;
    UINTN                           Index;
    EFI_INPUT_KEY                   Key;
    SIMPLE_TEXT_OUTPUT_INTERFACE    *ConOut;
    SIMPLE_INPUT_INTERFACE          *ConIn;
    INPUT_LINE                      *NewLine, *LineCmd;
    LIST_ENTRY                      *LinePos, *NewPos;

    ConOut = ST->ConOut;
    ConIn = ST->ConIn;
    Str = Buffer;

    if (*BufferSize < sizeof(CHAR16)*2) {
        *BufferSize = 0;
        return EFI_SUCCESS;
    }

    /* 
     *  Get input fields location
     */

    Column = ConOut->Mode->CursorColumn;
    Row = ConOut->Mode->CursorRow;
    ConOut->QueryMode (ConOut, ConOut->Mode->Mode, &MaxStr, &Index);

    /*  bugbug: for now wrapping is not handled */
    MaxStr = MaxStr - Column;

    /*  Clip to max cmdline */
    if (MaxStr > MAX_CMDLINE) {
        MaxStr = MAX_CMDLINE;
    }

    /*  Clip to user's buffer size */
    if (MaxStr > (*BufferSize / sizeof(CHAR16)) - 1) {
        MaxStr = (*BufferSize / sizeof(CHAR16)) - 1;
    }

    /* 
     *  Allocate a new key entry
     */

    NewLine = AllocateZeroPool (sizeof(INPUT_LINE));
    if (!NewLine) {
        return EFI_OUT_OF_RESOURCES;
    }

    NewLine->Signature = INPUT_LINE_SIGNATURE;
    LinePos = &SEnvLineHistory;

    /* 
     *  Set new input
     */

    Update = 0;
    Delete = 0;
    NewPos = &SEnvLineHistory;
    ZeroMem (Str, MaxStr * sizeof(CHAR16));

    Done = FALSE;
    do {
        /* 
         *  If we have a new position, reset
         */

        if (NewPos != &SEnvLineHistory) {
            LineCmd = CR(NewPos, INPUT_LINE, Link, INPUT_LINE_SIGNATURE);
            LinePos = NewPos;
            NewPos  = &SEnvLineHistory;

            CopyMem (Str, LineCmd->Buffer, MaxStr * sizeof(CHAR16));
            Index = Len;                /*  Save old len */
            Len = StrLen(Str);          /*  Get new len */
            StrPos = Len;
            Update = 0;                 /*  draw new input string */
            if (Index > Len) {
                Delete = Index - Len;   /*  if old string was longer, blank it */
            }
        }

        /* 
         *  If we need to update the output do so now
         */

        if (Update != -1) {
            PrintAt (Column+Update, Row, L"%s%.*s", Str + Update, Delete, L"");
            Len = StrLen (Str);

            if (Delete) {
                SetMem(Str+Len, Delete * sizeof(CHAR16), 0x00);
            }

            if (StrPos > Len) {
                StrPos = Len;
            }

            Update = -1;
            Delete = 0;
        }

        /* 
         *  Set the cursor position for this key
         */

        ConOut->SetCursorPosition (ConOut, Column+StrPos, Row);

        /* 
         *  Read the key
         */

        WaitForSingleEvent(ConIn->WaitForKey, 0);
        ConIn->ReadKeyStroke(ConIn, &Key);

        switch (Key.UnicodeChar) {
        case CHAR_CARRIAGE_RETURN:
            /* 
             *  All done, print a newline at the end of the string
             */

            PrintAt (Column+Len, Row, L"\n");
            Done = TRUE;
            break;

        case CHAR_BACKSPACE:
            if (StrPos) {
                StrPos -= 1;
                Update = StrPos;
                Delete = 1;
                CopyMem (Str+StrPos, Str+StrPos+1, sizeof(CHAR16) * (Len-StrPos));
            }
            break;

        default:
            if (Key.UnicodeChar >= ' ') {
                /*  If we are at the buffer's end, drop the key */
                if (Len == MaxStr-1 && 
                    (SEnvInsertMode || StrPos == Len)) {
                    break;
                }

                if (SEnvInsertMode) {
                    for (Index=Len; Index > StrPos; Index -= 1) {
                        Str[Index] = Str[Index-1];
                    }
                }

                Str[StrPos] = Key.UnicodeChar;
                Update = StrPos;
                StrPos += 1;
            }
            break;

        case 0:
            switch (Key.ScanCode) {
            case SCAN_DELETE:
                if (Len) {
                    Update = StrPos;
                    Delete = 1;
                    CopyMem (Str+StrPos, Str+StrPos+1, sizeof(CHAR16) * (Len-StrPos));
                }
                break;

            case SCAN_UP:
                NewPos = LinePos->Blink;
                if (NewPos == &SEnvLineHistory) {
                    NewPos = NewPos->Blink;
                }
                break;

            case SCAN_DOWN:
                NewPos = LinePos->Flink;
                if (NewPos == &SEnvLineHistory) {
                    NewPos = NewPos->Flink;
                }
                break;

            case SCAN_LEFT:
                if (StrPos) {
                    StrPos -= 1;
                }
                break;

            case SCAN_RIGHT:
                if (StrPos < Len) {
                    StrPos += 1;
                }
                break;

            case SCAN_HOME:
                StrPos = 0;
                break;

            case SCAN_END:
                StrPos = Len;
                break;

            case SCAN_ESC:
                Str[0] = 0;
                Update = 0;
                Delete = Len;
                break;

            case SCAN_INSERT:
                SEnvInsertMode = !SEnvInsertMode;
                break;

            case SCAN_F7:
                SEnvPrintHistory();
                *Str = 0;
                Done = TRUE;    
                break;
            }       
        }
    } while (!Done);

    /* 
     *  Copy the line to the history buffer
     */

    StrCpy (NewLine->Buffer, Str);
    if (Str[0]) {
        InsertTailList (&SEnvLineHistory, &NewLine->Link);
        SEnvNoHistory += 1;
    } else {
        FreePool (NewLine);
    }

    /* 
     *  If there's too much in the history buffer free an entry
     */

    if (SEnvNoHistory > MAX_HISTORY) {
        LineCmd = CR(SEnvLineHistory.Flink, INPUT_LINE, Link, INPUT_LINE_SIGNATURE);
        RemoveEntryList (&LineCmd->Link);
        SEnvNoHistory -= 1;
        FreePool (LineCmd);
    }

    /* 
     *  Return the data to the caller
     */

    *BufferSize = Len * sizeof(CHAR16);
    StrCpy(Buffer, Str);
    return EFI_SUCCESS;
}

/* 
 * 
 */


EFI_STATUS
SEnvReset (
    IN SIMPLE_TEXT_OUTPUT_INTERFACE     *This,
    IN BOOLEAN                          ExtendedVerification
    )
{ 
    return EFI_SUCCESS;
}

EFI_STATUS
SEnvOutputString (
    IN SIMPLE_TEXT_OUTPUT_INTERFACE     *This,
    IN CHAR16                       *String
    )
{
    EFI_STATUS              Status;         
    ENV_SHELL_REDIR_FILE    *Redir;
    UINTN                   Len, Size, WriteSize, Index, Start;
    CHAR8                   Buffer[100];
    CHAR16                  UnicodeBuffer[100];
    BOOLEAN                 InvalidChar;
    SIMPLE_INPUT_INTERFACE        *TextIn           = NULL;
    SIMPLE_TEXT_OUTPUT_INTERFACE  *TextOut          = NULL;

    Redir = CR(This, ENV_SHELL_REDIR_FILE, Out, ENV_REDIR_SIGNATURE);
    if (EFI_ERROR(Redir->WriteError)) {
        return(Redir->WriteError);
    }
    Status = EFI_SUCCESS;
    InvalidChar = FALSE;

    if (Redir->Ascii) {

        Start = 0;
        Len   = StrLen (String);
        while (Len) {
            Size = Len > sizeof(Buffer) ? sizeof(Buffer) : Len;
            for (Index=0; Index < Size; Index +=1) {
                if (String[Start+Index] > 0xff) {
                    Buffer[Index] = '_';
                    InvalidChar = TRUE;
                } else {
                    Buffer[Index] = (CHAR8) String[Start+Index];
                }  
            }

            WriteSize = Size;
            Status = Redir->File->Write (Redir->File, &WriteSize, Buffer);
            if (EFI_ERROR(Status)) {
                break;
            }

            Len   -= Size;
            Start += Size;
        }


    } else {

        Len = StrSize (String) - sizeof(CHAR16);
        Status = Redir->File->Write (Redir->File, &Len, String);
    }

    if (EFI_ERROR(Status)) {
        Redir->WriteError = Status;
        SEnvBatchGetConsole( &TextIn, &TextOut );
        SPrint(UnicodeBuffer,100,L"write error: %r\n\r",Status);
        Status = TextOut->OutputString( TextOut, UnicodeBuffer);
    }

    if (InvalidChar && !EFI_ERROR(Status)) {
        Status = EFI_WARN_UNKOWN_GLYPH;
    }

    return Status;
}



EFI_STATUS
SEnvTestString (
    IN SIMPLE_TEXT_OUTPUT_INTERFACE     *This,
    IN CHAR16                       *String
    )
{
    EFI_STATUS              Status;         
    ENV_SHELL_REDIR_FILE    *Redir;

    Redir = CR(This, ENV_SHELL_REDIR_FILE, Out, ENV_REDIR_SIGNATURE);
    Status = ST->ConOut->TestString(ST->ConOut, String);

    if (!EFI_ERROR(Status) && Redir->Ascii) {
        while (*String && *String < 0x100) {
            String += 1;
        }

        if (*String > 0xff) {
            Status = EFI_UNSUPPORTED;
        }
    }

    return Status;
}


EFI_STATUS 
SEnvQueryMode (
    IN SIMPLE_TEXT_OUTPUT_INTERFACE     *This,
    IN UINTN                        ModeNumber,
    OUT UINTN                       *Columns,
    OUT UINTN                       *Rows
    )
{
    if (ModeNumber > 0) {
        return EFI_INVALID_PARAMETER;
    }

    *Columns = 0;
    *Rows = 0;
    return EFI_SUCCESS;
}


EFI_STATUS
SEnvSetMode (
    IN SIMPLE_TEXT_OUTPUT_INTERFACE     *This,
    IN UINTN                        ModeNumber
    )
{
    return ModeNumber > 0 ? EFI_INVALID_PARAMETER : EFI_SUCCESS;
}

EFI_STATUS
SEnvSetAttribute (
    IN SIMPLE_TEXT_OUTPUT_INTERFACE     *This,
    IN UINTN                            Attribute
    )
{
    This->Mode->Attribute = (UINT32) Attribute;
    return EFI_SUCCESS;
}

EFI_STATUS
SEnvClearScreen (
    IN SIMPLE_TEXT_OUTPUT_INTERFACE     *This
    )
{
    return EFI_SUCCESS;
}


EFI_STATUS
SEnvSetCursorPosition (
    IN SIMPLE_TEXT_OUTPUT_INTERFACE     *This,
    IN UINTN                        Column,
    IN UINTN                        Row
    )
{
    return EFI_UNSUPPORTED;
}

EFI_STATUS
SEnvEnableCursor (
    IN SIMPLE_TEXT_OUTPUT_INTERFACE     *This,
    IN BOOLEAN                      Enable
    )
{
    This->Mode->CursorVisible = Enable;
    return EFI_SUCCESS;
}