#include "commission_func.h"

int begin_fcgi() {

    // Set up commissioning enviornment variables.

    bool parseJson, is_err, commissioning = false, complete = false;
    int popen_ret, atmpt_cntr = 0;
    Json::Reader reader;
    Json::Value j_post;
    Json::StyledWriter swriter;
    std::string posted, parse_cmd, parse_logger;
    std::string aasid = "";
    std::string tmp_usrname = "";
    std::string tmp_pass = "";
    std::streambuf * cin_streambuf = std::cin.rdbuf();
    std::streambuf * cout_streambuf = std::cout.rdbuf();
    std::streambuf * cerr_streambuf = std::cerr.rdbuf();
    FILE *fpopen;
    char fileread[MAX_GET];

    //set up the syslog event
    openlog("commissioning.fcgi", LOG_PID | LOG_PERROR, LOG_LOCAL0);

    //set up fcgi environment
    FCGX_Request request;

    FCGX_Init();
    FCGX_InitRequest(&request, 0, 0);

    //listen for requests
    while (FCGX_Accept_r(&request) == 0 && !complete) {

        // set streambuffers to fcgi request
        fcgi_streambuf cin_fcgi_streambuf(request.in);
        fcgi_streambuf cout_fcgi_streambuf(request.out);
        fcgi_streambuf cerr_fcgi_streambuf(request.err);

        std::cin.rdbuf(&cin_fcgi_streambuf);
        std::cout.rdbuf(&cout_fcgi_streambuf);
        std::cerr.rdbuf(&cerr_fcgi_streambuf);


        // receive data from POST
        posted = get_request_content(request);


        if (posted.length() == 0) {
            //standard output (check if commissioning is on)
            std::cout << confirmCommissioning();
        } else {
            //receive POSTed Json data
            bool parseJson = reader.parse(posted, j_post);

            //error state
            if (!parseJson) {
                syslog (LOG_ALERT, "Received unparsible data");
                std::cout << ERROR_PARSE;
                std::cout << "Content-type: text/plain"
                << "\r\n"
                << "Output : "
                << posted 
                <<"\r\n";

            //if sent data doesn't follow proper formatting
            } else if (!properInput(j_post)) {
                std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_MALDATA, AASTYPE_ERR);
                syslog (LOG_ALERT, "Received malformed user data entry");

            //if we are not in the middle of a commissioning session
            } else if (!commissioning) {

                //if the user requests an illegal username
                if(!legalName(j_post["username"].asString())) {
                    std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_ILLEGALUSRNAME, AASTYPE_ERR);
                    syslog (LOG_ALERT, "Received request for unauthorized username");

                } else {

                    // start a new commissioning session
                    commissioning = true;
                    atmpt_cntr = 0;
                    tmp_usrname = j_post["username"].asString();
                    aasid = gen_aasid();

                    //send confirmation message
                    std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, false, aasid, MSG_NEWPASSWD, AASTYPE_QSTN);
                    parse_cmd = "Received username: " + tmp_usrname + ", awaiting password request";
                    syslog (LOG_ALERT, parse_cmd.c_str());
                }
            // we're in a commissioning session           
            } else {
                if(tmp_pass == "") {
                    //password request
                    if(j_post["aasAnswer"].asString() == "") {
                        //password was omitted
                        std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_NOPASS, AASTYPE_ERR);
                        parse_cmd = tmp_usrname + " password omission";
                        syslog (LOG_ALERT, parse_cmd.c_str());

                    }else if (j_post["aasID"] != aasid) {
                        //aasID mismatch
                        std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_BADAASID, AASTYPE_ERR);
                        parse_cmd = tmp_usrname + " aasid does not match";
                        syslog (LOG_ALERT, parse_cmd.c_str());
                    } else if (j_post["username"].asString() != tmp_usrname) {
                        //user ID mismatch
                        std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_USRMISMATCH, AASTYPE_ERR);
                        parse_cmd = tmp_usrname + " returned a mismatched username mid session";
                        syslog (LOG_ALERT, parse_cmd.c_str());
                    } else {
                        //successful password request
                        atmpt_cntr = 0;
                        tmp_pass = j_post["aasAnswer"].asString();
                        std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, false, aasid, MSG_RETYPEPASS, AASTYPE_QSTN);
                        parse_cmd = tmp_usrname + " successfuly requested a password. Awaiting verification";
                        syslog (LOG_ALERT, parse_cmd.c_str());
                    }
                } else {
                    //password request
                    if(j_post["aasAnswer"].asString() == "") {
                        //password was omitted
                        std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_NOPASS, AASTYPE_ERR);
                        parse_cmd = tmp_usrname + " password omission";

                    }else if (j_post["aasID"] != aasid) {
                        //aasID mismatch
                        std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_BADAASID, AASTYPE_ERR);
                        parse_cmd = tmp_usrname + " aasid does not match";
                        syslog (LOG_ALERT, parse_cmd.c_str());

                    } else if (j_post["username"].asString() != tmp_usrname) {
                        //user ID mismatch
                        std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_USRMISMATCH, AASTYPE_ERR);
                        parse_cmd = tmp_usrname + " returned a mismatched username mid session";
                        syslog (LOG_ALERT, parse_cmd.c_str());

                    } else if (j_post["aasAnswer"].asString() != tmp_pass) {
                        std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, true, aasid, ERR_PWMISMATCH, AASTYPE_ERR);
                        parse_cmd = tmp_usrname + " verification attempt did not match";
                        syslog (LOG_ALERT, parse_cmd.c_str());

                    } else {
                        //successful password request
                        atmpt_cntr = 0;
                        commissioning = false;
                        std::cout << printMsg(commissioning, atmpt_cntr, tmp_pass, false, aasid, MSG_SUCCESS, AASTYPE_INFO);
                        parse_cmd = tmp_usrname + " data entry successful. Creating user account";
                        syslog (LOG_ALERT, parse_cmd.c_str());

                        /******
                         *  COMMISSIONING CODE
                         * ***/

                        /****useradd****/
                        fpopen = popen(useradd_cmd_gen(tmp_usrname).c_str(), "r");
                        if (fpopen == NULL) {
                            syslog (LOG_ALERT, "popen for useradd failed to return a useful pointer");
                            //handle error via logging, closing
                        }

                        while (fgets(fileread, MAX_GET, fpopen) != NULL) {
                            printf("%s", fileread);
                        }
                        popen_ret = pclose(fpopen);
                        if (popen_ret == -1) {
                            syslog (LOG_ALERT, "pclose for useradd failed");
                            //handle error via logging, closing
                        }

                        /****passwd****/
                        fpopen = popen(passwd_cmd_gen(tmp_usrname).c_str(), "w");
                        if (fpopen == NULL) {
                            syslog (LOG_ALERT, "popen for passwd failed to return a useful pointer");
                            //handle error via logging, closing                           
                        }
                        fprintf(fpopen, "%s\n", tmp_pass.c_str());
                        fprintf(fpopen, "%s\n", tmp_pass.c_str());
                        popen_ret = pclose(fpopen);
                        if (popen_ret == -1) {
                            syslog (LOG_ALERT, "pclose for passwd failed");
                            // handle error via logging, closing
                        }

                        /****mts-ubpasswd****/
                        fpopen = popen(POPEN_MTS_UBPW.c_str(), "w");
                        if (fpopen == NULL) {
                            syslog (LOG_ALERT, "mts-ubpasswd for passwd failed to return a useful pointer");
                            //handle error via logging, closing
                        }
                        fputs(tmp_pass.c_str(), fpopen);
                        popen_ret = pclose(fpopen);
                        if (popen_ret == -1) {
                            syslog (LOG_ALERT, "pclose for mts-ubpasswd failed");
                            //handle error via logging, closing
                        }
                        /*****
                         *   EMPTY TEMP VALS
                         * ***
                         * 

                        /******
                         *   CLOSEOUTCODE
                         * ****/
                        fpopen = popen(POPEN_CLOSEALL.c_str(), "r");
                        if (fpopen == NULL) {
                            syslog (LOG_ALERT, "closeall routine failed to return a useful pointer");
                            //handle error via logging, closing
                        } 

                        while (fgets(fileread, MAX_GET, fpopen) != NULL) {
                            printf("%s", fileread);
                        }
                        popen_ret = pclose(fpopen);
                        if (popen_ret == -1) {
                            syslog (LOG_ALERT, "pclose for closeall routine failed ");
                            //handle error via logging, closing
                        }
                        complete = true;
                    }
                }
            }
        }
    }

    // restore stdio streambufs
    std::cin.rdbuf(cin_streambuf);
    std::cout.rdbuf(cout_streambuf);
    std::cerr.rdbuf(cerr_streambuf);

    return 0;
}

