/*
 * refit/screen.c
 * Screen handling functions
 *
 * Copyright (c) 2006 Christoph Pfisterer
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the
 *    distribution.
 *
 *  * Neither the name of Christoph Pfisterer nor the names of the
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "libegint.h"

#ifndef DEBUG_ALL
#define DEBUG_SCR 1
#else
#define DEBUG_SCR DEBUG_ALL
#endif

#if DEBUG_SCR == 0
#define DBG(...)
#else
#define DBG(...) DebugLog(DEBUG_SCR, __VA_ARGS__)	
#endif

// Console defines and variables

UINTN ConWidth;
UINTN ConHeight;
CHAR16 *BlankLine = NULL;
INTN BanHeight = 0;

static VOID SwitchToText(IN BOOLEAN CursorEnabled);
static VOID SwitchToGraphics(VOID);
static VOID DrawScreenHeader(IN CHAR16 *Title);
static VOID UpdateConsoleVars(VOID);
static INTN ConvertEdgeAndPercentageToPixelPosition(INTN Edge, INTN DesiredPercentageFromEdge, INTN ImageDimension, INTN ScreenDimension);
static INTN CalculateNudgePosition(INTN Position, INTN NudgeValue, INTN ImageDimension, INTN ScreenDimension);
//INTN RecalculateImageOffset(INTN AnimDimension, INTN ValueToScale, INTN ScreenDimensionToFit, INTN ThemeDesignDimension);
static BOOLEAN IsImageWithinScreenLimits(INTN Value, INTN ImageDimension, INTN ScreenDimension);
static INTN RepositionFixedByCenter(INTN Value, INTN ScreenDimension, INTN DesignScreenDimension);
static INTN RepositionRelativeByGapsOnEdges(INTN Value, INTN ImageDimension, INTN ScreenDimension, INTN DesignScreenDimension);
static INTN HybridRepositioning(INTN Edge, INTN Value, INTN ImageDimension, INTN ScreenDimension, INTN DesignScreenDimension);

EG_IMAGE * LoadSvgFrame(INTN i);

// UGA defines and variables

INTN   UGAWidth;
INTN   UGAHeight;
BOOLEAN AllowGraphicsMode;

EG_RECT  BannerPlace = {0, 0, 0, 0};

EG_PIXEL StdBackgroundPixel   = { 0xbf, 0xbf, 0xbf, 0xff};
EG_PIXEL MenuBackgroundPixel  = { 0x00, 0x00, 0x00, 0x00};
EG_PIXEL InputBackgroundPixel = { 0xcf, 0xcf, 0xcf, 0x80};
EG_PIXEL BlueBackgroundPixel  = { 0x7f, 0x0f, 0x0f, 0xff};
EG_PIXEL EmbeddedBackgroundPixel  = { 0xaa, 0xaa, 0xaa, 0xff};
EG_PIXEL DarkSelectionPixel   = { 66, 66, 66, 0xff};
EG_PIXEL DarkEmbeddedBackgroundPixel  = { 0x33, 0x33, 0x33, 0xff};
EG_PIXEL WhitePixel  = { 0xff, 0xff, 0xff, 0xff};
EG_PIXEL BlackPixel  = { 0x00, 0x00, 0x00, 0xff};

EG_IMAGE *BackgroundImage = NULL;
EG_IMAGE *Banner = NULL;
EG_IMAGE *BigBack = NULL;

static BOOLEAN GraphicsScreenDirty;

// general defines and variables

static BOOLEAN haveError = FALSE;

//
// Screen initialization and switching
//

VOID InitScreen(IN BOOLEAN SetMaxResolution)
{
	//DbgHeader("InitScreen");
    // initialize libeg
    egInitScreen(SetMaxResolution);
    
    if (egHasGraphicsMode()) {
        egGetScreenSize(&UGAWidth, &UGAHeight);
        AllowGraphicsMode = TRUE;
    } else {
        AllowGraphicsMode = FALSE;
		//egSetGraphicsModeEnabled(FALSE);   // just to be sure we are in text mode
    }
	
    GraphicsScreenDirty = TRUE;
    
    // disable cursor
	gST->ConOut->EnableCursor(gST->ConOut, FALSE);
    
    UpdateConsoleVars();

    // show the banner (even when in graphics mode)
	//DrawScreenHeader(L"Initializing...");
}

VOID SetupScreen(VOID)
{
    if (GlobalConfig.TextOnly) {
        // switch to text mode if requested
        AllowGraphicsMode = FALSE;
        SwitchToText(FALSE);
    } else if (AllowGraphicsMode) {
        // clear screen and show banner
        // (now we know we'll stay in graphics mode)
        SwitchToGraphics();
		//BltClearScreen(TRUE);
    }
}

static VOID SwitchToText(IN BOOLEAN CursorEnabled)
{
    egSetGraphicsModeEnabled(FALSE);
	gST->ConOut->EnableCursor(gST->ConOut, CursorEnabled);
}

static VOID SwitchToGraphics(VOID)
{
    if (AllowGraphicsMode && !egIsGraphicsModeEnabled()) {
      InitScreen(FALSE);
        egSetGraphicsModeEnabled(TRUE);
        GraphicsScreenDirty = TRUE;
    }
}

//
// Screen control for running tools
//
VOID BeginTextScreen(IN CHAR16 *Title)
{
    DrawScreenHeader(Title);
    SwitchToText(FALSE);
    
    // reset error flag
    haveError = FALSE;
}

VOID FinishTextScreen(IN BOOLEAN WaitAlways)
{
    if (haveError || WaitAlways) {
        SwitchToText(FALSE);
 //       PauseForKey(L"FinishTextScreen");
    }
    
    // reset error flag
    haveError = FALSE;
}

VOID BeginExternalScreen(IN BOOLEAN UseGraphicsMode, IN CHAR16 *Title)
{
	if (!AllowGraphicsMode) {
        UseGraphicsMode = FALSE;
	}
    
    if (UseGraphicsMode) {
        SwitchToGraphics();
		//BltClearScreen(FALSE);
    }
    
    // show the header
	//DrawScreenHeader(Title);
    
	if (!UseGraphicsMode) {
        SwitchToText(TRUE);
	}
    
    // reset error flag
    haveError = FALSE;
}

VOID FinishExternalScreen(VOID)
{
    // make sure we clean up later
    GraphicsScreenDirty = TRUE;
    
    if (haveError) {
        // leave error messages on screen in case of error,
        // wait for a key press, and then switch
        PauseForKey(L"was error, press any key\n");
        SwitchToText(FALSE);
    }
    
    // reset error flag
    haveError = FALSE;
}

VOID TerminateScreen(VOID)
{
    // clear text screen
	gST->ConOut->SetAttribute(gST->ConOut, ATTR_BANNER);
	gST->ConOut->ClearScreen(gST->ConOut);
    
    // enable cursor
	gST->ConOut->EnableCursor(gST->ConOut, TRUE);
}

static VOID DrawScreenHeader(IN CHAR16 *Title)
{
  UINTN i;
	CHAR16* BannerLine = AllocatePool((ConWidth + 1) * sizeof(CHAR16));
  BannerLine[ConWidth] = 0;

  // clear to black background
	//gST->ConOut->SetAttribute(gST->ConOut, ATTR_BASIC);
  //gST->ConOut->ClearScreen (gST->ConOut);

  // paint header background
  gST->ConOut->SetAttribute(gST->ConOut, ATTR_BANNER);
	
	for (i = 1; i < ConWidth-1; i++) {
    BannerLine[i] = BOXDRAW_HORIZONTAL;
	}
	
	BannerLine[0] = BOXDRAW_DOWN_RIGHT;
	BannerLine[ConWidth-1] = BOXDRAW_DOWN_LEFT;
  gST->ConOut->SetCursorPosition (gST->ConOut, 0, 0);
	Print(BannerLine);

	for (i = 1; i < ConWidth-1; i++)
    BannerLine[i] = ' ';
	BannerLine[0] = BOXDRAW_VERTICAL;
	BannerLine[ConWidth-1] = BOXDRAW_VERTICAL;
  gST->ConOut->SetCursorPosition (gST->ConOut, 0, 1);
	Print(BannerLine);

	for (i = 1; i < ConWidth-1; i++)
    BannerLine[i] = BOXDRAW_HORIZONTAL;
 	BannerLine[0] = BOXDRAW_UP_RIGHT;
	BannerLine[ConWidth-1] = BOXDRAW_UP_LEFT;
  gST->ConOut->SetCursorPosition (gST->ConOut, 0, 2);
	Print(BannerLine);

	FreePool(BannerLine);

  // print header text
  gST->ConOut->SetCursorPosition (gST->ConOut, 3, 1);
  Print(L"Clover rev %s - %s", gFirmwareRevision, Title);

  // reposition cursor
  gST->ConOut->SetAttribute (gST->ConOut, ATTR_BASIC);
  gST->ConOut->SetCursorPosition (gST->ConOut, 0, 4);
}

//
// Keyboard input
//

BOOLEAN ReadAllKeyStrokes(VOID)
{
    BOOLEAN       GotKeyStrokes;
    EFI_STATUS    Status;
    EFI_INPUT_KEY key;
    
    GotKeyStrokes = FALSE;
    for (;;) {
        Status = gST->ConIn->ReadKeyStroke (gST->ConIn, &key);
        if (Status == EFI_SUCCESS) {
            GotKeyStrokes = TRUE;
            continue;
        }
        break;
    }
    return GotKeyStrokes;
}

VOID PauseForKey(CHAR16* msg)
{
#if REFIT_DEBUG > 0  
    UINTN index;
    if (msg) {
      Print(L"\n %s", msg);
    }
    Print(L"\n* Hit any key to continue *");
    
    if (ReadAllKeyStrokes()) {  // remove buffered key strokes
        gBS->Stall(5000000);     // 5 seconds delay
        ReadAllKeyStrokes();    // empty the buffer again
    }
    
    gBS->WaitForEvent(1, &gST->ConIn->WaitForKey, &index);
    ReadAllKeyStrokes();        // empty the buffer to protect the menu
    
    Print(L"\n");
#endif
}

#if REFIT_DEBUG > 0
VOID DebugPause(VOID)
{
    // show console and wait for key
    SwitchToText(FALSE);
    PauseForKey(L"");
    
    // reset error flag
    haveError = FALSE;
}
#endif

VOID EndlessIdleLoop(VOID)
{
    UINTN index;
    
    for (;;) {
        ReadAllKeyStrokes();
        gBS->WaitForEvent(1, &gST->ConIn->WaitForKey, &index);
    }
}

//
// Error handling
//
/*
VOID
StatusToString (
				OUT CHAR16      *Buffer,
				EFI_STATUS      Status
				)
{
	UnicodeSPrint(Buffer, 64, L"EFI Error %r", Status);
}*/


