#include <iostream>
#include <QApplication>
#include <QComboBox>
#include <QGraphicsPixmapItem>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGridLayout>
#include <QIntValidator>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPixmap>
#include <QPushButton>
#include <QSlider>
#include <QTextEdit>
#include <QVBoxLayout>

#include "pssdk.h"

class CameraManagerWrapper : public QObject
{
    Q_OBJECT
public:
    CameraManagerWrapper(PSCameraManagerHandle camMgr_)
        : camMgr(camMgr_)
    {
        qRegisterMetaType<PSCameraInfo>("PSCameraInfo");

        hCameraListChanged = PSCameraListChangedSubscribe(camMgr,
            CameraListChanged, this);
        hCameraInitializationError = PSCameraInitializationErrorSubscribe(
            camMgr, CameraInitializationError, this);
    }

    virtual ~CameraManagerWrapper()
    {
        PSFree(hCameraListChanged);
        PSFree(hCameraInitializationError);
        PSFree(camMgr);
    }

    PSCameraManagerHandle GetCamMgr()
    {
        return camMgr;
    }
private:
    PSCameraManagerHandle camMgr;
    PSSubscriberHandle hCameraListChanged;
    PSSubscriberHandle hCameraInitializationError;

    void sendCameraListChanged(PSCameraInfo devInfo, int ConnectionState)
    {
        emit cameraListChanged(devInfo, ConnectionState);
    }

    static void PSSTDCALL
    CameraListChanged(void* context, PSCameraInfo* devInfo,
        int ConnectionState)
    {
        static_cast<CameraManagerWrapper*>(context)->
            sendCameraListChanged(*devInfo, ConnectionState);
        PSFree(devInfo);
    }

    void sendCameraInitializationError(QString camSystemId)
    {
        emit cameraInitializationError(camSystemId);
    }

    static void PSSTDCALL
    CameraInitializationError(void* context, int code, wchar_t* camSystemId)
    {
        static_cast<CameraManagerWrapper*>(context)->
            sendCameraInitializationError(QString::fromWCharArray(camSystemId));
        PSFree(camSystemId);
    }
signals:
    void cameraListChanged(PSCameraInfo, int);
    void cameraInitializationError(QString);
};

class CameraWrapper : public QObject
{
    Q_OBJECT

protected:
    PSSessionHandle session;
    PSSubscriberHandle hNewPreviewFrame;
    PSSubscriberHandle hZoomChanged;
    PSSubscriberHandle hPropertyListChanged;
    PSSubscriberHandle hFocusChanged;

public:
    CameraWrapper(CameraManagerWrapper* mng, PSCameraId camId)
    {
        PSOpenSession(mng->GetCamMgr(), camId, &session);

        hNewPreviewFrame = PSNewPreviewFrameSubscribe(session, NewPreviewFrame,
            this);
        hZoomChanged = PSZoomChangedSubscribe(session, ZoomChanged,
            this);
        hPropertyListChanged = PSPropertyListChangedSubscribe(session,
            PropertyListChanged, this);
        hFocusChanged = PSFocusChangedSubscribe(session,
            FocusChanged, this);
    }

    virtual ~CameraWrapper()
    {
        PSFree(hNewPreviewFrame);
        PSFree(hZoomChanged);
        PSFree(hPropertyListChanged);
        PSFree(session);
    }

    PSSessionHandle GetSession()
    {
        return session;
    }

    void Shoot()
    {
        PSShootAsync(session, NewFileAsyncCallback, this);
    }

    void StopVideo()
    {
        PSStopVideoAsync(session, NewFileAsyncCallback, this);
    }
protected:
    void sendNewPreviewFrame()
    {
        emit newPreviewFrame();
    }

    static void PSSTDCALL NewPreviewFrame(void* context)
    {
        static_cast<CameraWrapper*>(context)->sendNewPreviewFrame();
    }

    void sendZoomChanged()
    {
        emit zoomChanged();
    }

    static void PSSTDCALL ZoomChanged(void* context)
    {
        static_cast<CameraWrapper*>(context)->sendZoomChanged();
    }

