/* $Id: UIMachineWindow.cpp $ */
/** @file
 *
 * VBox frontends: Qt GUI ("VirtualBox"):
 * UIMachineWindow class implementation
 */

/*
 * Copyright (C) 2010-2013 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */

/* Qt includes: */
#include <QCloseEvent>
#include <QTimer>
#include <QProcess>

/* GUI includes: */
#include "VBoxGlobal.h"
#include "UIMessageCenter.h"
#include "UIKeyboardHandler.h"
#include "UIMachineWindow.h"
#include "UIMachineLogic.h"
#include "UIMachineView.h"
#include "UIMachineWindowNormal.h"
#include "UIMachineWindowFullscreen.h"
#include "UIMachineWindowSeamless.h"
#include "UIMachineWindowScale.h"
#include "UIMouseHandler.h"
#include "UISession.h"
#include "UIVMCloseDialog.h"
#include "UIConverter.h"

/* COM includes: */
#include "CConsole.h"
#include "CSnapshot.h"

/* Other VBox includes: */
#include <VBox/version.h>
#ifdef VBOX_BLEEDING_EDGE
# include <iprt/buildconfig.h>
#endif /* VBOX_BLEEDING_EDGE */

/* External includes: */
#ifdef Q_WS_X11
# include <X11/Xlib.h>
#endif /* Q_WS_X11 */

/* static */
UIMachineWindow* UIMachineWindow::create(UIMachineLogic *pMachineLogic, ulong uScreenId)
{
    /* Create machine-window: */
    UIMachineWindow *pMachineWindow = 0;
    switch (pMachineLogic->visualStateType())
    {
        case UIVisualStateType_Normal:
            pMachineWindow = new UIMachineWindowNormal(pMachineLogic, uScreenId);
            break;
        case UIVisualStateType_Fullscreen:
            pMachineWindow = new UIMachineWindowFullscreen(pMachineLogic, uScreenId);
            break;
        case UIVisualStateType_Seamless:
            pMachineWindow = new UIMachineWindowSeamless(pMachineLogic, uScreenId);
            break;
        case UIVisualStateType_Scale:
            pMachineWindow = new UIMachineWindowScale(pMachineLogic, uScreenId);
            break;
        default:
            AssertMsgFailed(("Incorrect visual state!"));
            break;
    }
    /* Prepare machine-window: */
    pMachineWindow->prepare();
    /* Return machine-window: */
    return pMachineWindow;
}

/* static */
void UIMachineWindow::destroy(UIMachineWindow *pWhichWindow)
{
    /* Cleanup machine-window: */
    pWhichWindow->cleanup();
    /* Delete machine-window: */
    delete pWhichWindow;
}

void UIMachineWindow::prepare()
{
    /* Prepare session-connections: */
    prepareSessionConnections();

    /* Prepare main-layout: */
    prepareMainLayout();

    /* Prepare menu: */
    prepareMenu();

    /* Prepare status-bar: */
    prepareStatusBar();

    /* Prepare machine-view: */
    prepareMachineView();

    /* Prepare visual-state: */
    prepareVisualState();

    /* Prepare handlers: */
    prepareHandlers();

    /* Load settings: */
    loadSettings();

    /* Retranslate window: */
    retranslateUi();

    /* Update all the elements: */
    updateAppearanceOf(UIVisualElement_AllStuff);

    /* Show: */
    showInNecessaryMode();
}

void UIMachineWindow::cleanup()
{
    /* Save window settings: */
    saveSettings();

    /* Cleanup handlers: */
    cleanupHandlers();

    /* Cleanup visual-state: */
    cleanupVisualState();

    /* Cleanup machine-view: */
    cleanupMachineView();

    /* Cleanup status-bar: */
    cleanupStatusBar();

    /* Cleanup menu: */
    cleanupMenu();

    /* Cleanup main layout: */
    cleanupMainLayout();

    /* Cleanup session connections: */
    cleanupSessionConnections();
}

void UIMachineWindow::sltMachineStateChanged()
{
    /* Update window-title: */
    updateAppearanceOf(UIVisualElement_WindowTitle);
}