BOOLEAN CheckFatalError(IN EFI_STATUS Status, IN CHAR16 *where)
{
//    CHAR16 ErrorName[64];
    
    if (!EFI_ERROR(Status))
        return FALSE;
    
//    StatusToString(ErrorName, Status);
    gST->ConOut->SetAttribute (gST->ConOut, ATTR_ERROR);
    Print(L"Fatal Error: %r %s\n", Status, where);
    gST->ConOut->SetAttribute (gST->ConOut, ATTR_BASIC);
    haveError = TRUE;
    
    //gBS->Exit(ImageHandle, ExitStatus, ExitDataSize, ExitData);
    
    return TRUE;
}

BOOLEAN CheckError(IN EFI_STATUS Status, IN CHAR16 *where)
{
//    CHAR16 ErrorName[64];
    
    if (!EFI_ERROR(Status))
        return FALSE;
    
//    StatusToString(ErrorName, Status);
    gST->ConOut->SetAttribute (gST->ConOut, ATTR_ERROR);
    Print(L"Error: %r %s\n", Status, where);
    gST->ConOut->SetAttribute (gST->ConOut, ATTR_BASIC);
    haveError = TRUE;
    
    return TRUE;
}

//
// Graphics functions
//

VOID SwitchToGraphicsAndClear(VOID)
{
    SwitchToGraphics();
	if (GraphicsScreenDirty) {
        BltClearScreen(TRUE);
	}
}

