#include <QtCore/QCoreApplication>
#include <QTextStream>
#include <QCommandLineParser>
#include <QDir>

#include <iostream>

#include "Header_Files/oceanOpticsFiberImager.h"
#include "Header_Files/atpFiberImager.h"
#include "Header_Files/calibration.h"

enum CommandLineParseResult
{
    CommandLineOk,
    CommandLineError,
    CommandLineVersionRequested,
    CommandLineHelpRequested
};

enum DeviceType
{
    OPTOSKY,
    OceanOptics,
    UnknownDevice
};

struct TcQuery
{
    DeviceType deviceType;
    QString serialPort;

    int sleepTimeinSecond;//有默认值
    int averageTimes;
    int position;
    int integratingSphereDetectorValue;

    QString calFileOutputDirectory;//有默认值
    QString calFileOutputName;
    QString standardLightFilePath;

    bool justRecord;
};

CommandLineParseResult parseCommandLine2(QCommandLineParser &parser, TcQuery *query, QString *errorMessage);
bool copyFileToPath(QString sourceDir ,QString toDir, bool coverFileIfExist);
void logout(QString str);
void createDirectory(QString fullPath);
bool isFileExist(QString fullFileName);

int getNonlinearityCoeffs2(long deviceID, double * nonlinearityCoeffs);
int getNonlinearityCoeffs1(double * nonlinearityCoeffs);

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QCoreApplication::setApplicationName("Ocean optics radiance calibration software");
    QCoreApplication::setApplicationVersion("1.0");

    // 解析命令行参数
    QCommandLineParser parser;
    parser.setApplicationDescription("This software is used for doing radiance calibration for ocean optics fiber imager.");
    TcQuery query;
    QString errorMessage;

    switch (parseCommandLine2(parser, &query, &errorMessage))
    {
        case CommandLineOk:
            break;
        case CommandLineError:
            errorMessage = "<br><b style=\"color:red\">" + errorMessage + "s!</b>";
            logout(errorMessage);

//            fputs(qPrintable(errorMessage), stderr);
            fputs("\n\n", stderr);
            fputs(qPrintable(parser.helpText()), stderr);
            return 1;
        case CommandLineVersionRequested:
            printf("%s %s\n", qPrintable(QCoreApplication::applicationName()),
                   qPrintable(QCoreApplication::applicationVersion()));
            return 0;
        case CommandLineHelpRequested:
            parser.showHelp();
            Q_UNREACHABLE();
    }




    //创建光谱仪对象
    FiberSpectrometerOperationBase * m_FiberSpectrometer;
    bool isOcean = false;
    double * nonlinearityCoeffs;
    int numberOfNonlinearityCoeffs;
    switch (query.deviceType)
    {
        case OPTOSKY:
            m_FiberSpectrometer = new ATPFiberImager(false,query.serialPort.toStdString(),"OPTOSKY");
            break;
        case OceanOptics:
        {
            //使用sbapi读取海洋光学仪器的非线性定标参数
            nonlinearityCoeffs = new double[100];
            numberOfNonlinearityCoeffs = getNonlinearityCoeffs1(nonlinearityCoeffs);

            m_FiberSpectrometer = new OceanOpticsFiberImager(nonlinearityCoeffs, numberOfNonlinearityCoeffs);
            isOcean = true;
            break;
        }
        case UnknownDevice:
            parser.showHelp();
            Q_UNREACHABLE();
    }


    //连接光谱仪
    QString message;
    QString SN;
    QString pixelCount;
    QString wavelengthInfo;
    logout("<br><b style=\"color:red\">Connectting the fiber spectrometer!</b>");
    m_FiberSpectrometer->connectFiberSpectrometer(SN, pixelCount, wavelengthInfo);



    //自动曝光
    logout("<br><b style=\"color:red\">AutoExpose!</b>");
    m_FiberSpectrometer->autoExpose();//

    int iExposureTime;
    m_FiberSpectrometer->getExposureTime(iExposureTime);

    message="<br><b style=\"color:red\">ExposureTime: " + QString::number(iExposureTime) + "</b>";
    logout(message);

    //程序sleep：等待关闭快门
    message="<br><b style=\"color:red\">Please close the lamp in " + QString::number(query.sleepTimeinSecond) + "s!</b>";
    logout(message);
    QThread::sleep(query.sleepTimeinSecond);

    //采集暗帧
    logout("<br><b style=\"color:red\">Record dark frame!</b>");
    m_FiberSpectrometer->recordDark(query.calFileOutputDirectory);


    //程序sleep：等待打开快门
    message="<br><b style=\"color:red\">Please open the lamp in " + QString::number(query.sleepTimeinSecond) + "s!</b>";
    logout(message);
    QThread::sleep(query.sleepTimeinSecond);

    //采集积分球光谱
    logout("<br><b style=\"color:red\">Record integrating sphere frame!</b>");
    m_FiberSpectrometer->recordTarget(query.averageTimes, query.calFileOutputDirectory);

    //准备定标文件数据
    logout("<br><b style=\"color:red\">readAndResample_StandardLightFile!</b>");
    DeviceAttribute deviceAttribute;
    DeviceInfo deviceInfo;
    m_FiberSpectrometer->getDeviceAttribute(deviceAttribute);
    m_FiberSpectrometer->getDeviceInfo(deviceInfo);

    CalibrationAlgorithm * m_CalibrationAlgorithm = new CalibrationAlgorithm();
    m_CalibrationAlgorithm->readAndResample_StandardLightFile(query.standardLightFilePath,query.integratingSphereDetectorValue, deviceAttribute, deviceInfo);

    //生成辐射定标文件
    if (query.calFileOutputName.isEmpty())//query->calFileOutputName==""
    {
        QDateTime curDateTime = QDateTime::currentDateTime();
        QString currentTime = curDateTime.toString("yyyy_MM_dd_hh_mm_ss");
        QString calFileName = QDir::cleanPath(query.calFileOutputDirectory + QDir::separator() + currentTime + "_" + QString::fromStdString(deviceInfo.strSN) + ".dat");

        query.calFileOutputName=calFileName;
    }
    logout("<br><b style=\"color:red\">Produce calibration file!</b>");
    m_CalibrationAlgorithm->produceCalfile(query.calFileOutputName, deviceAttribute, m_FiberSpectrometer->m_IntegratingSphereData, m_FiberSpectrometer->m_DarkData);

    //复制辐射定标文件
    QDateTime curDateTime = QDateTime::currentDateTime();
    QString currentTime = curDateTime.toString("yyyy_MM_dd_hh_mm_ss");
    QString destName = QDir::cleanPath(query.calFileOutputDirectory + QDir::separator() + currentTime + "_" + QString::fromStdString(deviceInfo.strSN) + "_" +QString::number(query.position) + ".cal");
    copyFileToPath(query.calFileOutputName,destName,true);


    //将海洋光学仪器的非线性定标参数写到文件中
    if (isOcean)
    {
        QDateTime curDateTime = QDateTime::currentDateTime();
        QString currentTime = curDateTime.toString("yyyy_MM_dd_hh_mm_ss");
        QString nonlinearityCoeffsName = QDir::cleanPath(query.calFileOutputDirectory + QDir::separator() + currentTime + "_" + QString::fromStdString(deviceInfo.strSN) + ".nonLinear");

//        for (int i = 0; i < numberOfNonlinearityCoeffs; ++i)
//        {
//            printf("\n");
//
//            printf("nonlinearityCoeffs(第%d个): %1.2e\n",i , nonlinearityCoeffs[i]);
//
//            printf("\n");
//        }

        std::ofstream outfile(nonlinearityCoeffsName.toStdString().c_str());

        for (int i = 0; i < numberOfNonlinearityCoeffs; i++)
        {
            outfile << nonlinearityCoeffs[i] << std::endl;
        }
        outfile.close();

        free(nonlinearityCoeffs);
    }

    //断开光谱仪
    m_FiberSpectrometer->disconnectFiberSpectrometer();//要放在这句代码之后：free(nonlinearityCoeffs);
    //return a.exec();
}