UIMachineWindow::UIMachineWindow(UIMachineLogic *pMachineLogic, ulong uScreenId)
    : QIWithRetranslateUI2<QMainWindow>(0, windowFlags(pMachineLogic->visualStateType()))
    , m_pMachineLogic(pMachineLogic)
    , m_pMachineView(0)
    , m_uScreenId(uScreenId)
    , m_pMainLayout(0)
    , m_pTopSpacer(0)
    , m_pBottomSpacer(0)
    , m_pLeftSpacer(0)
    , m_pRightSpacer(0)
{
#ifndef Q_WS_MAC
    /* On Mac OS X application icon referenced in info.plist is used. */

    /* Set default application icon (will be changed to VM-specific icon little bit later): */
    setWindowIcon(QIcon(":/VirtualBox_48px.png"));

    /* Set VM-specific application icon: */
    setWindowIcon(vboxGlobal().vmGuestOSTypeIcon(machine().GetOSTypeId()));
#endif /* !Q_WS_MAC */

    /* Set the main application window for VBoxGlobal: */
    if (m_uScreenId == 0)
        vboxGlobal().setMainWindow(this);
}

UISession* UIMachineWindow::uisession() const
{
    return machineLogic()->uisession();
}

CSession& UIMachineWindow::session() const
{
    return uisession()->session();
}

CMachine UIMachineWindow::machine() const
{
    return session().GetMachine();
}

void UIMachineWindow::setMask(const QRegion &region)
{
    /* Call to base-class: */
    QMainWindow::setMask(region);
}

void UIMachineWindow::retranslateUi()
{
    /* Compose window-title prefix: */
    m_strWindowTitlePrefix = VBOX_PRODUCT;
#ifdef VBOX_BLEEDING_EDGE
    m_strWindowTitlePrefix += UIMachineWindow::tr(" EXPERIMENTAL build %1r%2 - %3")
                              .arg(RTBldCfgVersion())
                              .arg(RTBldCfgRevisionStr())
                              .arg(VBOX_BLEEDING_EDGE);
#endif /* VBOX_BLEEDING_EDGE */
    /* Update appearance of the window-title: */
    updateAppearanceOf(UIVisualElement_WindowTitle);
}

#ifdef Q_WS_X11
bool UIMachineWindow::x11Event(XEvent *pEvent)
{
    // TODO: Is that really needed?
    /* Qt bug: when the machine-view grabs the keyboard,
     * FocusIn, FocusOut, WindowActivate and WindowDeactivate Qt events are
     * not properly sent on top level window deactivation.
     * The fix is to substiute the mode in FocusOut X11 event structure
     * to NotifyNormal to cause Qt to process it as desired. */
    if (pEvent->type == FocusOut)
    {
        if (pEvent->xfocus.mode == NotifyWhileGrabbed  &&
            (pEvent->xfocus.detail == NotifyAncestor ||
             pEvent->xfocus.detail == NotifyInferior ||
             pEvent->xfocus.detail == NotifyNonlinear))
        {
             pEvent->xfocus.mode = NotifyNormal;
        }
    }
    return false;
}
#endif /* Q_WS_X11 */