/*
typedef struct {
  INTN     XPos;
  INTN     YPos;
  INTN     Width;
  INTN     Height;
} EG_RECT;
*/


VOID BltClearScreen(IN BOOLEAN ShowBanner) //ShowBanner always TRUE
{
  EG_PIXEL *p1;
  INTN i, j, x, x1, x2, y, y1, y2;
  if (BanHeight < 2) {
    BanHeight = ((UGAHeight - (int)(LAYOUT_TOTAL_HEIGHT * GlobalConfig.Scale)) >> 1);
    //+ (int)(LAYOUT_TOTAL_HEIGHT * GlobalConfig.Scale); //LAYOUT_TOTAL_HEIGHT=376
  }

  if (!(GlobalConfig.HideUIFlags & HIDEUI_FLAG_BANNER)) {
    // Banner is used in this theme
    if (!Banner) {
      // Banner is not loaded yet
      if (IsEmbeddedTheme()) {
        // embedded theme - use text as banner
   //     Banner = egCreateImage(7 * StrLen(L"CLOVER"), 32, TRUE);
   //     egFillImage(Banner, &MenuBackgroundPixel);
   //     egRenderText(L"CLOVER", Banner, 0, 0, 0xFFFF);
   //     CopyMem(&BlueBackgroundPixel, &StdBackgroundPixel, sizeof(EG_PIXEL));
   //     DebugLog(1, "Text <%s> rendered\n", L"Clover");
        Banner = BuiltinIcon(BUILTIN_ICON_BANNER);
        if (GlobalConfig.DarkEmbedded) {
          CopyMem(&BlueBackgroundPixel, &DarkEmbeddedBackgroundPixel, sizeof(EG_PIXEL));
        } else {
          CopyMem(&BlueBackgroundPixel, &StdBackgroundPixel, sizeof(EG_PIXEL));
        }
      } else  {
        Banner = egLoadImage(ThemeDir, GlobalConfig.BannerFileName, FALSE);
        if (Banner) {
          // Banner was changed, so copy into BlueBackgroundBixel first pixel of banner
          CopyMem(&BlueBackgroundPixel, &Banner->PixelData[0], sizeof(EG_PIXEL));
        } else {
          DBG("banner file not read\n");
        }
      }
    }
    if (Banner) {
      // Banner was loaded, so calculate its size and position
      BannerPlace.Width = Banner->Width;
      BannerPlace.Height = (BanHeight >= Banner->Height) ? (INTN)Banner->Height : BanHeight;
 //     DBG("banner width-height [%d,%d]\n", BannerPlace.Width, BannerPlace.Height);
 //     DBG("global banner pos [%d,%d]\n", GlobalConfig.BannerPosX, GlobalConfig.BannerPosY);
      if (GlobalConfig.TypeSVG) {
        BannerPlace.XPos = GlobalConfig.BannerPosX;
        BannerPlace.YPos = GlobalConfig.BannerPosY;
      } else {
        // Check if new style placement value was used for banner in theme.plist

        if ((GlobalConfig.BannerPosX >=0 && GlobalConfig.BannerPosX <=1000) && (GlobalConfig.BannerPosY >=0 && GlobalConfig.BannerPosY <=1000)) {
          // Check if screen size being used is different from theme origination size.
          // If yes, then recalculate the placement % value.
          // This is necessary because screen can be a different size, but banner is not scaled.
          BannerPlace.XPos = HybridRepositioning(GlobalConfig.BannerEdgeHorizontal, GlobalConfig.BannerPosX, BannerPlace.Width,  UGAWidth,  GlobalConfig.ThemeDesignWidth );
          BannerPlace.YPos = HybridRepositioning(GlobalConfig.BannerEdgeVertical,   GlobalConfig.BannerPosY, BannerPlace.Height, UGAHeight, GlobalConfig.ThemeDesignHeight);
          // Check if banner is required to be nudged.
          BannerPlace.XPos = CalculateNudgePosition(BannerPlace.XPos, GlobalConfig.BannerNudgeX, Banner->Width,  UGAWidth);
          BannerPlace.YPos = CalculateNudgePosition(BannerPlace.YPos, GlobalConfig.BannerNudgeY, Banner->Height, UGAHeight);
 //         DBG("banner position new style\n");
        } else {
          // Use rEFIt default (no placement values speicifed)
          BannerPlace.XPos = (UGAWidth - Banner->Width) >> 1;
          BannerPlace.YPos = (BanHeight >= Banner->Height) ? (BanHeight - Banner->Height) : 0;
  //        DBG("banner position old style\n");
        }
      }
    }
  }

//  DBG("Banner position [%d,%d]\n",  BannerPlace.XPos, BannerPlace.YPos);

  if (!Banner || (GlobalConfig.HideUIFlags & HIDEUI_FLAG_BANNER) || 
      !IsImageWithinScreenLimits(BannerPlace.XPos, BannerPlace.Width, UGAWidth) || 
      !IsImageWithinScreenLimits(BannerPlace.YPos, BannerPlace.Height, UGAHeight)) {
    // Banner is disabled or it cannot be used, apply defaults for placement
    if (Banner) {
      FreePool(Banner);
      Banner = NULL;
    }
    BannerPlace.XPos = 0;
    BannerPlace.YPos = 0;
    BannerPlace.Width = UGAWidth;
    BannerPlace.Height = BanHeight;
  }
  
  // Load Background and scale
  if (!BigBack && (GlobalConfig.BackgroundName != NULL)) {
    BigBack = egLoadImage(ThemeDir, GlobalConfig.BackgroundName, FALSE);
  }
  
  if (BackgroundImage != NULL && (BackgroundImage->Width != UGAWidth || BackgroundImage->Height != UGAHeight)) {
    // Resolution changed
    egFreeImage(BackgroundImage);
    BackgroundImage = NULL;
  }
  
  if (BackgroundImage == NULL) {
/*    DBG("BltClearScreen(%c): calling egCreateFilledImage UGAWidth %ld, UGAHeight %ld, BlueBackgroundPixel %02x%02x%02x%02x\n",
        ShowBanner?'Y':'N', UGAWidth, UGAHeight,
        BlueBackgroundPixel.r, BlueBackgroundPixel.g, BlueBackgroundPixel.b, BlueBackgroundPixel.a); */
    BackgroundImage = egCreateFilledImage(UGAWidth, UGAHeight, FALSE, &BlueBackgroundPixel);
  }
  
  if (BigBack != NULL) {
    switch (GlobalConfig.BackgroundScale) {
      case imScale:
        ScaleImage(BackgroundImage, BigBack);
        break;
      case imCrop:
        x = UGAWidth - BigBack->Width;
        if (x >= 0) {
          x1 = x >> 1;
          x2 = 0;
          x = BigBack->Width;
        } else {
          x1 = 0;
          x2 = (-x) >> 1;
          x = UGAWidth;
        }
        y = UGAHeight - BigBack->Height;
        if (y >= 0) {
          y1 = y >> 1;
          y2 = 0;
          y = BigBack->Height;
        } else {
          y1 = 0;
          y2 = (-y) >> 1;
          y = UGAHeight;
        }
        egRawCopy(BackgroundImage->PixelData + y1 * UGAWidth + x1,
                  BigBack->PixelData + y2 * BigBack->Width + x2,
                  x, y, UGAWidth, BigBack->Width);
        break;
      case imTile:
        x = (BigBack->Width * ((UGAWidth - 1) / BigBack->Width + 1) - UGAWidth) >> 1;
        y = (BigBack->Height * ((UGAHeight - 1) / BigBack->Height + 1) - UGAHeight) >> 1;
        p1 = BackgroundImage->PixelData;
        for (j = 0; j < UGAHeight; j++) {
          y2 = ((j + y) % BigBack->Height) * BigBack->Width;
          for (i = 0; i < UGAWidth; i++) {
            *p1++ = BigBack->PixelData[y2 + ((i + x) % BigBack->Width)];
          }
        }
        break;
      case imNone:
      default:
        // already scaled
        break;
    }
  }
  
  // Draw background
  if (BackgroundImage) {
/*    DBG("BltClearScreen(%c): calling BltImage BackgroundImage %p\n",
        ShowBanner?'Y':'N', BackgroundImage); */
    BltImage(BackgroundImage, 0, 0); //if NULL then do nothing
  } else {
/*    DBG("BltClearScreen(%c): calling egClearScreen StdBackgroundPixel %02x%02x%02x%02x\n",
        ShowBanner?'Y':'N', StdBackgroundPixel.r, StdBackgroundPixel.g, StdBackgroundPixel.b, StdBackgroundPixel.a); */
    egClearScreen(&StdBackgroundPixel);
  }
  
  // Draw banner
  if (Banner && ShowBanner) {
    BltImageAlpha(Banner, BannerPlace.XPos, BannerPlace.YPos, &MenuBackgroundPixel, 16);
  }
  
  InputBackgroundPixel.r = (MenuBackgroundPixel.r + 0) & 0xFF;
  InputBackgroundPixel.g = (MenuBackgroundPixel.g + 0) & 0xFF;
  InputBackgroundPixel.b = (MenuBackgroundPixel.b + 0) & 0xFF;
  InputBackgroundPixel.a = (MenuBackgroundPixel.a + 0) & 0xFF;
  GraphicsScreenDirty = FALSE;
}

