/*
 * Copyright (c) 2011-2014, Intel Corporation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors
 * may be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "TestPlatform.h"
#include "FullIo.hpp"

#include <iostream>
#include <cstdlib>
#include <semaphore.h>
#include <string.h>
#include <unistd.h>
#include <cerrno>
#include <cassert>

using namespace std;

const int iDefaultPortNumber = 5001;

// Starts test-platform in blocking mode
static bool startBlockingTestPlatform(const char *filePath, int portNumber, string &strError)
{

    // Init semaphore
    sem_t sem;

    sem_init(&sem, false, 0);

    // Create param mgr
    CTestPlatform testPlatform(filePath, portNumber, sem);

    // Start platformmgr
    if (!testPlatform.load(strError)) {

        sem_destroy(&sem);

        return false;
    }

    // Block here
    sem_wait(&sem);

    sem_destroy(&sem);

    return true;
}

static void notifyParent(int parentFd, bool success)
{
    if (not utility::fullWrite(parentFd, &success, sizeof(success))) {
        cerr << "Unable to warn parent process of load "
             << (success ? "success" : "failure") << ": "
             << strerror(errno) << endl;
        assert(false);
    }
}

// Starts test-platform in daemon mode
static bool startDaemonTestPlatform(const char *filePath, int portNumber, string &strError)
{
    // Pipe used for communication between the child and the parent processes
    int pipefd[2];

    if (pipe(pipefd) == -1) {

        strError = "pipe failed";
        return false;
    }

    // Fork the current process:
    // - Child process is used to start test-platform
    // - Parent process is killed in order to turn its child into a daemon
    pid_t pid = fork();

    if (pid < 0) {

        strError = "fork failed!";
        return false;

    } else if (pid == 0) {

        // Child process : starts test-platform and notify the parent if it succeeds.

        // Close read side of the pipe
        close(pipefd[0]);

        // Init semaphore
        sem_t sem;

        sem_init(&sem, false, 0);

        // Create param mgr
        CTestPlatform testPlatform(filePath, portNumber, sem);

        // Message to send to parent process
        bool loadSuccess = testPlatform.load(strError);

        if (!loadSuccess) {

            cerr << strError << endl;

            // Notify parent of failure;
            notifyParent(pipefd[1], false);

        } else {

            // Notify parent of success
            notifyParent(pipefd[1], true);

            // Block here
            sem_wait(&sem);
        }
        sem_destroy(&sem);

        return loadSuccess;

    } else {

        // Parent process : need to kill it once the child notifies the successs/failure to start
        // test-platform (this status is used as exit value of the program).

        // Close write side of the pipe
        close(pipefd[1]);

        // Message received from the child process
        bool msgFromChild = false;

        if (not utility::fullRead(pipefd[0], &msgFromChild, sizeof(msgFromChild))) {
            strError = "Read pipe failed";
            return false;
        }

        // return success/failure in exit status
        return msgFromChild;
    }
}

static void showUsage()
{
    cerr << "test-platform [-dh] <file path> [port number, default "
         << iDefaultPortNumber << "]" << endl;
}

static void showInvalidUsage()
{
    cerr << "Invalid arguments: ";
    showUsage();
}

static void showHelp()
{
    showUsage();
    cerr << "<file path> must be a valid .xml file, oftenly ParameterFrameworkConfiguration.xml" << endl;
    cerr << "Arguments:" << endl
        << "    -d  starts as a deamon" << endl
        << "    -h  display this help and exit" << endl;
}

int main(int argc, char *argv[])
{
    // Option found by call to getopt()
    int opt;

    // Port number to be used by test-platform
    int portNumber;

    // Daemon flag
    bool isDaemon = false;

    // Index of the <file path> argument in the arguments list provided
    int indexFilePath = 1;

    // Handle the -d option
    while ((opt = getopt(argc, argv, "dh")) != -1) {
        switch (opt) {
        case 'd':
            isDaemon = true;
            indexFilePath = 2;
            break;
        case 'h':
            showHelp();
            return 0;
        default:
            showInvalidUsage();
            return -1;
        }
    }

    // Check the number of arguments
    if ((argc < indexFilePath + 1) || (argc > indexFilePath + 2)) {

        showInvalidUsage();
        return -1;
    }

    char *filePath = argv[indexFilePath];
    portNumber = argc > indexFilePath + 1 ? atoi(argv[indexFilePath + 1]) : iDefaultPortNumber;

    // Choose either blocking or daemon test-platform
    bool startError;
    string strError;

    if (isDaemon) {

        startError = startDaemonTestPlatform(filePath, portNumber, strError);
    } else {

        startError = startBlockingTestPlatform(filePath, portNumber, strError);
    }

    if (!startError) {

        cerr << "Test-platform error:" << strError.c_str() << endl;
        return -1;
    }
    return 0;
}