void UIMachineWindow::closeEvent(QCloseEvent *pEvent)
{
    /* Always ignore close-event: */
    pEvent->ignore();

    /* Depending on machine-state: */
    switch (uisession()->machineState())
    {
        case KMachineState_Running:
        case KMachineState_Paused:
        case KMachineState_Stuck:
        case KMachineState_LiveSnapshotting: // TODO: Test this!
        case KMachineState_Teleporting: // TODO: Test this!
        case KMachineState_TeleportingPausedVM: // TODO: Test this!
        {
            /* Get the machine: */
            CMachine m = machine();

            /* Check if there is a close hook script defined. */
            const QString& strScript = m.GetExtraData(GUI_CloseActionHook);
            if (!strScript.isEmpty())
            {
                QProcess::startDetached(strScript, QStringList() << m.GetId());
                return;
            }

            /* Is ACPI mode enabled? */
            bool isACPIEnabled = session().GetConsole().GetGuestEnteredACPIMode();

            /* Define close-action: */
            MachineCloseAction closeAction = MachineCloseAction_Invalid;

            /* If default close-action defined and not restricted: */
            MachineCloseAction defaultCloseAction = uisession()->defaultCloseAction();
            MachineCloseAction restrictedCloseActions = uisession()->restrictedCloseActions();
            if ((defaultCloseAction != MachineCloseAction_Invalid) &&
                !(restrictedCloseActions & defaultCloseAction))
            {
                switch (defaultCloseAction)
                {
                    /* If VM is stuck, and the default close-action is 'save-state' or 'shutdown',
                     * we should ask the user about what to do: */
                    case MachineCloseAction_SaveState:
                    case MachineCloseAction_Shutdown:
                        closeAction = uisession()->isStuck() ? MachineCloseAction_Invalid : defaultCloseAction;
                        break;
                    /* Otherwise we just use what we have: */
                    default:
                        closeAction = defaultCloseAction;
                        break;
                }
            }

            /* If the close-action still undefined: */
            if (closeAction == MachineCloseAction_Invalid)
            {
                /* Read the last user's choice for the given VM: */
                MachineCloseAction lastCloseAction = gpConverter->fromInternalString<MachineCloseAction>(m.GetExtraData(GUI_LastCloseAction));
                /* Prepare close-dialog: */
                UIVMCloseDialog *pDlg = new UIVMCloseDialog(this);
                if (pDlg)
                {
                    /* Assign close-dialog pixmap: */
                    pDlg->pmIcon->setPixmap(vboxGlobal().vmGuestOSTypeIcon(m.GetOSTypeId()));

                    /* Check which close actions are disallowed: */
                    MachineCloseAction restictedCloseActions = VBoxGlobal::restrictedMachineCloseActions(m);
                    bool fIsStateSavingAllowed = !(restictedCloseActions & MachineCloseAction_SaveState);
                    bool fIsACPIShutdownAllowed = !(restictedCloseActions & MachineCloseAction_Shutdown);
                    bool fIsPowerOffAllowed = !(restictedCloseActions & MachineCloseAction_PowerOff);
                    bool fIsPowerOffAndRestoreAllowed = fIsPowerOffAllowed && !(restictedCloseActions & MachineCloseAction_PowerOff_RestoringSnapshot);

                    /* Make Save State button visible/hidden depending on restriction: */
                    pDlg->mRbSave->setVisible(fIsStateSavingAllowed);
                    pDlg->mTxSave->setVisible(fIsStateSavingAllowed);
                    /* Make Save State button enabled/disabled depending on machine state: */
                    pDlg->mRbSave->setEnabled(uisession()->machineState() != KMachineState_Stuck);

                    /* Make ACPI shutdown button visible/hidden depending on restriction: */
                    pDlg->mRbShutdown->setVisible(fIsACPIShutdownAllowed);
                    pDlg->mTxShutdown->setVisible(fIsACPIShutdownAllowed);
                    /* Make ACPI shutdown button enabled/disabled depending on ACPI state & machine state: */
                    pDlg->mRbShutdown->setEnabled(isACPIEnabled && uisession()->machineState() != KMachineState_Stuck);

                    /* Make Power Off button visible/hidden depending on restriction: */
                    pDlg->mRbPowerOff->setVisible(fIsPowerOffAllowed);
                    pDlg->mTxPowerOff->setVisible(fIsPowerOffAllowed);

                    /* Make the Restore Snapshot checkbox visible/hidden depending on snapshots count & restrictions: */
                    pDlg->mCbDiscardCurState->setVisible(fIsPowerOffAndRestoreAllowed && m.GetSnapshotCount() > 0);
                    if (!m.GetCurrentSnapshot().isNull())
                        pDlg->mCbDiscardCurState->setText(pDlg->mCbDiscardCurState->text().arg(m.GetCurrentSnapshot().GetName()));

                    /* Check which button should be initially chosen: */
                    QRadioButton *pRadioButton = 0;

                    /* If choosing 'last choice' is possible: */
                    if (lastCloseAction == MachineCloseAction_SaveState && fIsStateSavingAllowed)
                    {
                        pRadioButton = pDlg->mRbSave;
                    }
                    else if (lastCloseAction == MachineCloseAction_Shutdown && fIsACPIShutdownAllowed && isACPIEnabled)
                    {
                        pRadioButton = pDlg->mRbShutdown;
                    }
                    else if (lastCloseAction == MachineCloseAction_PowerOff && fIsPowerOffAllowed)
                    {
                        pRadioButton = pDlg->mRbPowerOff;
                    }
                    else if (lastCloseAction == MachineCloseAction_PowerOff_RestoringSnapshot && fIsPowerOffAndRestoreAllowed)
                    {
                        pRadioButton = pDlg->mRbPowerOff;
                        pDlg->mCbDiscardCurState->setChecked(true);
                    }
                    /* Else 'default choice' will be used: */
                    else
                    {
                        if (fIsStateSavingAllowed)
                            pRadioButton = pDlg->mRbSave;
                        else if (fIsACPIShutdownAllowed && isACPIEnabled)
                            pRadioButton = pDlg->mRbShutdown;
                        else if (fIsPowerOffAllowed)
                            pRadioButton = pDlg->mRbPowerOff;
                    }

                    /* If some radio button was chosen: */
                    if (pRadioButton)
                    {
                        /* Check and focus it: */
                        pRadioButton->setChecked(true);
                        pRadioButton->setFocus();
                    }
                    /* If no one of radio buttons was chosen: */
                    else
                    {
                        /* Just break and leave: */
                        delete pDlg;
                        pDlg = 0;
                        break;
                    }
                }

                /* This flag will keep the status of every further logical operation: */
                bool fSuccess = true;

                /* Pause before showing dialog if necessary: */
                bool fWasPaused = uisession()->isPaused() || uisession()->isStuck();
                if (!fWasPaused)
                    fSuccess = uisession()->pause();

                /* Let the user choose the close-action: */
                if (fSuccess)
                {
                    /* Prevent auto-closure: */
                    machineLogic()->setPreventAutoClose(true);
                    /* Show the close-dialog: */
                    bool fDialogAccepted = pDlg->exec() == QDialog::Accepted;
                    /* Allow auto-closure again: */
                    machineLogic()->setPreventAutoClose(false);

                    /* Parse dialog-result: */
                    if (!fDialogAccepted)
                        closeAction = MachineCloseAction_Invalid;
                    else if (pDlg->mRbSave->isChecked())
                        closeAction = MachineCloseAction_SaveState;
                    else if (pDlg->mRbShutdown->isChecked())
                        closeAction = MachineCloseAction_Shutdown;
                    else
                        closeAction = MachineCloseAction_PowerOff;
                    bool fDiscardCurState = pDlg->mCbDiscardCurState->isChecked();
                    bool fDiscardCheckboxVisible = pDlg->mCbDiscardCurState->isVisibleTo(pDlg);
                    if (closeAction == MachineCloseAction_PowerOff && fDiscardCheckboxVisible && fDiscardCurState)
                        closeAction = MachineCloseAction_PowerOff_RestoringSnapshot;
                }

                /* Cleanup close-dialog: */
                delete pDlg;
                pDlg = 0;

                /* If VM was not paused before but paused now,
                 * we should resume it if user canceled dialog or chosen shutdown: */
                if (!fWasPaused && uisession()->isPaused() &&
                    (closeAction == MachineCloseAction_Invalid || closeAction == MachineCloseAction_Shutdown))
                    fSuccess = uisession()->unpause();

                if (fSuccess)
                {
                    if (closeAction != MachineCloseAction_Invalid)
                    {
                        /* Memorize the last user choice: */
                        MachineCloseAction newCloseAction = closeAction;
                        /* If user choose 'PowerOff': */
                        if (closeAction == MachineCloseAction_PowerOff)
                        {
                            /* Preserve 'Shutdown' if its just not available now: */
                            if (lastCloseAction == MachineCloseAction_Shutdown && !isACPIEnabled)
                                newCloseAction = MachineCloseAction_Shutdown;
                        }
                        m.SetExtraData(GUI_LastCloseAction, gpConverter->toInternalString(newCloseAction));
                    }
                }
                else
                {
                    /* If some of steps failed, close-action will be MachineCloseAction_Invalid: */
                    closeAction = MachineCloseAction_Invalid;
                }
            }

            /* Was some close-action selected? */
            if (closeAction != MachineCloseAction_Invalid)
            {
                switch (closeAction)
                {
                    case MachineCloseAction_SaveState:
                    {
                        machineLogic()->save();
                        break;
                    }
                    case MachineCloseAction_Shutdown:
                    {
                        machineLogic()->shutdown();
                        break;
                    }
                    case MachineCloseAction_PowerOff:
                    case MachineCloseAction_PowerOff_RestoringSnapshot:
                    {
                        machineLogic()->powerOff(closeAction == MachineCloseAction_PowerOff_RestoringSnapshot);
                        break;
                    }
                    default:
                        break;
                }
            }
            break;
        }
        default:
            break;
    }
}