VOID BltImage(IN EG_IMAGE *Image, IN INTN XPos, IN INTN YPos)
{
  if (!Image) {
    return;
  }
  egDrawImageArea(Image, 0, 0, 0, 0, XPos, YPos);
  GraphicsScreenDirty = TRUE;
}

VOID BltImageAlpha(IN EG_IMAGE *Image, IN INTN XPos, IN INTN YPos, IN EG_PIXEL *BackgroundPixel, INTN Scale)
{
  EG_IMAGE *CompImage;
  EG_IMAGE *NewImage = NULL;
  INTN Width = Scale << 3;
  INTN Height = Width;

  GraphicsScreenDirty = TRUE;
  if (Image) {
    NewImage = egCopyScaledImage(Image, Scale); //will be Scale/16
    Width = NewImage->Width;
    Height = NewImage->Height;
  }
//  DBG("w=%d, h=%d\n", Width, Height);
  // compose on background
  CompImage = egCreateFilledImage(Width, Height, (BackgroundImage != NULL), BackgroundPixel);
  egComposeImage(CompImage, NewImage, 0, 0);
  if (NewImage) {
    egFreeImage(NewImage);
  }
  if (!BackgroundImage) {
    egDrawImageArea(CompImage, 0, 0, 0, 0, XPos, YPos);
    egFreeImage(CompImage);
    return;
  }
  NewImage = egCreateImage(Width, Height, FALSE);
  if (!NewImage) return;
//  DBG("draw on background\n");
  egRawCopy(NewImage->PixelData,
            BackgroundImage->PixelData + YPos * BackgroundImage->Width + XPos,
            Width, Height,
            Width,
            BackgroundImage->Width);
  egComposeImage(NewImage, CompImage, 0, 0);
  egFreeImage(CompImage);

  // blit to screen and clean up
  egDrawImageArea(NewImage, 0, 0, 0, 0, XPos, YPos);
  egFreeImage(NewImage);
}