CommandLineParseResult parseCommandLine2(QCommandLineParser &parser, TcQuery *query, QString *errorMessage)
{
    parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);

    QCommandLineOption deviceType("deviceType", "Device type. Options are OPTOSKY and OceanOptics", "deviceType");
    parser.addOption(deviceType);

    QCommandLineOption serialPort("serialPort", "Serial port.", "serialPort");
    parser.addOption(serialPort);

    QCommandLineOption sleepTimeinSecond("t", "The time app sleep.", "sleepTimeinSecond");
    sleepTimeinSecond.setDefaultValue("30");//设置默认参数
    parser.addOption(sleepTimeinSecond);

    QCommandLineOption averageTimes("a", "Average times.", "average_times");
    averageTimes.setDefaultValue("5");//设置默认参数
    parser.addOption(averageTimes);//

    QCommandLineOption position("position", "Position.", "position");
    parser.addOption(position);

    QCommandLineOption integratingSphereDetectorValue("integratingSphereDetectorValue", "integratingSphereDetectorValue.", "integratingSphereDetectorValue");
    parser.addOption(integratingSphereDetectorValue);

//    parser.addPositionalArgument("name", "The name to look up.");//????????????????????????????????????????????????????????????????????????????
    QCommandLineOption helpOption = parser.addHelpOption();//Adds the help option (-h, --help and -? on Windows) This option is handled automatically by QCommandLineParser.
    QCommandLineOption versionOption = parser.addVersionOption();//This option is handled automatically by QCommandLineParser.


    //// A boolean option with a single name (-p)
    //QCommandLineOption showProgressOption("p", QCoreApplication::translate("main", "Show progress during copy"));
    //parser.addOption(showProgressOption);

    // A boolean option with multiple names (-r, --record)
    QCommandLineOption recordOption(QStringList() << "f" << "record",
                                    QCoreApplication::translate("main", "Just record one spectral."));
    parser.addOption(recordOption);


    //标准灯文件
    QCommandLineOption standardLightFilePath(QStringList() << "slfp" << "standard-light-file-path",
                                             QCoreApplication::translate("main", "set standard light file."),
                                             QCoreApplication::translate("main", "file"));
    parser.addOption(standardLightFilePath);

    QCommandLineOption standardLightFileSelector(QStringList() << "slfs" << "standard-light-file-selector",
                                                 QCoreApplication::translate("main", "select standard light file."),
                                                 QCoreApplication::translate("main", "file"));
    parser.addOption(standardLightFileSelector);


    //定标文件输出路径
    // An option with a value
    QCommandLineOption calFileOutputDirectory(QStringList() << "cfod" << "calibration-file-output-directory",
                                              QCoreApplication::translate("main", "Save cal file into <directory>."),
                                              QCoreApplication::translate("main", "directory"));