void UIMachineWindow::prepareSessionConnections()
{
    /* Machine state-change updater: */
    connect(uisession(), SIGNAL(sigMachineStateChange()), this, SLOT(sltMachineStateChanged()));
}

void UIMachineWindow::prepareMainLayout()
{
    /* Create central-widget: */
    setCentralWidget(new QWidget);

    /* Create main-layout: */
    m_pMainLayout = new QGridLayout(centralWidget());
    m_pMainLayout->setMargin(0);
    m_pMainLayout->setSpacing(0);

    /* Create shifting-spacers: */
    m_pTopSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding);
    m_pBottomSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding);
    m_pLeftSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed);
    m_pRightSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed);

    /* Add shifting-spacers into main-layout: */
    m_pMainLayout->addItem(m_pTopSpacer, 0, 1);
    m_pMainLayout->addItem(m_pBottomSpacer, 2, 1);
    m_pMainLayout->addItem(m_pLeftSpacer, 1, 0);
    m_pMainLayout->addItem(m_pRightSpacer, 1, 2);
}

void UIMachineWindow::prepareMachineView()
{
#ifdef VBOX_WITH_VIDEOHWACCEL
    /* Need to force the QGL framebuffer in case 2D Video Acceleration is supported & enabled: */
    bool bAccelerate2DVideo = machine().GetAccelerate2DVideoEnabled() && VBoxGlobal::isAcceleration2DVideoAvailable();
#endif /* VBOX_WITH_VIDEOHWACCEL */

    /* Get visual-state type: */
    UIVisualStateType visualStateType = machineLogic()->visualStateType();

    /* Create machine-view: */
    m_pMachineView = UIMachineView::create(  this
                                           , m_uScreenId
                                           , visualStateType
#ifdef VBOX_WITH_VIDEOHWACCEL
                                           , bAccelerate2DVideo
#endif /* VBOX_WITH_VIDEOHWACCEL */
                                           );

    /* Add machine-view into main-layout: */
    m_pMainLayout->addWidget(m_pMachineView, 1, 1, viewAlignment(visualStateType));
}