VOID BltImageComposite(IN EG_IMAGE *BaseImage, IN EG_IMAGE *TopImage, IN INTN XPos, IN INTN YPos)
{
  INTN TotalWidth, TotalHeight, CompWidth, CompHeight, OffsetX, OffsetY;
  EG_IMAGE *CompImage;

  if (!BaseImage || !TopImage) {
    return;
  }

  // initialize buffer with base image
  CompImage = egCopyImage(BaseImage);
  TotalWidth  = BaseImage->Width;
  TotalHeight = BaseImage->Height;

  // place the top image
  CompWidth = TopImage->Width;
  if (CompWidth > TotalWidth)
    CompWidth = TotalWidth;
  OffsetX = (TotalWidth - CompWidth) >> 1;
  CompHeight = TopImage->Height;
  if (CompHeight > TotalHeight)
    CompHeight = TotalHeight;
  OffsetY = (TotalHeight - CompHeight) >> 1;
  egComposeImage(CompImage, TopImage, OffsetX, OffsetY);

  // blit to screen and clean up
  //    egDrawImageArea(CompImage, 0, 0, TotalWidth, TotalHeight, XPos, YPos);
  BltImageAlpha(CompImage, XPos, YPos, &MenuBackgroundPixel, 16);
  egFreeImage(CompImage);
  GraphicsScreenDirty = TRUE;
}

/*
  --------------------------------------------------------------------
  Pos                           : Bottom    -> Mid        -> Top
  --------------------------------------------------------------------
   GlobalConfig.SelectionOnTop  : MainImage -> Badge      -> Selection
  !GlobalConfig.SelectionOnTop  : Selection -> MainImage  -> Badge

 GlobalConfig.SelectionOnTop
  BaseImage = MainImage, TopImage = Selection
*/

VOID BltImageCompositeBadge(IN EG_IMAGE *BaseImage, IN EG_IMAGE *TopImage, IN EG_IMAGE *BadgeImage, IN INTN XPos, IN INTN YPos, INTN Scale)
{
  INTN TotalWidth, TotalHeight, CompWidth, CompHeight, OffsetX, OffsetY, OffsetXTmp, OffsetYTmp;
  BOOLEAN Selected = TRUE;
  EG_IMAGE *CompImage;
  EG_IMAGE *NewBaseImage;
  EG_IMAGE *NewTopImage;
  EG_PIXEL *BackgroundPixel = &EmbeddedBackgroundPixel;
  
  if (!IsEmbeddedTheme()) {
    BackgroundPixel = &MenuBackgroundPixel;
  } else if (GlobalConfig.DarkEmbedded) {
    BackgroundPixel = &DarkEmbeddedBackgroundPixel;
  }

  if (!BaseImage || !TopImage) {
    return;
  }
  if (Scale < 0) {
    Scale = -Scale;
    Selected = FALSE;
  }

  NewBaseImage = egCopyScaledImage(BaseImage, Scale); //will be Scale/16
  TotalWidth = NewBaseImage->Width;  //mainImage sizes if GlobalConfig.SelectionOnTop
  TotalHeight = NewBaseImage->Height;

  NewTopImage = egCopyScaledImage(TopImage, Scale); //will be Scale/16
  CompWidth = NewTopImage->Width;  //selection sizes if GlobalConfig.SelectionOnTop
  CompHeight = NewTopImage->Height;
  CompImage = egCreateFilledImage((CompWidth > TotalWidth)?CompWidth:TotalWidth,
                                    (CompHeight > TotalHeight)?CompHeight:TotalHeight,
                                    TRUE,
                                    BackgroundPixel);
  
  if (!CompImage) {
    DBG("Can't create CompImage\n");
    return;
  }
//  DBG("compose image total=[%d,%d], comp=[%d,%d] at [%d,%d] scale=%d\n", TotalWidth, TotalHeight,
//      CompWidth, CompHeight, XPos, YPos, Scale);
  //to simplify suppose square images
  if (CompWidth < TotalWidth) {
    OffsetX = (TotalWidth - CompWidth) >> 1;
    OffsetY = (TotalHeight - CompHeight) >> 1;
    egComposeImage(CompImage, NewBaseImage, 0, 0);
    if (!GlobalConfig.SelectionOnTop) {
      egComposeImage(CompImage, NewTopImage, OffsetX, OffsetY);
    }
    CompWidth = TotalWidth;
    CompHeight = TotalHeight;
  } else {
    OffsetX = (CompWidth - TotalWidth) >> 1;
    OffsetY = (CompHeight - TotalHeight) >> 1;
    egComposeImage(CompImage, NewBaseImage, OffsetX, OffsetY);
    if (!GlobalConfig.SelectionOnTop) {
      egComposeImage(CompImage, NewTopImage, 0, 0);
    }
  }

  OffsetXTmp = OffsetX;
  OffsetYTmp = OffsetY;

  // place the badge image
  if (BadgeImage != NULL &&
      (BadgeImage->Width + 8) < CompWidth &&
      (BadgeImage->Height + 8) < CompHeight) {

    //blackosx
    // Check for user badge x offset from theme.plist
    if (GlobalConfig.BadgeOffsetX != 0xFFFF) {
      // Check if value is between 0 and ( width of the main icon - width of badge )
      if (GlobalConfig.BadgeOffsetX < 0 || GlobalConfig.BadgeOffsetX > (CompWidth - BadgeImage->Width)) {
        DBG("User offset X %d is out of range\n", GlobalConfig.BadgeOffsetX);
        GlobalConfig.BadgeOffsetX = CompWidth  - 8 - BadgeImage->Width;
        DBG("   corrected to default %d\n", GlobalConfig.BadgeOffsetX);
      }
      OffsetX += GlobalConfig.BadgeOffsetX;
    } else {
      // Set default position
      OffsetX += CompWidth  - 8 - BadgeImage->Width;
    }
    // Check for user badge y offset from theme.plist
    if (GlobalConfig.BadgeOffsetY != 0xFFFF) {
      // Check if value is between 0 and ( height of the main icon - height of badge )
      if (GlobalConfig.BadgeOffsetY < 0 || GlobalConfig.BadgeOffsetY > (CompHeight - BadgeImage->Height)) {
        DBG("User offset Y %d is out of range\n",GlobalConfig.BadgeOffsetY);
        GlobalConfig.BadgeOffsetY = CompHeight - 8 - BadgeImage->Height;
        DBG("   corrected to default %d\n", GlobalConfig.BadgeOffsetY);
      }
      OffsetY += GlobalConfig.BadgeOffsetY;
    } else {
      // Set default position
      OffsetY += CompHeight - 8 - BadgeImage->Height;
    }
    egComposeImage(CompImage, BadgeImage, OffsetX, OffsetY);
  }

  if (GlobalConfig.SelectionOnTop) {
    if (CompWidth < TotalWidth) {
      egComposeImage(CompImage, NewTopImage, OffsetXTmp, OffsetYTmp);
    } else {
      egComposeImage(CompImage, NewTopImage, 0, 0);
    }
  }

  // blit to screen and clean up
//  if (!IsEmbeddedTheme()) { // regular theme
    if (GlobalConfig.NonSelectedGrey && !Selected) {
      BltImageAlpha(CompImage, XPos, YPos, &MenuBackgroundPixel, -16);
    } else {
      BltImageAlpha(CompImage, XPos, YPos, &MenuBackgroundPixel, 16);
    }
/*  } else { // embedded theme - don't use BltImageAlpha as it can't handle refit's built in image
    egDrawImageArea(CompImage, 0, 0, TotalWidth, TotalHeight, XPos, YPos);
  } */
  egFreeImage(CompImage);
  egFreeImage(NewBaseImage);
  egFreeImage(NewTopImage);
  GraphicsScreenDirty = TRUE;
}
    
