/** @file
  Implement UnitTestResultReportLib doing plain txt out to console

  Copyright (c) Microsoft Corporation.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Uefi.h>
#include <Library/UnitTestResultReportLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>

VOID
ReportPrint (
  IN CONST CHAR8  *Format,
  ...
  );

VOID
ReportOutput (
  IN CONST CHAR8  *Output
  );

struct _UNIT_TEST_STATUS_STRING {
  UNIT_TEST_STATUS  Status;
  CHAR8             *String;
};

struct _UNIT_TEST_FAILURE_TYPE_STRING {
  FAILURE_TYPE  Type;
  CHAR8         *String;
};

struct _UNIT_TEST_STATUS_STRING  mStatusStrings[] = {
  { UNIT_TEST_PASSED,                     "PASSED"},
  { UNIT_TEST_ERROR_PREREQUISITE_NOT_MET, "NOT RUN - PREREQUISITE FAILED"},
  { UNIT_TEST_ERROR_TEST_FAILED,          "FAILED"},
  { UNIT_TEST_RUNNING,                    "RUNNING"},
  { UNIT_TEST_PENDING,                    "PENDING"},
  { 0,                                    "**UNKNOWN**"}
};

struct _UNIT_TEST_FAILURE_TYPE_STRING mFailureTypeStrings[] = {
  { FAILURETYPE_NOFAILURE,         "NO FAILURE"},
  { FAILURETYPE_OTHER,             "OTHER FAILURE"},
  { FAILURETYPE_ASSERTTRUE,        "ASSERT_TRUE FAILURE"},
  { FAILURETYPE_ASSERTFALSE,       "ASSERT_FALSE FAILURE"},
  { FAILURETYPE_ASSERTEQUAL,       "ASSERT_EQUAL FAILURE"},
  { FAILURETYPE_ASSERTNOTEQUAL,    "ASSERT_NOTEQUAL FAILURE"},
  { FAILURETYPE_ASSERTNOTEFIERROR, "ASSERT_NOTEFIERROR FAILURE"},
  { FAILURETYPE_ASSERTSTATUSEQUAL, "ASSERT_STATUSEQUAL FAILURE"},
  { FAILURETYPE_ASSERTNOTNULL,     "ASSERT_NOTNULL FAILURE"},
  { FAILURETYPE_EXPECTASSERT,      "EXPECT_ASSERT FAILURE"},
  { 0,                             "*UNKNOWN* Failure"}
};

//
// TEST REPORTING FUNCTIONS
//

STATIC
CONST CHAR8*
GetStringForUnitTestStatus (
  IN UNIT_TEST_STATUS  Status
  )
{
  UINTN  Index;

  for (Index = 0; Index < ARRAY_SIZE (mStatusStrings) - 1; Index++) {
    if (mStatusStrings[Index].Status == Status) {
      //
      // Return string from matching entry
      //
      return mStatusStrings[Index].String;
    }
  }
  //
  // Return last entry if no match found.
  //
  return mStatusStrings[Index].String;
}

STATIC
CONST CHAR8*
GetStringForFailureType (
  IN FAILURE_TYPE  Failure
  )
{
  UINTN  Index;

  for (Index = 0; Index < ARRAY_SIZE (mFailureTypeStrings) - 1; Index++) {
    if (mFailureTypeStrings[Index].Type == Failure) {
      //
      // Return string from matching entry
      //
      return mFailureTypeStrings[Index].String;
    }
  }
  //
  // Return last entry if no match found.
  //
  DEBUG((DEBUG_INFO, "%a Failure Type does not have string defined 0x%X\n", __FUNCTION__, (UINT32)Failure));
  return mFailureTypeStrings[Index].String;
}

/*
  Method to print the Unit Test run results

  @retval  Success
*/
EFI_STATUS
EFIAPI
OutputUnitTestFrameworkReport (
  IN UNIT_TEST_FRAMEWORK_HANDLE  FrameworkHandle
  )
{
  UNIT_TEST_FRAMEWORK         *Framework;
  INTN                        Passed;
  INTN                        Failed;
  INTN                        NotRun;
  UNIT_TEST_SUITE_LIST_ENTRY  *Suite;
  UNIT_TEST_LIST_ENTRY        *Test;
  INTN                        SPassed;
  INTN                        SFailed;
  INTN                        SNotRun;

  Passed = 0;
  Failed = 0;
  NotRun = 0;
  Suite = NULL;

  Framework = (UNIT_TEST_FRAMEWORK *)FrameworkHandle;
  if (Framework == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  ReportPrint ("---------------------------------------------------------\n");
  ReportPrint ("------------- UNIT TEST FRAMEWORK RESULTS ---------------\n");
  ReportPrint ("---------------------------------------------------------\n");

  //print the version and time

  //
  // Iterate all suites
  //
  for (Suite = (UNIT_TEST_SUITE_LIST_ENTRY*)GetFirstNode(&Framework->TestSuiteList);
    (LIST_ENTRY*)Suite != &Framework->TestSuiteList;
    Suite = (UNIT_TEST_SUITE_LIST_ENTRY*)GetNextNode(&Framework->TestSuiteList, (LIST_ENTRY*)Suite)) {

    Test = NULL;
    SPassed = 0;
    SFailed = 0;
    SNotRun = 0;

    ReportPrint ("/////////////////////////////////////////////////////////\n");
    ReportPrint ("  SUITE: %a\n", Suite->UTS.Title);
    ReportPrint ("   PACKAGE: %a\n", Suite->UTS.Name);
    ReportPrint ("/////////////////////////////////////////////////////////\n");

    //
    // Iterate all tests within the suite
    //
    for (Test = (UNIT_TEST_LIST_ENTRY*)GetFirstNode(&(Suite->UTS.TestCaseList));
      (LIST_ENTRY*)Test != &(Suite->UTS.TestCaseList);
      Test = (UNIT_TEST_LIST_ENTRY*)GetNextNode(&(Suite->UTS.TestCaseList), (LIST_ENTRY*)Test)) {

      ReportPrint ("*********************************************************\n");
      ReportPrint ("  CLASS NAME: %a\n", Test->UT.Name);
      ReportPrint ("  TEST:    %a\n", Test->UT.Description);
      ReportPrint ("  STATUS:  %a\n", GetStringForUnitTestStatus (Test->UT.Result));
      ReportPrint ("  FAILURE: %a\n", GetStringForFailureType (Test->UT.FailureType));
      ReportPrint ("  FAILURE MESSAGE:\n%a\n", Test->UT.FailureMessage);

      if (Test->UT.Log != NULL) {
        ReportPrint ("  LOG:\n");
        ReportOutput (Test->UT.Log);
      }

      switch (Test->UT.Result) {
      case UNIT_TEST_PASSED:
        SPassed++;
        break;
      case UNIT_TEST_ERROR_TEST_FAILED:
        SFailed++;
        break;
      case UNIT_TEST_PENDING:               // Fall through...
      case UNIT_TEST_RUNNING:               // Fall through...
      case UNIT_TEST_ERROR_PREREQUISITE_NOT_MET:
        SNotRun++;
        break;
      default:
        break;
      }
      ReportPrint ("**********************************************************\n");
    } //End Test iteration

    ReportPrint ("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
    ReportPrint ("Suite Stats\n");
    ReportPrint (" Passed:  %d  (%d%%)\n", SPassed, (SPassed * 100)/(SPassed+SFailed+SNotRun));
    ReportPrint (" Failed:  %d  (%d%%)\n", SFailed, (SFailed * 100) / (SPassed + SFailed + SNotRun));
    ReportPrint (" Not Run: %d  (%d%%)\n", SNotRun, (SNotRun * 100) / (SPassed + SFailed + SNotRun));
    ReportPrint ("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n" );

    Passed += SPassed;  //add to global counters
    Failed += SFailed;  //add to global counters
    NotRun += SNotRun;  //add to global counters
  }//End Suite iteration

  ReportPrint ("=========================================================\n");
  ReportPrint ("Total Stats\n");
  ReportPrint (" Passed:  %d  (%d%%)\n", Passed, (Passed * 100) / (Passed + Failed + NotRun));
  ReportPrint (" Failed:  %d  (%d%%)\n", Failed, (Failed * 100) / (Passed + Failed + NotRun));
  ReportPrint (" Not Run: %d  (%d%%)\n", NotRun, (NotRun * 100) / (Passed + Failed + NotRun));
  ReportPrint ("=========================================================\n" );

  return EFI_SUCCESS;
}