//    QString tmpPath1 = QDir::cleanPath(QDir::rootPath() + QDir::separator()+"calFile");
    QString tmpPath1 = "/home/data/Cal/";
    calFileOutputDirectory.setDefaultValue(tmpPath1);//设置默认参数，QCoreApplication::applicationDirPath()，standardLightFile
    parser.addOption(calFileOutputDirectory);

    //定标文件输出文件名
    // An option with a value
    QCommandLineOption calFileOutputName(QStringList() << "cfon" << "calibration-file-output-name",
                                         QCoreApplication::translate("main", "Cal file name."),
                                         QCoreApplication::translate("main", "fileName"));
    parser.addOption(calFileOutputName);














    if (!parser.parse(QCoreApplication::arguments()))//Process the actual command line arguments given by the user
    {
        *errorMessage = parser.errorText();
        return CommandLineError;
    }

    if (parser.isSet(versionOption))
        return CommandLineVersionRequested;

    if (parser.isSet(helpOption))
        return CommandLineHelpRequested;

    if (parser.isSet(deviceType))
    {
        const QString deviceTypeTmp = parser.value(deviceType);

        if (deviceTypeTmp=="OPTOSKY")
        {
            query->deviceType = OPTOSKY;
        }
        else if(deviceTypeTmp=="OceanOptics")
        {
            query->deviceType = OceanOptics;
        }
        else
        {
            *errorMessage = "DeviceType set error.";
            return CommandLineError;
        }

    }
    else//默认参数
    {
        *errorMessage = "No deviceType set.";
        return CommandLineError;
    }

    if (parser.isSet(serialPort))
    {
        const QString serialPortTmp = parser.value(serialPort);
        query->serialPort = serialPortTmp;
    }
    else//默认参数
    {
        if (query->deviceType == OceanOptics)
        {
            ;
        } else if (query->deviceType == OPTOSKY)
        {
            *errorMessage = "No serialPort set.";
            return CommandLineError;
        }
    }

    if (parser.isSet(sleepTimeinSecond))
    {
        const QString timeTmp = parser.value(sleepTimeinSecond);
        query->sleepTimeinSecond = timeTmp.toInt();
    }
    else//默认参数
    {
        QStringList tmp = sleepTimeinSecond.defaultValues();
        query->sleepTimeinSecond = tmp[0].toInt();
    }

    if (parser.isSet(averageTimes))
    {
        const QString averageTimesTmp = parser.value(averageTimes);

        string tttt=averageTimesTmp.toStdString();

        query->averageTimes = averageTimesTmp.toInt();
    }
    else//默认参数
    {
        QStringList tmp = averageTimes.defaultValues();
        query->averageTimes = tmp[0].toInt();
    }

    if (parser.isSet(position))
    {
        const QString positionTmp = parser.value(position);
        query->position = positionTmp.toInt();
    }
    else
    {
        *errorMessage = "No position set.";
        return CommandLineError;
    }

    query->justRecord = parser.isSet(recordOption);


    if (!parser.isSet(standardLightFilePath) && !parser.isSet(standardLightFileSelector))//没有设置定标保存文件路径
    {
        *errorMessage = "No standard light file set.";
        return CommandLineError;
    }

    if (parser.isSet(standardLightFileSelector))//
    {
        QString selector = parser.value(standardLightFileSelector);
//        QString standardLightFilePath_tmp = QDir::cleanPath(QDir::rootPath() + QDir::separator() + "standardLightFile" + QDir::separator() + selector);

        QString tmp = "/home/data/Setting/StandardLightFile";
        QString standardLightFilePath_tmp = tmp + QDir::separator() + selector;

        string xx=standardLightFilePath_tmp.toStdString();

        //判断定标文件是否存在
        if (!isFileExist(standardLightFilePath_tmp))
        {
            *errorMessage = "Standard light file '" + selector + "' does not exist!";
            return CommandLineError;
        }

        query->standardLightFilePath = standardLightFilePath_tmp;
    }

    if (parser.isSet(standardLightFilePath))//
    {
        query->standardLightFilePath = parser.value(standardLightFilePath);
    }

    if (parser.isSet(integratingSphereDetectorValue))
    {
        if(query->standardLightFilePath.contains("ocean_optics.lmp",Qt::CaseSensitive))
        {
            query->integratingSphereDetectorValue = -1;
        }
        else
        {
            const QString integratingSphereDetectorValueTmp = parser.value(integratingSphereDetectorValue);
            query->integratingSphereDetectorValue = integratingSphereDetectorValueTmp.toInt();
        }
    }
    else
    {
        *errorMessage = "No integratingSphereDetectorValue set.";
        return CommandLineError;
    }


    if (parser.isSet(calFileOutputDirectory))//定标保存文件路径
    {
        query->calFileOutputDirectory = parser.value(calFileOutputDirectory);
        createDirectory(query->calFileOutputDirectory);//如果文件夹不存在 则创建
    }
    else//默认参数
    {
        QStringList tmp = calFileOutputDirectory.defaultValues();
        QString directory = tmp[0];

        createDirectory(directory);//如果文件夹不存在 则创建

        query->calFileOutputDirectory = directory;
    }

    if (parser.isSet(calFileOutputName))//-------
    {
        QString calFileOutputNameTmp = QDir::cleanPath(query->calFileOutputDirectory + QDir::separator() + parser.value(calFileOutputName));
        query->calFileOutputName = calFileOutputNameTmp;
    }
    else//默认参数
    {
        query->calFileOutputName = "";//使用此参数时，如果query->calFileOutputName为空 → 会给此变量赋值
    }