#define MAX_SIZE_ANIME 256

VOID FreeAnime(GUI_ANIME *Anime)
{
   if (Anime) {
     if (Anime->Path) {
       FreePool(Anime->Path);
       Anime->Path = NULL;
     }
     FreePool(Anime);
//     Anime = NULL;
   }
}

/* Replaced for now with Reposition* below
INTN RecalculateImageOffset(INTN AnimDimension, INTN ValueToScale, INTN ScreenDimensionToFit, INTN ThemeDesignDimension)
{
    INTN SuppliedGapDimensionPxDesigned=0;
    INTN OppositeGapDimensionPxDesigned=0;
    INTN OppositeGapPcDesigned=0;
    INTN ScreenDimensionLessAnim=0;
    INTN GapNumTimesLarger=0;
    INTN GapNumFinal=0;
    INTN NewSuppliedGapPx=0;
    INTN NewOppositeGapPx=0;
    INTN ReturnValue=0;
    
    SuppliedGapDimensionPxDesigned = (ThemeDesignDimension * ValueToScale) / 100;
    OppositeGapDimensionPxDesigned = ThemeDesignDimension - (SuppliedGapDimensionPxDesigned + AnimDimension);
    OppositeGapPcDesigned = (OppositeGapDimensionPxDesigned * 100)/ThemeDesignDimension;
    ScreenDimensionLessAnim = (ScreenDimensionToFit - AnimDimension);
    if (ValueToScale > OppositeGapPcDesigned) {
      GapNumTimesLarger = (ValueToScale * 100)/OppositeGapPcDesigned;
      GapNumFinal = GapNumTimesLarger + 100;
      NewOppositeGapPx = (ScreenDimensionLessAnim * 100)/GapNumFinal;
      NewSuppliedGapPx = (NewOppositeGapPx * GapNumTimesLarger)/100;
    } else if (ValueToScale < OppositeGapPcDesigned) {
      GapNumTimesLarger = (OppositeGapPcDesigned * 100)/ValueToScale;
      GapNumFinal = (GapNumTimesLarger + 100);
      NewSuppliedGapPx = (ScreenDimensionLessAnim * 100)/GapNumFinal;
      NewOppositeGapPx = (NewSuppliedGapPx * GapNumTimesLarger)/100;
    } else if (ValueToScale == OppositeGapPcDesigned) {
      NewSuppliedGapPx = (ScreenDimensionLessAnim * 100)/200;
      NewOppositeGapPx = (NewSuppliedGapPx * 100)/100;
    }
    ReturnValue = (NewSuppliedGapPx * 100)/ScreenDimensionToFit;
    
    if (ReturnValue>0 && ReturnValue<100) {
      //DBG("Different screen size being used. Adjusted original anim gap to %d\n",ReturnValue);
      return ReturnValue;
    } else {
      DBG("Different screen size being used. Adjusted value %d invalid. Returning original value %d\n",ReturnValue, ValueToScale);
      return ValueToScale;
    }
}
*/

static INTN ConvertEdgeAndPercentageToPixelPosition(INTN Edge, INTN DesiredPercentageFromEdge, INTN ImageDimension, INTN ScreenDimension)
{
  if (Edge == SCREEN_EDGE_LEFT || Edge == SCREEN_EDGE_TOP) {
      return ((ScreenDimension * DesiredPercentageFromEdge) / 100);
  } else if (Edge == SCREEN_EDGE_RIGHT || Edge == SCREEN_EDGE_BOTTOM) {
      return (ScreenDimension - ((ScreenDimension * DesiredPercentageFromEdge) / 100) - ImageDimension);
  }
  return 0xFFFF; // to indicate that wrong edge was specified.
}

static INTN CalculateNudgePosition(INTN Position, INTN NudgeValue, INTN ImageDimension, INTN ScreenDimension)
{
  INTN value=Position;
  
  if ((NudgeValue != INITVALUE) && (NudgeValue != 0) && (NudgeValue >= -32) && (NudgeValue <= 32)) {
    if ((value + NudgeValue >=0) && (value + NudgeValue <= ScreenDimension - ImageDimension)) {
     value += NudgeValue;
    }
  }
  return value;
}

static BOOLEAN IsImageWithinScreenLimits(INTN Value, INTN ImageDimension, INTN ScreenDimension)
{
  return (Value >= 0 && Value + ImageDimension <= ScreenDimension);
}

static INTN RepositionFixedByCenter(INTN Value, INTN ScreenDimension, INTN DesignScreenDimension)
{
  return (Value + ((ScreenDimension - DesignScreenDimension) / 2));
}

static INTN RepositionRelativeByGapsOnEdges(INTN Value, INTN ImageDimension, INTN ScreenDimension, INTN DesignScreenDimension)
{
  return (Value * (ScreenDimension - ImageDimension) / (DesignScreenDimension - ImageDimension));
}