    void sendPropertyListChanged()
    {
        emit propertyListChanged();
    }

    static void PSSTDCALL PropertyListChanged(void* context)
    {
        static_cast<CameraWrapper*>(context)->sendPropertyListChanged();
    }

    struct DownloadContext
    {
        CameraWrapper* obj;
        QString name;
    };

    static void PSSTDCALL NewFileAsyncCallback(void* context,
        PSFileInfo* resFileInfo)
    {
        CameraWrapper* obj = static_cast<CameraWrapper*>(context);

        DownloadContext* dc = new DownloadContext;
        dc->obj = obj;
        dc->name = QString::fromWCharArray(resFileInfo->name);

        PSDownloadFileToAsync(obj->session,
            resFileInfo->id, resFileInfo->name,  PS_TRUE,
            DownloadFileToAsyncCallback, dc);
        PSFree(resFileInfo);
    }

    void sendNewFileDownloadComplete(QString name)
    {
        emit newFileDownloadComplete(name);
    }

    static void PSSTDCALL DownloadFileToAsyncCallback(void* context,
        PSFileId fileId)
    {
        DownloadContext* dc = static_cast<DownloadContext*>(context);
        dc->obj->sendNewFileDownloadComplete(dc->name);
        delete dc;
    }

    void sendFocusChanged()
    {
        emit focusChanged();
    }

    static void PSSTDCALL FocusChanged(void* context)
    {
        static_cast<CameraWrapper*>(context)->sendFocusChanged();
    }
signals:
    void newPreviewFrame();
    void newFileDownloadComplete(QString file);
    void zoomChanged();
    void propertyListChanged();
    void focusChanged();
};

class SampleMainWindow : public QWidget
{
    Q_OBJECT
public:
    SampleMainWindow(QWidget *parent = 0);
    virtual ~SampleMainWindow();
private:
    CameraManagerWrapper* camMgr;
    CameraWrapper* camera;

    QPushButton* shootButton;
    QPushButton* onofPreview;
    QPushButton* updateAeAf;
    QPushButton* startStopVideo;
    QLabel* zoomLabel;
    QSlider* zoomSlider;
    QLabel* propNameLabel;
    QComboBox* propName;
    QLabel* propValueLabel;
    QComboBox* propValue;
    QLabel* focusLabel;
    QLineEdit* focusValEdit;
    QPushButton* focusSetButton;
    QPushButton* focusSetInfButton;
    QPushButton* focusM5Button;
    QPushButton* focusM1Button;
    QPushButton* focusP1Button;
    QPushButton* focusP5Button;

    QGraphicsScene previewScene;
    QGraphicsView previewView;
    QGraphicsPixmapItem* previewPixmapItem;

    bool isPreviewEnabled;
    bool isVideoSupported;
    bool isVideo;

    void ConnectNewCamera();
    void RealizeControls();
    void RealizeZoom();
    void RealizePropertyNameList();
    void RealizePropertyValueList();
    void RealizePropertyValueList_Enabled();
    void RealizeFocus();
    void ShowFocusVal();
public slots:
    void onCameraListChanged(PSCameraInfo devInfo, int ConnectionState);
    void onCameraInitializationError(QString camSystemId);
    void onNewPreviewFrame();
    void onShootClicked();
    void onNewFileDownloadComplete(QString name);
    void onPreviewClicked();
    void onUpdateAeAfClicked();
    void onZoomSliderReleased();
    void onZoomChanged();
    void onPropNameIndexChanged(int index);
    void onPropValueActivated(int index);
    void onPropertyListChanged();
    void onFocusChanged();
    void onFocusSetClicked();
    void onFocusSetInfClicked();
    void onFocusM5Clicked();
    void onFocusM1Clicked();
    void onFocusP1Clicked();
    void onFocusP5Clicked();
    void onStartStopVideoClicked();
};

#define ENABLE_PREVIEW_TEXT "Enable preview"
#define DISABLE_PREVIEW_TEXT "Disable preview"