//    const QStringList positionalArguments = parser.positionalArguments();
//    if (positionalArguments.isEmpty())
//    {
//        *errorMessage = "Argument 'name' missing.";
//        return CommandLineError;
//    }
//    if (positionalArguments.size() > 1)
//    {
//        *errorMessage = "Several 'name' arguments specified.";
//        return CommandLineError;
//    }

    return CommandLineOk;
}

bool copyFileToPath(QString sourceDir ,QString toDir, bool coverFileIfExist)
{
    toDir.replace("\\","/");
    if (sourceDir == toDir){
        return true;
    }
    if (!QFile::exists(sourceDir)){
        return false;
    }
    QDir *createfile     = new QDir;
    bool exist = createfile->exists(toDir);
    if (exist){
        if(coverFileIfExist){
            createfile->remove(toDir);
        }
    }//end if

    if(!QFile::copy(sourceDir, toDir))
    {
        return false;
    }
    return true;
}

void logout(QString str)
{
    std::cout << str.toStdString() << "<br>";
    std::fflush(stdout);
}

void createDirectory(QString fullPath)//
{
    QDir dir(fullPath);
    if (dir.exists())
    {
        return;
    }
    else
    {
        bool ok = dir.mkdir(fullPath);//只创建一级子目录，即必须保证上级目录存在
        return;
    }
}