static INTN HybridRepositioning(INTN Edge, INTN Value, INTN ImageDimension, INTN ScreenDimension, INTN DesignScreenDimension)
{
  INTN pos, posThemeDesign;
  
  if (DesignScreenDimension == 0xFFFF || ScreenDimension == DesignScreenDimension) {
    // Calculate the horizontal pixel to place the top left corner of the animation - by screen resolution
    pos = ConvertEdgeAndPercentageToPixelPosition(Edge, Value, ImageDimension, ScreenDimension);
  } else {
    // Calculate the horizontal pixel to place the top left corner of the animation - by theme design resolution
    posThemeDesign = ConvertEdgeAndPercentageToPixelPosition(Edge, Value, ImageDimension, DesignScreenDimension);
    // Try repositioning by center first
    pos = RepositionFixedByCenter(posThemeDesign, ScreenDimension, DesignScreenDimension);
    // If out of edges, try repositioning by gaps on edges
    if (!IsImageWithinScreenLimits(pos, ImageDimension, ScreenDimension)) {
      pos = RepositionRelativeByGapsOnEdges(posThemeDesign, ImageDimension, ScreenDimension, DesignScreenDimension);
    }
  }
  return pos;
}

static EG_IMAGE *AnimeImage = NULL;

VOID UpdateAnime(REFIT_MENU_SCREEN *Screen, EG_RECT *Place)
{
  UINT64      Now;
  INTN        x, y;
  
  //INTN LayoutAnimMoveForMenuX = 0;
  INTN MenuWidth = 50;
  
  if (!Screen || !Screen->AnimeRun || !Screen->Film || GlobalConfig.TextOnly) return;
  if (!AnimeImage ||
      (AnimeImage->Width != Screen->Film[0]->Width) ||
      (AnimeImage->Height != Screen->Film[0]->Height)){
    if (AnimeImage) {
      egFreeImage(AnimeImage);
    }
//    DBG("create new AnimeImage [%d,%d]\n", Screen->Film[0]->Width, Screen->Film[0]->Height);
    AnimeImage = egCreateImage(Screen->Film[0]->Width, Screen->Film[0]->Height, TRUE);
  }
//  DBG("anime rect pos=[%d,%d] size=[%d,%d]\n", Place->XPos, Place->YPos,
//      Place->Width, Place->Height);
//  DBG("anime size=[%d,%d]\n", AnimeImage->Width, AnimeImage->Height);
  
  // Retained for legacy themes without new anim placement options.
  x = Place->XPos + (Place->Width - AnimeImage->Width) / 2;
  y = Place->YPos + (Place->Height - AnimeImage->Height) / 2;
  
  if (!IsImageWithinScreenLimits(x, Screen->Film[0]->Width, UGAWidth) || !IsImageWithinScreenLimits(y, Screen->Film[0]->Height, UGAHeight)) {
 //   DBG(") This anime can't be displayed\n");
    return;
  }
  
  // Check if the theme.plist setting for allowing an anim to be moved horizontally in the quest 
  // to avoid overlapping the menu text on menu pages at lower resolutions is set.
  if ((Screen->ID > 1) && (LayoutAnimMoveForMenuX != 0)) { // these screens have text menus which the anim may interfere with.
    MenuWidth = (INTN)(TEXT_XMARGIN * 2 + (50 * GlobalConfig.CharWidth * GlobalConfig.Scale)); // taken from menu.c
    if ((x + Screen->Film[0]->Width) > (UGAWidth - MenuWidth) >> 1) {
      if ((x + LayoutAnimMoveForMenuX >= 0) || (UGAWidth-(x + LayoutAnimMoveForMenuX + Screen->Film[0]->Width)) <= 100) {
        x += LayoutAnimMoveForMenuX;
      }
    }
  }
  
  Now = AsmReadTsc();
  if (Screen->LastDraw == 0) {
    //first start, we should save background into last frame
    egFillImageArea(AnimeImage, 0, 0, AnimeImage->Width, AnimeImage->Height, &MenuBackgroundPixel);
    egTakeImage(Screen->Film[Screen->Frames],
                x, y,
                Screen->Film[Screen->Frames]->Width,
                Screen->Film[Screen->Frames]->Height);
  }
  if (TimeDiff(Screen->LastDraw, Now) < Screen->FrameTime) return;
  if (Screen->Film[Screen->CurrentFrame]) {
    egRawCopy(AnimeImage->PixelData, Screen->Film[Screen->Frames]->PixelData,
              Screen->Film[Screen->Frames]->Width, 
              Screen->Film[Screen->Frames]->Height,
              AnimeImage->Width,
              Screen->Film[Screen->Frames]->Width);
    AnimeImage->HasAlpha = FALSE;
    egComposeImage(AnimeImage, Screen->Film[Screen->CurrentFrame], 0, 0);  //aaaa
    BltImage(AnimeImage, x, y);
  }
  Screen->CurrentFrame++;
  if (Screen->CurrentFrame >= Screen->Frames) {
    Screen->AnimeRun = !Screen->Once;
    Screen->CurrentFrame = 0;
  }
  Screen->LastDraw = Now;
}