/*****message delivery function definitions ********************************************/

std::string confirmCommissioning() {
    std::string formatted_output;
    Json::Value output;
    Json::StyledWriter writer;

    output["code"] = 200;
    output["status"] = "success";

    formatted_output = HEADER + writer.write(output) + "\n";

    return formatted_output;
}

std::string printMsg(bool &is_commission, int &atmpt_cntr, std::string &pw, bool is_err, std::string aasid, std::string msg, std::string aastype) {
    std::string formatted_output, msg_output;
    Json::Value output;
    Json::StyledWriter writer;
    bool aasDone = false;
    int retries_left;

    //set aasDone
    if (aastype == MSG_SUCCESS) {
        aasDone = true;
    }

   //not commissioning yet. 
    msg_output = msg;
    if (is_err) {
        msg_output += ": Please try again.";

        //mid commission attempt.
        if (is_commission) {
            atmpt_cntr++;
            retries_left = NUM_ATTEMPTS - atmpt_cntr;
            msg_output = msg + ": Retries left: " + std::to_string(retries_left);

            //commission attempts exhausted.
            if (retries_left <= 0) {
                atmpt_cntr = 0;
                is_commission = false;
                pw = "";
                msg_output += ". Attempts exhausted. Please reattempt username.";
            }
        }
    }

    output["code"] = 200;
    output["result"]["aasDone"] = aasDone;
    output["result"]["aasID"] = aasid;
    output["result"]["aasMsg"] = msg_output;
    output["result"]["aasType"] = aastype;
    output["status"] = "success";

    return HEADER + writer.write(output) + "\n";
}