bool isFileExist(QString fullFileName)
{
    QFileInfo fileInfo(fullFileName);
    if (fileInfo.isFile())
    {
        return true;
    }
    return false;
}

int getNonlinearityCoeffs1(double * nonlinearityCoeffs)
{
    int number_of_devices;
    long *device_ids;
    int i;
    int test_index;
    int flag;
    int error = 0;
    char nameBuffer[80];


    /* Give the driver a chance to initialize itself */
    sbapi_initialize();

//    printf("Probing for devices...\n"); fflush(stdout);
    sbapi_probe_devices();

//#define RS232_TEST
#ifdef RS232_TEST
    printf("Adding an STS at 9600 baud...\n");
    /* Uncomment for Linux */
    //sbapi_add_RS232_device_location("STS", "/dev/ttyS0", 9600);
    //sbapi_add_RS232_device_location("STS", "/dev/ttyUSB0", 9600);

    /* Uncomment for Windows */
    //sbapi_add_RS232_device_location("STS", "COM1", 9600);

    /* Uncomment for e.g. USB-RS232 adapter under OSX */
    //sbapi_add_RS232_device_location("STS", "/dev/tty.KeySerial1", 9600);
    //sbapi_add_RS232_device_location("STS", "/dev/tty.usbserial", 9600);
#endif

    /* This shows how to add network devices (note that most use TCP/IP) */
    //sbapi_add_TCPIPv4_device_location("Jaz", "192.168.1.150", 7654);
    //sbapi_add_TCPIPv4_device_location("Blaze", "192.168.1.151", 57357);

//    printf("Getting device count...\n"); fflush(stdout);
    number_of_devices = sbapi_get_number_of_device_ids();

//    printf("Device count is %d\n", number_of_devices);
    if(0 == number_of_devices) {
        return 0;
    }

//    printf("Getting device IDs...\n");
    device_ids = (long *)calloc(number_of_devices, sizeof(long));
    number_of_devices = sbapi_get_device_ids(device_ids, number_of_devices);
//    printf("Got %d device ID%s.\n", number_of_devices, number_of_devices == 1 ? "" : "s");

    int number;
    for(i = 0; i < number_of_devices; i++) {
//        printf("%d: Device 0x%02lX:\n", i, device_ids[i]);
//        printf("\tGetting device type...\n");
        flag = sbapi_get_device_type(device_ids[i], &error, nameBuffer, 79);
//        printf("\t\tResult is (%d) [%s]\n", flag, sbapi_get_error_string(error));
        if(flag > 0) {
//            printf("\tDevice type: [%s]\n", nameBuffer);
        }

        /* Open the device */
//        printf("\tAttempting to open:\n");
        flag = sbapi_open_device(device_ids[i], &error);
//        printf("\t\tResult is (%d) [%s]\n", flag, sbapi_get_error_string(error));

        // jump to the next iteration if there was a problem
        if(flag != 0) {
            continue;
        }

        number = getNonlinearityCoeffs2(device_ids[i],nonlinearityCoeffs);

        /* Close the device */
//        printf("\tAttempting to close:\n");
        sbapi_close_device(device_ids[i], &error);
//        printf("\t\tResult is (%d) [%s]\n", flag, sbapi_get_error_string(error));
    }
    free(device_ids);

//    printf("Finished testing.\n");

    /* Clean up memory allocated by the driver */
    sbapi_shutdown();

    return number;
}//