void UIMachineWindow::prepareHandlers()
{
    /* Register keyboard-handler: */
    machineLogic()->keyboardHandler()->prepareListener(m_uScreenId, this);

    /* Register mouse-handler: */
    machineLogic()->mouseHandler()->prepareListener(m_uScreenId, this);
}

void UIMachineWindow::cleanupHandlers()
{
    /* Unregister mouse-handler: */
    machineLogic()->mouseHandler()->cleanupListener(m_uScreenId);

    /* Unregister keyboard-handler: */
    machineLogic()->keyboardHandler()->cleanupListener(m_uScreenId);
}

void UIMachineWindow::cleanupMachineView()
{
    /* Destroy machine-view: */
    UIMachineView::destroy(m_pMachineView);
    m_pMachineView = 0;
}

void UIMachineWindow::handleScreenCountChange()
{
    /* Ignore if window is minimized: */
    if (isMinimized())
        return;

    /* Make sure window is in necessary mode: */
    showInNecessaryMode();
}

void UIMachineWindow::handleScreenGeometryChange()
{
    /* Ignore if window is minimized: */
    if (isMinimized())
        return;

    /* Make sure window is in necessary mode: */
    showInNecessaryMode();
}

void UIMachineWindow::updateAppearanceOf(int iElement)
{
    /* Update window title: */
    if (iElement & UIVisualElement_WindowTitle)
    {
        /* Get machine: */
        const CMachine &m = machine();
        /* Get machine state: */
        KMachineState state = uisession()->machineState();
        /* Prepare full name: */
        QString strSnapshotName;
        if (m.GetSnapshotCount() > 0)
        {
            CSnapshot snapshot = m.GetCurrentSnapshot();
            strSnapshotName = " (" + snapshot.GetName() + ")";
        }
        QString strMachineName = m.GetName() + strSnapshotName;
        if (state != KMachineState_Null)
            strMachineName += " [" + gpConverter->toString(state) + "]";
        /* Unusual on the Mac. */
#ifndef Q_WS_MAC
        strMachineName += " - " + defaultWindowTitle();
#endif /* !Q_WS_MAC */
        if (m.GetMonitorCount() > 1)
            strMachineName += QString(" : %1").arg(m_uScreenId + 1);
        setWindowTitle(strMachineName);
    }
}

#ifdef VBOX_WITH_DEBUGGER_GUI
void UIMachineWindow::updateDbgWindows()
{
    /* The debugger windows are bind to the main VM window. */
    if (m_uScreenId == 0)
        machineLogic()->dbgAdjustRelativePos();
}
#endif /* VBOX_WITH_DEBUGGER_GUI */

/* static */
Qt::WindowFlags UIMachineWindow::windowFlags(UIVisualStateType visualStateType)
{
    switch (visualStateType)
    {
        case UIVisualStateType_Normal: return Qt::Window;
        case UIVisualStateType_Fullscreen: return Qt::FramelessWindowHint;
        case UIVisualStateType_Seamless: return Qt::FramelessWindowHint;
        case UIVisualStateType_Scale: return Qt::Window;
    }
    AssertMsgFailed(("Incorrect visual state!"));
    return 0;
}

/* static */
Qt::Alignment UIMachineWindow::viewAlignment(UIVisualStateType visualStateType)
{
    switch (visualStateType)
    {
        case UIVisualStateType_Normal: return 0;
        case UIVisualStateType_Fullscreen: return Qt::AlignVCenter | Qt::AlignHCenter;
        case UIVisualStateType_Seamless: return 0;
        case UIVisualStateType_Scale: return 0;
    }
    AssertMsgFailed(("Incorrect visual state!"));
    return 0;
}