/******** recieve input function definitions *********************************/
std::string get_request_content(const FCGX_Request &request) {
    char * content_length_str = FCGX_GetParam("CONTENT_LENGTH", request.envp);
    unsigned long content_length = STDIN_MAX;

    if (content_length_str) {
        content_length = strtol(content_length_str, &content_length_str, 10);
        if (*content_length_str) {
            std::cerr << "Can't Parse 'CONTENT_LENGTH='"
                 << FCGX_GetParam("CONTENT_LENGTH", request.envp)
                 << "'. Consuming stdin up to " << STDIN_MAX << std::endl;
        }

        if (content_length > STDIN_MAX) {
            content_length = STDIN_MAX;
        }
    } else {
        // Do not read from stdin if CONTENT_LENGTH is missing
        content_length = 0;
    }

    char * content_buffer = new char[content_length];
    std::cin.read(content_buffer, content_length);
    content_length = std::cin.gcount();

    // Chew up any remaining stdin - this shouldn't be necessary
    // but is because mod_fastcgi doesn't handle it correctly.

    // ignore() doesn't set the eof bit in some versions of glibc++
    // so use gcount() instead of eof()...
    do std::cin.ignore(1024); while (std::cin.gcount() == 1024);

    std::string content(content_buffer, content_length);
    delete [] content_buffer;
    return content;
}

std::string gen_aasid() {
    std::string output;
    std::string table = "ABCDEFGHIJKLMNOPQRSTUVWZYZ1234567890";
    
srand(time(0));
    for (int i=0; i<AASID_LENGTH; i++) {
        output += table[rand() % (table.length()-1)];
    }
    return output;
}

bool properInput(Json::Value jobj) {
    bool aasid = false;
    bool aasanswr = false;
    bool usrname = false;
    std::vector<std::string> memberNames;
    std::string m_username = "username";
    std::string m_aasid = "aasID";
    std::string m_aasAnswr = "aasAnswer";

    memberNames = jobj.getMemberNames();

    for (int i=0; i < memberNames.size(); i++) {
        if (memberNames[i] == m_username) {
            usrname = true;
        } else if (memberNames[i] == m_aasid) {
            aasid = true;
        } else if (memberNames[i] == m_aasAnswr) {
            aasanswr = true;
        }
    }
    if (aasid && aasanswr && usrname) {
        return true;
    }
    return false;
}
/* TODO: expand list of legal and illegal names as needed */
bool legalName(std::string name) {
    /*
    std::string cmd = "/usr/bin/id -u \"";
    char *temp = NULL;
    std::string popen_out;
    */
    if (name != "root") {
        syslog (LOG_ALERT, "Received a legal name request");
        //the name is available
        return true;
    }
    syslog (LOG_ALERT, "Receives an illegal name request");
    return false;
}

/*** generate command functions ***/
std::string passwd_cmd_gen(std::string pw) {
    std::string cmd = "/usr/bin/passwd \"";
    cmd +=pw;
    cmd +="\"";
    return cmd;
}

std::string useradd_cmd_gen(std::string usr) {
    std::string cmd = "/usr/sbin/useradd -U -m -G sudo,dialout,disk -s /bin/bash -- \"";
    cmd +=usr;
    cmd +="\"";
    return cmd;
}