VOID InitAnime(REFIT_MENU_SCREEN *Screen)
{
  CHAR16      FileName[256];
  CHAR16      *Path;
  EG_IMAGE    *p = NULL;
  EG_IMAGE    *Last = NULL;
  GUI_ANIME   *Anime;

  if (!Screen || GlobalConfig.TextOnly) return;
  // 
  for (Anime = GuiAnime; Anime != NULL && Anime->ID != Screen->ID; Anime = Anime->Next);

  // Check if we should clear old film vars (no anime or anime path changed)
  //
  if (gThemeOptionsChanged || !Anime || !Screen->Film || IsEmbeddedTheme() || !Screen->Theme ||
      (/*gThemeChanged && */StriCmp(GlobalConfig.Theme, Screen->Theme) != 0)) {
//    DBG(" free screen\n");
    if (Screen->Film) {
      //free images in the film
      INTN i;
      for (i = 0; i <= Screen->Frames; i++) { //really there are N+1 frames
        // free only last occurrence of repeated frames
        if (Screen->Film[i] != NULL && (i == Screen->Frames || Screen->Film[i] != Screen->Film[i+1])) {
          FreePool(Screen->Film[i]);
        }
      }
      FreePool(Screen->Film);
      Screen->Film = NULL;
      Screen->Frames = 0;
    }
    if (Screen->Theme) {
      FreePool(Screen->Theme);
      Screen->Theme = NULL;
    }
  }
  // Check if we should load anime files (first run or after theme change)
  if (Anime && Screen->Film == NULL) {
    Path = Anime->Path;
    Screen->Film = (EG_IMAGE**)AllocateZeroPool((Anime->Frames + 1) * sizeof(VOID*));
    if ((GlobalConfig.TypeSVG || Path) && Screen->Film) {
      // Look through contents of the directory
      UINTN i;
      for (i = 0; i < Anime->Frames; i++) {

 //       DBG("Try to load file %s\n", FileName);
        if (GlobalConfig.TypeSVG) {
          p = LoadSvgFrame(i);
   //       DBG("frame %d loaded\n", i);
        } else {
          UnicodeSPrint(FileName, 512, L"%s\\%s_%03d.png", Path, Path, i);
          p = egLoadImage(ThemeDir, FileName, TRUE);
        }
        if (!p) {
          p = Last;
          if (!p) break;
        } else {
          Last = p;
        }
        Screen->Film[i] = p;
      }
      if (Screen->Film[0] != NULL) {
        Screen->Frames = i;
        DBG(" found %d frames of the anime\n", i);
        // Create background frame
        Screen->Film[i] = egCreateImage(Screen->Film[0]->Width, Screen->Film[0]->Height, FALSE);
        // Copy some settings from Anime into Screen
        Screen->FrameTime = Anime->FrameTime;
        Screen->Once = Anime->Once;
        Screen->Theme = AllocateCopyPool(StrSize(GlobalConfig.Theme), GlobalConfig.Theme);
      } /*else {
        DBG("Film[0] == NULL\n");
      } */
    }
  }
  // Check if a new style placement value has been specified
  if (Anime && (Anime->FilmX >=0) && (Anime->FilmX <=100) &&
      (Anime->FilmY >=0) && (Anime->FilmY <=100) &&
      (Screen->Film != NULL) && (Screen->Film[0] != NULL)) {
    // Check if screen size being used is different from theme origination size.
    // If yes, then recalculate the animation placement % value.
    // This is necessary because screen can be a different size, but anim is not scaled.
    Screen->FilmPlace.XPos = HybridRepositioning(Anime->ScreenEdgeHorizontal, Anime->FilmX, Screen->Film[0]->Width,  UGAWidth,  GlobalConfig.ThemeDesignWidth );
    Screen->FilmPlace.YPos = HybridRepositioning(Anime->ScreenEdgeVertical,   Anime->FilmY, Screen->Film[0]->Height, UGAHeight, GlobalConfig.ThemeDesignHeight);
    
    // Does the user want to fine tune the placement?
    Screen->FilmPlace.XPos = CalculateNudgePosition(Screen->FilmPlace.XPos, Anime->NudgeX, Screen->Film[0]->Width, UGAWidth);
    Screen->FilmPlace.YPos = CalculateNudgePosition(Screen->FilmPlace.YPos, Anime->NudgeY, Screen->Film[0]->Height, UGAHeight);
    
    Screen->FilmPlace.Width = Screen->Film[0]->Width;
    Screen->FilmPlace.Height = Screen->Film[0]->Height;
    DBG("recalculated Screen->Film position\n");
  } else {
    // We are here if there is no anime, or if we use oldstyle placement values
    // For both these cases, FilmPlace will be set after banner/menutitle positions are known
    Screen->FilmPlace.XPos = 0;
    Screen->FilmPlace.YPos = 0;
    Screen->FilmPlace.Width = 0;
    Screen->FilmPlace.Height = 0;
  }
  if (Screen->Film != NULL && Screen->Film[0] != NULL) {
    DBG(" Anime seems OK, init it\n");
    Screen->AnimeRun = TRUE;
    Screen->CurrentFrame = 0;
    Screen->LastDraw = 0;
  } else {
//    DBG("not run anime\n");
    Screen->AnimeRun = FALSE;
  }
//  DBG("anime inited\n");
}

BOOLEAN GetAnime(REFIT_MENU_SCREEN *Screen)
{
  GUI_ANIME   *Anime;
  
  if (!Screen || !GuiAnime) return FALSE;
  
  for (Anime = GuiAnime; Anime != NULL && Anime->ID != Screen->ID; Anime = Anime->Next);
  if (Anime == NULL || Anime->Path == NULL) {
    return FALSE;
  }
  
  DBG("Use anime=%s frames=%d\n", Anime->Path, Anime->Frames);
  
  return TRUE;
}

//
// Sets next/previous available screen resolution, according to specified offset
//

VOID SetNextScreenMode(INT32 Next)
{
    EFI_STATUS Status;

    Status = egSetMode(Next);
    if (!EFI_ERROR(Status)) {
        UpdateConsoleVars();
    }
}

//
// Updates console variables, according to ConOut resolution 
// This should be called when initializing screen, or when resolution changes
//

static VOID UpdateConsoleVars()
{
    UINTN i;

    // get size of text console
	if  (gST->ConOut->QueryMode(gST->ConOut, gST->ConOut->Mode->Mode, &ConWidth, &ConHeight) != EFI_SUCCESS) {
        // use default values on error
        ConWidth = 80;
        ConHeight = 25;
    }

    // free old BlankLine when it exists
    if (BlankLine != NULL) {
        FreePool(BlankLine);
    }

    // make a buffer for a whole text line
    BlankLine = AllocatePool((ConWidth + 1) * sizeof(CHAR16));
	
	for (i = 0; i < ConWidth; i++) {
        BlankLine[i] = ' ';
	}
	
    BlankLine[i] = 0;
}