SampleMainWindow::
SampleMainWindow(QWidget *parent)
    :   QWidget(parent),
        previewView(&previewScene),
        isPreviewEnabled(false),
        isVideo(false)
{
    PSInitializeSDK();
    camMgr = new CameraManagerWrapper(PSGetCameraManager());

    camera = nullptr;

    connect(camMgr, SIGNAL(cameraListChanged(PSCameraInfo, int)),
            this, SLOT(onCameraListChanged(PSCameraInfo, int)));
    connect(camMgr, SIGNAL(cameraInitializationError(QString)),
            this, SLOT(onCameraInitializationError(QString)));

    setWindowTitle("PS SDK Sample");
    resize(800,600);
    setMinimumSize(300,300);

    int spacing = 10;

    QGridLayout* controls = new QGridLayout;
    shootButton = new QPushButton("Shoot");
    controls->addWidget(shootButton, 0, 0);
    onofPreview = new QPushButton(ENABLE_PREVIEW_TEXT);
    controls->addWidget(onofPreview, 0, 1);
    updateAeAf = new QPushButton("Update AeAf");
    controls->addWidget(updateAeAf, 1, 0);
    startStopVideo = new QPushButton("Video");
    controls->addWidget(startStopVideo, 1, 1);
    controls->setRowMinimumHeight(2, spacing);
    zoomLabel = new QLabel("Zoom");
    controls->addWidget(zoomLabel, 3, 0);
    zoomSlider = new QSlider(Qt::Horizontal);
    zoomSlider->setMinimum(0);
    zoomSlider->setMaximum(100);
    controls->addWidget(zoomSlider, 4, 0, 1, 2);
    controls->setRowMinimumHeight(5, spacing);
    propNameLabel = new QLabel("Property name");
    controls->addWidget(propNameLabel, 6, 0);
    propName = new QComboBox();
    propName->setMinimumWidth(100);
    controls->addWidget(propName, 7, 0, 1, 2);
    propValueLabel = new QLabel("Property value");
    controls->addWidget(propValueLabel, 8, 0);
    propValue = new QComboBox();
    propValue->setMinimumWidth(100);
    controls->addWidget(propValue, 9, 0, 1, 2);

    controls->setRowMinimumHeight(10, spacing);

    focusLabel = new QLabel("Focus");
    controls->addWidget(focusLabel, 11, 0);
    QHBoxLayout* focus1 = new QHBoxLayout;
    focusValEdit = new QLineEdit;
    focusValEdit->setValidator(new QIntValidator(1, 1000000000, this));
    focus1->addWidget(focusValEdit);
    focusSetButton = new QPushButton("Set");
    focus1->addWidget(focusSetButton);
    focus1->addSpacing(spacing);
    focusSetInfButton = new QPushButton("Set Infinity");
    focus1->addWidget(focusSetInfButton);
    controls->addLayout(focus1, 12, 0, 1, 2);
    QHBoxLayout* focus2 = new QHBoxLayout;
    focusM5Button = new QPushButton("-5");
    focus2->addWidget(focusM5Button);
    focusM1Button = new QPushButton("-1");
    focus2->addWidget(focusM1Button);
    focusP1Button = new QPushButton("+1");
    focus2->addWidget(focusP1Button);
    focusP5Button = new QPushButton("+5");
    focus2->addWidget(focusP5Button);
    controls->addLayout(focus2, 13, 0, 1, 2);

    controls->setRowStretch(100, 100);

    QHBoxLayout *layout = new QHBoxLayout;
    layout->addWidget(&previewView);
    layout->addLayout(controls);

    setLayout(layout);

    connect(shootButton, SIGNAL(clicked()), this, SLOT(onShootClicked()));
    connect(onofPreview, SIGNAL(clicked()), this, SLOT(onPreviewClicked()));
    connect(updateAeAf, SIGNAL(clicked()), this, SLOT(onUpdateAeAfClicked()));
    connect(startStopVideo, SIGNAL(clicked()), this,
        SLOT(onStartStopVideoClicked()));
    connect(zoomSlider, SIGNAL(sliderReleased()), this,
        SLOT(onZoomSliderReleased()));
    connect(propName, SIGNAL(currentIndexChanged(int)), this,
        SLOT(onPropNameIndexChanged(int)));
    connect(propValue, SIGNAL(activated(int)), this,
        SLOT(onPropValueActivated(int)));
    connect(focusSetButton, SIGNAL(clicked()), this,
        SLOT(onFocusSetClicked()));
    connect(focusSetInfButton, SIGNAL(clicked()), this,
        SLOT(onFocusSetInfClicked()));
    connect(focusM5Button, SIGNAL(clicked()), this,
        SLOT(onFocusM5Clicked()));
    connect(focusM1Button, SIGNAL(clicked()), this,
        SLOT(onFocusM1Clicked()));
    connect(focusP1Button, SIGNAL(clicked()), this,
        SLOT(onFocusP1Clicked()));
    connect(focusP5Button, SIGNAL(clicked()), this,
        SLOT(onFocusP5Clicked()));

    RealizeControls();
    ConnectNewCamera();
}

