// Updates the front panel displays, based on printer status
void FrontPanel::ShowStatus(const PrinterStatus& ps)
{
    if (ps._change != Leaving)
    {
        // display the screen for this state and sub-state
        PrinterStatusKey key = PS_KEY(ps._state, ps._UISubState);

        if (ps._state == PrintingLayerState)
        {
            // force the display of the remaining print time 
            // whenever we enter or re-enter the PrintingLayer state
            // e.g. after door closed or resuming from pause
            _forceDisplay = true;   
        }
        
        if (_screens.count(key) < 1)
        {            
            std::cout << "Unknown screen for state: " << STATE_NAME(ps._state) 
                      << ", substate: " << SUBSTATE_NAME(ps._UISubState) 
                      << std::endl;
            
            key = UNKNOWN_SCREEN_KEY;
        }
        Screen* pScreen = _screens[key];
        if (pScreen != NULL)
        {
            // make sure a display thread isn't already running
            AwaitThreadComplete();
            
            // display the selected screen in a separate thread, to
            // avoid blocking here
            FrontPanelScreen* pFPS = new FrontPanelScreen(this, ps, pScreen);
            pthread_create(&_showScreenThread, NULL, &ThreadHelper, pFPS);  
        }
    }
}
// Build the screens and add them to a map keyed by PrintEngine states and UI
// sub states.
void ScreenBuilder::BuildScreens(std::map<PrinterStatusKey, Screen*>& screenMap) 
{
    ScreenText* unknown = new ScreenText();
    unknown->Add(new ScreenLine(UNKNOWN_SCREEN_LINE1));
    unknown->Add(new ReplaceableLine(UNKNOWN_SCREEN_LINE2));
    unknown->Add(new ReplaceableLine(UNKNOWN_SCREEN_LINE3));
    screenMap[UNKNOWN_SCREEN_KEY] =  new UnknownScreen(unknown, 
                                                       UNKNOWN_SCREEN_LED_SEQ);
    
    // NULL screens for states that shouldn't change what's already displayed
    screenMap[PS_KEY(PrinterOnState, NoUISubState)] = NULL;  
    screenMap[PS_KEY(InitializingState, NoUISubState)] = NULL;
    screenMap[PS_KEY(HomeState, NoUISubState)] = NULL;
    screenMap[PS_KEY(DoorClosedState, NoUISubState)] = NULL;
    screenMap[PS_KEY(ApproachingState, NoUISubState)] = NULL; 
    screenMap[PS_KEY(SeparatingState, NoUISubState)] = NULL; 
    screenMap[PS_KEY(PreExposureDelayState, NoUISubState)] = NULL; 
    screenMap[PS_KEY(ExposingState, NoUISubState)] = NULL; 
    screenMap[PS_KEY(PressingState, NoUISubState)] = NULL; 
    screenMap[PS_KEY(PressDelayState, NoUISubState)] = NULL; 
    screenMap[PS_KEY(UnpressingState, NoUISubState)] = NULL; 
    screenMap[PS_KEY(AwaitingCancelationState, NoUISubState)] = NULL; 
    
    ScreenText* readyLoaded = new ScreenText;
    readyLoaded->Add(new ScreenLine(READY_LOADED_LINE1));
    readyLoaded->Add(new ScreenLine(READY_LOADED_LINE2));
    readyLoaded->Add(new ScreenLine(READY_LOADED_LINE3));
    readyLoaded->Add(new ScreenLine(READY_LOADED_LINE4));
    readyLoaded->Add(new ScreenLine(READY_LOADED_BTN1_LINE1));
    readyLoaded->Add(new ScreenLine(READY_LOADED_BTN1_LINE2));
    readyLoaded->Add(new ScreenLine(READY_LOADED_BTN2_LINE1));
    readyLoaded->Add(new ScreenLine(READY_LOADED_BTN2_LINE2));
    screenMap[PS_KEY(HomeState, HavePrintData)] = 
                                new Screen(readyLoaded, READY_LOADED_LED_SEQ);
    
    ScreenText* startLoaded = new ScreenText;
    startLoaded->Add(new ReplaceableLine(START_LOADED_LINE1)); 
    startLoaded->Add(new ScreenLine(START_LOADED_LINE2));
    startLoaded->Add(new ScreenLine(START_LOADED_LINE3));
    startLoaded->Add(new ScreenLine(START_LOADED_LINE4));
    startLoaded->Add(new ScreenLine(START_LOADED_LINE5));
    startLoaded->Add(new ScreenLine(START_LOADED_BTN1_LINE2));
    startLoaded->Add(new ScreenLine(START_LOADED_BTN2_LINE2));
    screenMap[PS_KEY(HomeState, LoadedPrintData)] = 
                        new JobNameScreen(startLoaded, START_LOADED_LED_SEQ);
    
    ScreenText* loadFail = new ScreenText;
    loadFail->Add(new ScreenLine(LOAD_FAIL_LINE1));
    loadFail->Add(new ScreenLine(LOAD_FAIL_LINE2));
    loadFail->Add(new ScreenLine(LOAD_FAIL_BTN2_LINE2));
    screenMap[PS_KEY(HomeState, PrintDataLoadFailed)] = 
                                new Screen(loadFail, LOAD_FAIL_LED_SEQ);
    
    ScreenText* downloadFail = new ScreenText;
    downloadFail->Add(new ScreenLine(DOWNLOAD_FAIL_LINE1));
    downloadFail->Add(new ScreenLine(DOWNLOAD_FAIL_LINE2));
    downloadFail->Add(new ScreenLine(DOWNLOAD_FAIL_BTN2_LINE2));
    screenMap[PS_KEY(HomeState, PrintDownloadFailed)] = 
                                new Screen(downloadFail, DOWNLOAD_FAIL_LED_SEQ);
    
    // the next screen contains the static portions of print status
    ScreenText* printing = new ScreenText;
    printing->Add(new ScreenLine(PRINTING_LINE1));
    printing->Add(new ReplaceableLine(PRINTING_LINE2));
    printing->Add(new ScreenLine(PRINTING_LINE4));
    printing->Add(new ScreenLine(PRINTING_BTN1_LINE2));
    printing->Add(new ScreenLine(PRINTING_BTN2_LINE2));
    screenMap[PS_KEY(PrintingLayerState, NoUISubState)] = 
                             new JobNameScreen(printing, PRINTING_LED_SEQ);
    
    // the next screen adds the remaining print time to print status
    ScreenText* countdown = new ScreenText;
    // clear the previously shown time
    countdown->Add(new ReplaceableLine(PRINTING_CLEAR_LINE3));
    // show the new remaining print time
    countdown->Add(new ReplaceableLine(PRINTING_LINE3));
    screenMap[PS_KEY(InitializingLayerState, NoUISubState)] = 
                             new PrintStatusScreen(countdown, PRINTING_LED_SEQ);  
    
    ScreenText* aboutToPause0 = new ScreenText;
    aboutToPause0->Add(new ScreenLine(ABOUT_TO_PAUSE_LINE1));
    screenMap[PS_KEY(PreExposureDelayState, AboutToPause)] = 
                            new Screen(aboutToPause0, ABOUT_TO_PAUSE_LED_SEQ,
                                                                   true, false); 
     
    ScreenText* aboutToPause1 = new ScreenText;
    aboutToPause1->Add(new ScreenLine(ABOUT_TO_PAUSE_LINE1));
    screenMap[PS_KEY(ExposingState, AboutToPause)] = 
                            new Screen(aboutToPause1, ABOUT_TO_PAUSE_LED_SEQ,
                                                                   true, false); 
     
    ScreenText* aboutToPause2 = new ScreenText;
    aboutToPause2->Add(new ScreenLine(ABOUT_TO_PAUSE_LINE1));
    screenMap[PS_KEY(SeparatingState, AboutToPause)] = 
                            new Screen(aboutToPause2, ABOUT_TO_PAUSE_LED_SEQ, 
                                                                   true, false); 
    ScreenText* aboutToPause3 = new ScreenText;
    aboutToPause3->Add(new ScreenLine(ABOUT_TO_PAUSE_LINE1));
    screenMap[PS_KEY(ApproachingState, AboutToPause)] = 
                            new Screen(aboutToPause3, ABOUT_TO_PAUSE_LED_SEQ, 
                                                                   true, false); 
   
    ScreenText* aboutToPause4 = new ScreenText;
    aboutToPause4->Add(new ScreenLine(ABOUT_TO_PAUSE_LINE1));
    screenMap[PS_KEY(MovingToPauseState, NoUISubState)] = 
                            new Screen(aboutToPause4, ABOUT_TO_PAUSE_LED_SEQ, 
                                                                   true, false);

    ScreenText* aboutToPause5 = new ScreenText;
    aboutToPause5->Add(new ScreenLine(ABOUT_TO_PAUSE_LINE1));
    screenMap[PS_KEY(PressingState, AboutToPause)] = 
                            new Screen(aboutToPause5, ABOUT_TO_PAUSE_LED_SEQ, 
                                                                   true, false);
    ScreenText* aboutToPause6 = new ScreenText;
    aboutToPause6->Add(new ScreenLine(ABOUT_TO_PAUSE_LINE1));
    screenMap[PS_KEY(PressDelayState, AboutToPause)] = 
                            new Screen(aboutToPause6, ABOUT_TO_PAUSE_LED_SEQ, 
                                                                   true, false);
    ScreenText* aboutToPause7 = new ScreenText;
    aboutToPause7->Add(new ScreenLine(ABOUT_TO_PAUSE_LINE1));
    screenMap[PS_KEY(UnpressingState, AboutToPause)] = 
                            new Screen(aboutToPause7, ABOUT_TO_PAUSE_LED_SEQ, 
                                                                  true, false);

    ScreenText* aboutToPause8 = new ScreenText;
    aboutToPause8->Add(new ScreenLine(ABOUT_TO_PAUSE_LINE1));
    screenMap[PS_KEY(InitializingLayerState, AboutToPause)] = 
                            new Screen(aboutToPause8, ABOUT_TO_PAUSE_LED_SEQ, 
                                                                  true, false);

    ScreenText* aboutToResume = new ScreenText;
    aboutToResume->Add(new ScreenLine(ABOUT_TO_RESUME_LINE1));
    screenMap[PS_KEY(MovingToResumeState, NoUISubState)] = 
                            new Screen(aboutToResume, ABOUT_TO_RESUME_LED_SEQ,
                                                                   true, false);     
    
    ScreenText* paused = new ScreenText;
    paused->Add(new ScreenLine(PAUSED_LINE1));
    paused->Add(new ScreenLine(PAUSED_LINE2));
    paused->Add(new ScreenLine(PAUSED_BTN1_LINE1));
    paused->Add(new ScreenLine(PAUSED_BTN1_LINE2));
    paused->Add(new ScreenLine(PAUSED_BTN2_LINE2));
    // don't clear LEDs before showing paused screen's animation,
    // so that it will only animate those LEDs
    screenMap[PS_KEY(PausedState, NoUISubState)] = 
                                new Screen(paused, PAUSED_LED_SEQ, true, false);
    
    ScreenText* unjamming = new ScreenText;
    unjamming->Add(new ScreenLine(UNJAMMING_LINE1));
    unjamming->Add(new ScreenLine(UNJAMMING_LINE2));
    unjamming->Add(new ScreenLine(UNJAMMING_LINE3));
    unjamming->Add(new ScreenLine(PAUSED_BTN1_LINE1));    
    unjamming->Add(new ScreenLine(PAUSED_BTN1_LINE2));
    screenMap[PS_KEY(UnjammingState, NoUISubState)] = 
                                new Screen(unjamming, UNJAMMING_LED_SEQ);
    
    ScreenText* jammed = new ScreenText;
    jammed->Add(new ScreenLine(JAMMED_LINE1));
    jammed->Add(new ScreenLine(JAMMED_LINE2));
    jammed->Add(new ScreenLine(JAMMED_LINE3));
    jammed->Add(new ScreenLine(PAUSED_BTN1_LINE1));    
    jammed->Add(new ScreenLine(PAUSED_BTN1_LINE2));
    jammed->Add(new ScreenLine(PAUSED_BTN2_LINE2));
    screenMap[PS_KEY(JammedState, NoUISubState)] = 
                                new Screen(jammed, JAMMED_LED_SEQ);
   
    ScreenText* cancelPrompt = new ScreenText;
    cancelPrompt->Add(new ScreenLine(CONFIRM_CANCEL_LINE1));
    cancelPrompt->Add(new ScreenLine(CONFIRM_CANCEL_LINE2));
    cancelPrompt->Add(new ScreenLine(CONFIRM_CANCEL_BTN1_LINE1));
    cancelPrompt->Add(new ScreenLine(CONFIRM_CANCEL_BTN2_LINE1));
    cancelPrompt->Add(new ScreenLine(CONFIRM_CANCEL_BTN1_LINE2));
    cancelPrompt->Add(new ScreenLine(CONFIRM_CANCEL_BTN2_LINE2));
    screenMap[PS_KEY(ConfirmCancelState, NoUISubState)] = 
                            new Screen(cancelPrompt, CONFIRM_CANCEL_LED_SEQ);

    ScreenText* printComplete = new ScreenText;
    printComplete->Add(new ScreenLine(PRINT_COMPLETE_LINE1));
    printComplete->Add(new ScreenLine(PRINT_COMPLETE_LINE2));
    printComplete->Add(new ScreenLine(PRINT_COMPLETE_LINE3));
    screenMap[PS_KEY(HomingState, PrintCompleted)] = 
                            new Screen(printComplete, PRINT_COMPLETE_LED_SEQ, 
                                                                   true, false);    

    ScreenText* getFeedback = new ScreenText;
    getFeedback->Add(new ScreenLine(GET_FEEDBACK_LINE1));
    getFeedback->Add(new ScreenLine(GET_FEEDBACK_LINE2));
    getFeedback->Add(new ScreenLine(GET_FEEDBACK_BTN1_LINE2));
    getFeedback->Add(new ScreenLine(GET_FEEDBACK_BTN2_LINE2));
    screenMap[PS_KEY(GettingFeedbackState, NoUISubState)] = 
                            new Screen(getFeedback, GET_FEEDBACK_LED_SEQ, 
                                                                   true, false);    
    
    ScreenText* startingPrint = new ScreenText;
    startingPrint->Add(new ScreenLine(STARTING_PRINT_LINE1));
    startingPrint->Add(new ReplaceableLine(STARTING_PRINT_LINE2));
    screenMap[PS_KEY(MovingToStartPositionState, NoUISubState)] = 
                       new JobNameScreen(startingPrint, STARTING_PRINT_LED_SEQ);
        
    ScreenText* calibratePrompt = new ScreenText;
    calibratePrompt->Add(new ScreenLine(STARTING_PRINT_LINE1));
    calibratePrompt->Add(new ReplaceableLine(STARTING_PRINT_LINE2));
    calibratePrompt->Add(new ScreenLine(CALIBRATE_PROMPT_LINE1));
    calibratePrompt->Add(new ScreenLine(CALIBRATE_PROMPT_BTN2_LINE2));
    screenMap[PS_KEY(MovingToStartPositionState, CalibratePrompt)] = 
                            new JobNameScreen(calibratePrompt, 
                                                    CALIBRATE_PROMPT_LED_SEQ);
    
    ScreenText* loadFirst = new ScreenText;
    loadFirst->Add(new ScreenLine(LOAD_FIRST_LINE1));
    loadFirst->Add(new ScreenLine(LOAD_FIRST_LINE2));
    loadFirst->Add(new ScreenLine(LOAD_FIRST_LINE3));
    loadFirst->Add(new ScreenLine(LOAD_FIRST_LINE4));
    screenMap[PS_KEY(HomeState, NoPrintData)] = 
                            new Screen(loadFirst, LOAD_FIRST_LED_SEQ);        
    
    ScreenText* downLoading = new ScreenText;
    downLoading->Add(new ScreenLine(DOWNLOADING_FILE_LINE1));
    screenMap[PS_KEY(HomeState, DownloadingPrintData)] = 
                            new Screen(downLoading, DOWNLOADING_FILE_LED_SEQ); 
    
    ScreenText* loading = new ScreenText;
    loading->Add(new ScreenLine(LOADING_FILE_LINE1));
    screenMap[PS_KEY(HomeState, LoadingPrintData)] = 
                            new Screen(loading, LOADING_FILE_LED_SEQ);        
    
    ScreenText* printCanceled = new ScreenText;
    printCanceled->Add(new ScreenLine(CANCELED_LINE1));
    printCanceled->Add(new ScreenLine(CANCELED_LINE2));
    printCanceled->Add(new ScreenLine(CANCELED_LINE3));
    screenMap[PS_KEY(HomingState, PrintCanceled)] = 
                            new Screen(printCanceled, CANCELED_LED_SEQ);    
    
    ScreenText* doorOpen = new ScreenText;
    doorOpen->Add(new ScreenLine(DOOR_OPEN_LINE1));
    doorOpen->Add(new ScreenLine(DOOR_OPEN_LINE2));
    doorOpen->Add(new ScreenLine(DOOR_OPEN_LINE3));
    doorOpen->Add(new ScreenLine(DOOR_OPEN_LINE4));
    doorOpen->Add(new ScreenLine(DOOR_OPEN_LINE5));
    screenMap[PS_KEY(DoorOpenState, NoUISubState)] = 
                            new Screen(doorOpen, DOOR_OPEN_LED_SEQ); 
    
    // when leaving door opened, just clear the screen,
    // in case next state has no screen defined
    ScreenText* doorClosed = new ScreenText;
    screenMap[PS_KEY(DoorOpenState, ExitingDoorOpen)] = 
                            new Screen(doorClosed, 0);
    
    ScreenText* error = new ScreenText;
    error->Add(new ScreenLine(ERROR_CODE_LINE1));
    error->Add(new ReplaceableLine(ERROR_CODE_LINE2));
    error->Add(new ReplaceableLine(ERROR_CODE_LINE3));
    error->Add(new ScreenLine(ERROR_CODE_BTN1_LINE2));
    screenMap[PS_KEY(ErrorState, NoUISubState)] = 
                            new ErrorScreen(error, ERROR_CODE_LED_SEQ);   
    
    ScreenText* homing = new ScreenText;
    homing->Add(new ScreenLine(HOMING_LINE1));
    homing->Add(new ScreenLine(HOMING_LINE2));
    screenMap[PS_KEY(HomingState, NoUISubState)] = 
                            new Screen(homing, HOMING_LED_SEQ);
    
    ScreenText* version = new ScreenText;
    version->Add(new ScreenLine(SYSINFO_LINE1));
    version->Add(new ScreenLine(SYSINFO_LINE2));
    version->Add(new ScreenLine(SYSINFO_LINE3));
    version->Add(new ReplaceableLine(SYSINFO_LINE4));
    version->Add(new ScreenLine(SYSINFO_BTN2_LINE2));
    screenMap[PS_KEY(ShowingVersionState, NoUISubState)] = 
                            new SysInfoScreen(version, SYSINFO_LED_SEQ);           
    
    ScreenText* calibrating = new ScreenText;
    calibrating->Add(new ScreenLine(CALIBRATING_LINE1));
    calibrating->Add(new ScreenLine(CALIBRATING_LINE2));
    calibrating->Add(new ScreenLine(CALIBRATING_BTN2_LINE2));
    screenMap[PS_KEY(CalibratingState, NoUISubState)] = 
                            new Screen(calibrating, CALIBRATING_LED_SEQ);  
    

    ScreenText* pair = new ScreenText;
    pair->Add(new ScreenLine(PAIR_ACCOUNT_LINE1));
    pair->Add(new ScreenLine(PAIR_ACCOUNT_LINE2));
    pair->Add(new ReplaceableLine(PAIR_ACCOUNT_LINE3));
    pair->Add(new ScreenLine(PAIR_ACCOUNT_LINE4));
    pair->Add(new ReplaceableLine(PAIR_ACCOUNT_LINE5));
    pair->Add(new ScreenLine(PAIR_ACCOUNT_BTN1_LINE2));
    screenMap[PS_KEY(RegisteringState, NoUISubState)] = 
                            new RegistrationScreen(pair, PAIR_ACCOUNT_LED_SEQ);       

    ScreenText* pairSuccess = new ScreenText;
    pairSuccess->Add(new ScreenLine(PAIR_SUCCESS_LINE1));
    pairSuccess->Add(new ScreenLine(PAIR_SUCCESS_LINE2));
    pairSuccess->Add(new ScreenLine(PAIR_SUCCESS_LINE3));
    pairSuccess->Add(new ScreenLine(PAIR_SUCCESS_LINE4));
    pairSuccess->Add(new ScreenLine(PAIR_SUCCESS_LINE5));
    pairSuccess->Add(new ScreenLine(PAIR_SUCCESS_BTN2_LINE2));
    screenMap[PS_KEY(HomeState, Registered)] = 
                            new Screen(pairSuccess, PAIR_SUCCESS_LED_SEQ);   
    
    ScreenText* wifiConnecting = new ScreenText;
    wifiConnecting->Add(new ScreenLine(WIFI_CONNECTING_LINE1));
    wifiConnecting->Add(new ScreenLine(WIFI_CONNECTING_LINE2));
    wifiConnecting->Add(new ScreenLine(WIFI_CONNECTING_BTN2_LINE2));
    screenMap[PS_KEY(HomeState, WiFiConnecting)] = 
                            new Screen(wifiConnecting, WIFI_CONNECTING_LED_SEQ);    
    
    ScreenText* wifiNotConnected = new ScreenText;
    wifiNotConnected->Add(new ScreenLine(WIFI_NOT_CONNECTED_LINE1));
    wifiNotConnected->Add(new ScreenLine(WIFI_NOT_CONNECTED_LINE2));
    wifiNotConnected->Add(new ScreenLine(WIFI_NOT_CONNECTED_BTN2_LINE2));
    screenMap[PS_KEY(HomeState, WiFiConnectionFailed)] = 
                       new Screen(wifiNotConnected, WIFI_NOT_CONNECTED_LED_SEQ);    
    
    ScreenText* wifiConnected = new ScreenText;
    wifiConnected->Add(new ScreenLine(WIFI_CONNECTED_LINE1));
    wifiConnected->Add(new ScreenLine(WIFI_CONNECTED_BTN2_LINE2));
    screenMap[PS_KEY(HomeState, WiFiConnected)] = 
                            new Screen(wifiConnected, WIFI_CONNECTED_LED_SEQ);

    ScreenText* demoMode = new ScreenText;
    demoMode->Add(new ScreenLine(DEMO_SCREEN_LINE1));
    demoMode->Add(new ScreenLine(DEMO_SCREEN_LINE2));
    screenMap[PS_KEY(DemoModeState, NoUISubState)] = 
                            new Screen(demoMode, DEMO_SCREEN_LED_SEQ, 
                                                                  false, false); 
    ScreenText* usbError = new ScreenText;
    usbError->Add(new ScreenLine(USB_FILE_FOUND_LINE1));
    usbError->Add(new ReplaceableLine(USB_FILE_FOUND_LINE2));
    usbError->Add(new ReplaceableLine(USB_FILE_FOUND_LINE3));
    usbError->Add(new ReplaceableLine(USB_FILE_FOUND_LINE4));
    usbError->Add(new ScreenLine(USB_FILE_FOUND_BTN1_LINE2));
    usbError->Add(new ScreenLine(USB_FILE_FOUND_BTN2_LINE2));
    screenMap[PS_KEY(HomeState, USBDriveFileFound)] = 
                       new USBFileFoundScreen(usbError, USB_FILE_FOUND_LED_SEQ);

    ScreenText* usbFileFound = new ScreenText;
    usbFileFound->Add(new ScreenLine(USB_DRIVE_ERROR_LINE1));
    usbFileFound->Add(new ScreenLine(USB_DRIVE_ERROR_LINE2));
    usbFileFound->Add(new ScreenLine(USB_DRIVE_ERROR_LINE3));
    usbFileFound->Add(new ReplaceableLine(USB_DRIVE_ERROR_LINE4));
    usbFileFound->Add(new ScreenLine(USB_DRIVE_ERROR_LINE5));
    usbFileFound->Add(new ScreenLine(USB_DRIVE_ERROR_BTN2_LINE2));
    screenMap[PS_KEY(HomeState, USBDriveError)] = 
                       new USBErrorScreen(usbFileFound, 
                                                    USB_DRIVE_ERROR_LED_SEQ); 
}