//返回值是非线性校正系数的个数
int getNonlinearityCoeffs2(long deviceID, double * nonlinearityCoeffs)
{
    int error = 0;
    int number_of_nonlinearity_coeff_features;
    long *nonlinearity_coeff_feature_ids = 0;
    double buffer[10];
    int i;
    int length = 0;

//    printf("\n\tTesting nonlinearity coefficient features:\n");

//    printf("\t\tGetting number of nonlinearity coefficient features:\n");
    number_of_nonlinearity_coeff_features =
            sbapi_get_number_of_nonlinearity_coeffs_features(deviceID, &error);
//    printf("\t\t\tResult is %d [%s]\n", number_of_nonlinearity_coeff_features,
//           sbapi_get_error_string(error));

    if(0 == number_of_nonlinearity_coeff_features) {
        printf("\tNo nonlinearity coefficient capabilities found.\n");

        return 0;
    }

    nonlinearity_coeff_feature_ids =
            (long *)calloc(number_of_nonlinearity_coeff_features, sizeof(long));
//    printf("\t\tGetting nonlinearity coefficient feature IDs...\n");
    number_of_nonlinearity_coeff_features = sbapi_get_nonlinearity_coeffs_features(
            deviceID, &error, nonlinearity_coeff_feature_ids,
            number_of_nonlinearity_coeff_features);
//    printf("\t\t\tResult is %d [%s]\n", number_of_nonlinearity_coeff_features,
//           sbapi_get_error_string(error));

    for(i = 0; i < number_of_nonlinearity_coeff_features; i++)
    {
//        printf("\t\t%d: Testing device 0x%02lX, nonlinearity coeffs 0x%02lX\n",
//               i, deviceID, nonlinearity_coeff_feature_ids[i]);

//        printf("\t\t\tAttempting to get nonlinearity coefficients...\n");
        memset(nonlinearityCoeffs, (int)0, 20);//----------------------------------------------------------------------------
        length = sbapi_nonlinearity_coeffs_get(deviceID,
                                               nonlinearity_coeff_feature_ids[i], &error, nonlinearityCoeffs, 20);
//        printf("\t\t\t\tResult is %d [%s]\n", length, sbapi_get_error_string(error));

        if(0 == error && length > 0) {
//            printf("\t\t\t\tFirst calibration term: %1.2e\n", nonlinearityCoeffs[0]);
//            printf("\t\t\t\tFirst calibration term: %1.2e\n", nonlinearityCoeffs[1]);
//            printf("\t\t\t\tFirst calibration term: %1.2e\n", nonlinearityCoeffs[2]);
//            printf("\t\t\t\tFirst calibration term: %1.2e\n", nonlinearityCoeffs[3]);
//            printf("\t\t\t\tFirst calibration term: %1.2e\n", nonlinearityCoeffs[4]);
//            printf("\t\t\t\tFirst calibration term: %1.2e\n", nonlinearityCoeffs[5]);
//            printf("\t\t\t\tFirst calibration term: %1.2e\n", nonlinearityCoeffs[6]);
        }

//        printf("\t\t%d: Finished testing device 0x%02lX, nonlinearity coeffs 0x%02lX\n",
//               i, deviceID, nonlinearity_coeff_feature_ids[i]);
    }
    free(nonlinearity_coeff_feature_ids);



//    printf("\tFinished testing nonlinearity coefficient capabilities.\n");

    return length;
}