SampleMainWindow::
~SampleMainWindow()
{
    delete camera;
    delete camMgr;
    PSTerminateSDK();
}

void SampleMainWindow::
RealizeControls()
{
    static QWidget* allControls[] = {
        onofPreview, updateAeAf, zoomLabel, propNameLabel, propName,
        propValueLabel};

    bool isEnabled = camera != nullptr;

    for ( unsigned int i = 0; i < sizeof(allControls) / sizeof(allControls[0]);
                                                                        i++ )
        allControls[i]->setEnabled(isEnabled);

    shootButton->setEnabled(isEnabled && !isVideo);
    startStopVideo->setEnabled(isEnabled && isVideoSupported);

    startStopVideo->setText(!isVideo ? "Start Video" : "Stop Video");

    if ( camera )
    {
        onofPreview->setText(isPreviewEnabled
            ? DISABLE_PREVIEW_TEXT : ENABLE_PREVIEW_TEXT);
    }

    RealizeZoom();
    RealizePropertyNameList();
    RealizePropertyValueList_Enabled();
    RealizeFocus();
}

void SampleMainWindow::
RealizeZoom()
{
    zoomSlider->setEnabled(camera != nullptr);

    if ( camera )
    {
        int maxZoom;
        PSGetMaximumZoom(camera->GetSession(), &maxZoom);
        zoomSlider->setMaximum(maxZoom);

        int zoomPos;
        PSGetZoom(camera->GetSession(), &zoomPos);
        zoomSlider->setSliderPosition(zoomPos);
    }
}

void SampleMainWindow::
RealizePropertyNameList()
{
    if ( !camera )
    {
        propName->clear();
        return;
    }

    int* list;
    int listLen;
    int i;

    PSGetPropertyList(camera->GetSession(), &list, &listLen);

    bool hasPrevIdx = propName->currentIndex() >= 0;
    int prevSel;
    int prevSelIdx = 0;
    if ( hasPrevIdx )
        prevSel = propName->itemData(propName->currentIndex()).toInt();

    propName->clear();
    for ( i = 0; i < listLen; i++ )
    {
        const char* name;
        PSGetPropertyName(list[i], &name);
        propName->addItem(name, list[i]);

        if ( hasPrevIdx && prevSel == list[i] )
            prevSelIdx = i;
    }

    if ( hasPrevIdx )
        propName->setCurrentIndex(prevSelIdx);

    PSFree(list);
}

void SampleMainWindow::
RealizePropertyValueList()
{
    propValue->clear();

    if ( !camera )
        return;

    if ( propName->currentIndex() < 0 )
        return;

    int selectedProp = propName->itemData(propName->currentIndex()).toInt();
    PSProp_Desc* desc = nullptr;
    PSGetPropertyDesc(camera->GetSession(), selectedProp, &desc);
    int currPropVal;
    PSGetPropertyData(camera->GetSession(), selectedProp, &currPropVal);

    int currValIdx = -1;

    for ( int i = 0; i < desc->availableValuesLength; i++ )
    {
        const char* name;
        PSGetPropertyValName(selectedProp, desc->availableValues[i], &name);

        if ( selectedProp == PSProp::PS_ImageSize )
        {
            QString nm;
            nm.sprintf("%s (%dx%d)", name,
                desc->extendedValueInfo[i].ImageSize.width,
                desc->extendedValueInfo[i].ImageSize.height);
            propValue->addItem(nm, desc->availableValues[i]);
        }
        else
            propValue->addItem(name, desc->availableValues[i]);

        if ( desc->availableValues[i] == currPropVal )
            currValIdx = i;
    }

    PSFree(desc);
    propValue->setCurrentIndex(currValIdx);
    RealizePropertyValueList_Enabled();
}

