// Copyright 1984-2019 The MathWorks, Inc.
// All Rights Reserved.

#include <iostream>
#include "mps/client.h"

void handleErrors(mpsClientRuntime* mpsruntime, mpsClientContext* client)
{

    mpsErrorInfo err;
    mpsruntime->getLastErrorInfo(client, &err);
    std::cout << "Error message : " << err.message << std::endl;

    switch (err.type) {

    case MPS_HTTP_ERROR_INFO:
        std::cout << "HTTP response code : " << err.details.http.responseCode << std::endl;
        std::cout << "HTTP response message : " << err.details.http.responseMessage << std::endl;
        break;

    case MPS_MATLAB_ERROR_INFO: {
        std::cout << "MATLAB error message : " << err.details.matlab.message << std::endl;
        std::cout << "MATLAB identifier : " << err.details.matlab.identifier << std::endl;

        size_t stackDepth = err.details.matlab.matlabStackDepth;
        std::cout << "MATLAB stack depth : " << stackDepth << std::endl;

        for (size_t i = 0; i < stackDepth; i++) {
            std::cout << "MATLAB function : " << err.details.matlab.matlabStack[i].function
                      << std::endl;
            std::cout << "MATLAB file : " << err.details.matlab.matlabStack[i].file << std::endl;
            std::cout << "MATLAB line in the function : " << err.details.matlab.matlabStack[i].line
                      << std::endl;
            std::cout << std::endl;
        }
    } break;

    case MPS_GENERIC_ERROR_INFO:
        std::cout << "General error thrown : " << err.details.general.genericErrorMsg << std::endl;
        break;
    }
    mpsruntime->destroyLastErrorInfo(&err);
}

void printStudentsArray(mpsArray* students)
{

    size_t numEle = mpsGetNumberOfElements(students);
    int numFields = mpsGetNumberOfFields(students);

    for (size_t i = 0; i < numEle; i++) {
        std::cout << "Student" << (i + 1) << std::endl;
        for (int j = 0; j < numFields; j++) {
            std::cout << "\t" << mpsGetFieldNameByNumber(students, j) << ":";
            mpsArray* field = mpsGetFieldByNumber(students, i, j);

            if (mpsIsChar(field)) {
                size_t numChars = mpsGetNumberOfElements(field) + 1;
                char* dstArr = new char[numChars];
                mpsGetString(field, dstArr, numChars);
                std::cout << dstArr << std::endl;
                delete[] dstArr;

            } else if (mpsIsDouble(field)) {

                double* data = mpsGetPr(field);
                std::cout << data[0] << std::endl;
            }
        }
        std::cout << std::endl;
    }
}

void sortstudents(mpsClientRuntime* mpsruntime, mpsClientContext* client)
{

    int nlhs = 1, nrhs = 1, nfields = 3, nEle = 3;
    mpsArray** plhs = new mpsArray*[nlhs];
    const mpsArray** const prhs = new const mpsArray*[nrhs];
    const char* fieldnames[] = {"name", "score", "grade"};

    mpsArray* students = mpsCreateStructMatrix(1, nEle, nfields, fieldnames);

    mpsArray* name1 = mpsCreateString("Toni Miller");
    mpsSetField(students, 0, "name", name1);
    mpsArray* score1 = mpsCreateDoubleScalar(90);
    mpsSetField(students, 0, "score", score1);
    mpsArray* grade1 = mpsCreateString("A");
    mpsSetField(students, 0, "grade", grade1);

    mpsArray* name2 = mpsCreateString("Ed Plum");
    mpsSetField(students, 1, "name", name2);
    mpsArray* score2 = mpsCreateDoubleScalar(80);
    mpsSetField(students, 1, "score", score2);
    mpsArray* grade2 = mpsCreateString("B+");
    mpsSetField(students, 1, "grade", grade2);

    mpsArray* name3 = mpsCreateString("Mark Jones");
    mpsSetField(students, 2, "name", name3);
    mpsArray* score3 = mpsCreateDoubleScalar(85);
    mpsSetField(students, 2, "score", score3);
    mpsArray* grade3 = mpsCreateString("A-");
    mpsSetField(students, 2, "grade", grade3);

    prhs[0] = students;

    std::cout << "INPUT TO MATLAB:" << std::endl;
    printStudentsArray(students);

    mpsStatus status = mpsruntime->feval(client, "http://localhost:9910/studentsorter/sortstudents",
                                         1, plhs, 1, prhs);

    if (status == MPS_OK) {

        std::cout << "OUTPUT FROM MATLAB:" << std::endl;
        printStudentsArray(plhs[0]);

        mpsDestroyArray(plhs[0]);

    } else {
        handleErrors(mpsruntime, client);
    }

    mpsDestroyArray(name1);
    mpsDestroyArray(score1);
    mpsDestroyArray(grade1);
    mpsDestroyArray(name2);
    mpsDestroyArray(score2);
    mpsDestroyArray(grade2);
    mpsDestroyArray(name3);
    mpsDestroyArray(score3);
    mpsDestroyArray(grade3);

    mpsDestroyArray(const_cast<mpsArray*>(prhs[0]));
    delete[] prhs;
    delete[] plhs;
}

int main(void)
{
    /**
    Following call needs to be made only once per application. It performs following
    two important tasks:
    1. Initialization of underlying HTTP framework
    2. Create a function table of MPS client API
*/
    mpsClientRuntime* mpsruntime = mpsInitialize();

    /**
    Create client configuration. The default configuration uses following
    parameters:
    1. Response timeout of 120 sec (2 mins)
    2. Response size limit of 64 Mb
*/
    mpsClientConfig* config;
    mpsruntime->createConfig(&config);

    /**
    Initialize client context using configuration. The client
    context gets used for invoking the MATLAB function
*/
    mpsClientContext* client;
    mpsStatus status = mpsruntime->createContext(&client, config);

    if (status == MPS_OK) {

        /**
            Invoke MATLAB function
        */
        sortstudents(mpsruntime, client);

        /**
    Destroy mpsClientContext and mpsClientConfig
*/
        mpsruntime->destroyConfig(config);
        mpsruntime->destroyContext(client);

    } else {
        std::cout << "Error initializing mpsClientContext" << std::endl;
    }

    /**
        Termination of underlying HTTP framework
    */
    mpsTerminate();

    return 0;
}