void SampleMainWindow::
RealizePropertyValueList_Enabled()
{
    bool enabled = false;

    if ( camera && propName->currentIndex() >= 0 )
    {
        int selectedProp = propName->itemData(propName->currentIndex()).toInt();
        PSProp_Desc* desc = nullptr;
        PSGetPropertyDesc(camera->GetSession(), selectedProp, &desc);
        enabled = desc->isReadOnly != PS_TRUE;
        PSFree(desc);
    }

    propValue->setEnabled(enabled);
}

void SampleMainWindow::
RealizeFocus()
{
    bool enabled = false;
    bool hasProp = false;

    if ( camera )
    {
        int val;
        if ( PSGetPropertyData(camera->GetSession(),
            PSProp::PS_ManualFocusMode, &val) == PSResult::PS_OK)
        {
            enabled = val == PSProp_ManualFocusMode::PS_ManualFocusMode_On;
            hasProp = true;
        }
    }

    static QWidget* controls[] = {
        focusLabel, focusValEdit, focusSetButton, focusSetInfButton,
        focusM5Button, focusM1Button, focusP1Button, focusP5Button};

    for ( unsigned int i = 0; i < sizeof(controls) / sizeof(controls[0]); i++ )
        controls[i]->setEnabled(enabled);

    if ( !enabled )
        focusValEdit->setText("");

    if ( hasProp )
        ShowFocusVal();
}

void SampleMainWindow::
ShowFocusVal()
{
    if ( !camera )
        return;

    int distance;
    PSGetFocus(camera->GetSession(), &distance);

    QString v;
    if ( distance != -1 )
        v.sprintf("%d", distance);
    else
        v = "Infinity";

    focusValEdit->setText(v);
}

void SampleMainWindow::
onFocusSetClicked()
{
    if ( !camera )
        return;

    QString strVal = focusValEdit->text();
    bool ok;
    int val = strVal.toInt(&ok);
    if ( strVal == "Infinity" )
        val = -1;
    if ( val == -1 || ok )
        PSSetFocus(camera->GetSession(), val);
    ShowFocusVal();
}

void SampleMainWindow::
onFocusSetInfClicked()
{
    if ( !camera )
        return;
    PSSetFocus(camera->GetSession(), -1);
    ShowFocusVal();
}

void SampleMainWindow::
onFocusM5Clicked()
{
    if ( !camera )
        return;
    PSFocusShift(camera->GetSession(), -5);
}

void SampleMainWindow::
onFocusM1Clicked()
{
    if ( !camera )
        return;
    PSFocusShift(camera->GetSession(), -1);
}

void SampleMainWindow::
onFocusP1Clicked()
{
    if ( !camera )
        return;
    PSFocusShift(camera->GetSession(), +1);
}

void SampleMainWindow::
onFocusP5Clicked()
{
    if ( !camera )
        return;
    PSFocusShift(camera->GetSession(), +5);
}

void SampleMainWindow::
onPropNameIndexChanged(int index)
{
    RealizePropertyValueList();
}

void SampleMainWindow::
onPropValueActivated(int index)
{
    if ( !camera || propName->currentIndex() < 0 )
        return;

    int selectedProp = propName->itemData(propName->currentIndex()).toInt();
    int newVal = propValue->itemData(index).toInt();
    PSSetPropertyData(camera->GetSession(), selectedProp, newVal);
    RealizeControls();
}

void SampleMainWindow::
onZoomSliderReleased()
{
    if ( camera )
    {
        PSSetZoom(camera->GetSession(), zoomSlider->sliderPosition());
    }
}

void SampleMainWindow::
onCameraListChanged(PSCameraInfo devInfo, int ConnectionState)
{
    if ( ConnectionState == PSCameraConnectionState::PS_CS_CONNECTED )
        ConnectNewCamera();
    else
    {
        if ( camera )
        {
            int state;
            PSGetCameraConnectionState(camera->GetSession(), &state);
            if ( state == PSCameraConnectionState::PS_CS_DISCONNECTED )
            {
                delete camera;
                camera = nullptr;
                isPreviewEnabled = false;
                RealizeControls();
                previewScene.clear();
            }
        }
    }
}

void SampleMainWindow::
ConnectNewCamera()
{
    if ( !camera )
    {
        PSCameraInfo* camList;
        int camListLen;

        PSGetCameraList(camMgr->GetCamMgr(), &camList, &camListLen);

        if ( camListLen )
        {
            camera = new CameraWrapper(camMgr, camList[0].id);

            connect(camera, SIGNAL(newPreviewFrame()),
                this, SLOT(onNewPreviewFrame()));
            connect(camera, SIGNAL(newFileDownloadComplete(QString)),
                this, SLOT(onNewFileDownloadComplete(QString)));
            connect(camera, SIGNAL(zoomChanged()),
                this, SLOT(onZoomChanged()));
            connect(camera, SIGNAL(propertyListChanged()),
                this, SLOT(onPropertyListChanged()));
            connect(camera, SIGNAL(focusChanged()),
                this, SLOT(onFocusChanged()));

            isVideoSupported =
                PSIsVideoSupported(camera->GetSession()) == PS_TRUE;
            isVideo = false;

            RealizeControls();
        }

        PSFree(camList);
    }
}

void SampleMainWindow::
onCameraInitializationError(QString camSystemId)
{
    QMessageBox::critical(this, "Error",
        QString("Camera Initialization Error. Camera System ID: ") +
        camSystemId);
}

void SampleMainWindow::
onNewPreviewFrame()
{
    if ( !camera )
        return;

    previewScene.clear();

    if ( !isPreviewEnabled )
        return;

    void* data;
    int size;

    PSGetPreviewFrame(camera->GetSession(), &data, &size);

    QPixmap pxm;
    pxm.loadFromData((uchar*)data, size);

    previewPixmapItem = new QGraphicsPixmapItem(pxm);
    previewScene.addItem(previewPixmapItem);
    previewView.fitInView(previewScene.sceneRect(), Qt::KeepAspectRatio);

    PSFree(data);
}

void SampleMainWindow::
onZoomChanged()
{
    RealizeZoom();
}

void SampleMainWindow::
onShootClicked()
{
    if ( camera )
        camera->Shoot();
}

void SampleMainWindow::
onPreviewClicked()
{
    if ( camera )
    {
        PSSetPreviewState(camera->GetSession(),
            isPreviewEnabled
            ? PSPreviewState::PS_PREVIEW_DISABLED
            : PSPreviewState::PS_PREVIEW_ENABLED);
        isPreviewEnabled = !isPreviewEnabled;
        previewScene.clear();
        RealizeControls();
    }
}

void SampleMainWindow::
onNewFileDownloadComplete(QString name)
{
    QMessageBox::information(this, "New file",
        QString("New file downloading complete: ") + name);
}

void SampleMainWindow::
onUpdateAeAfClicked()
{
    if ( camera )
        PSUpdateAEAF(camera->GetSession());
}

void SampleMainWindow::
onPropertyListChanged()
{
    RealizeControls();
}

void SampleMainWindow::
onFocusChanged()
{
    ShowFocusVal();
}

void SampleMainWindow::
onStartStopVideoClicked()
{
    if ( !camera )
        return;

    if ( !isVideo )
    {
        PSStartVideo(camera->GetSession());
        isVideo = true;
    }
    else
    {
        isVideo = false;
        camera->StopVideo();
    }

    RealizeControls();
}

int
main(int argc, char* argv[])
{
    QApplication app(argc, argv);

    SampleMainWindow mwnd;
    mwnd.show();

    return app.exec();
}

#include "main.moc"
