/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the name of The Linux Foundation, 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 "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER 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 <LocThread.h>
#include <string.h>
#include <pthread.h>
#include <platform_lib_macros.h>

class LocThreadDelegate {
    LocRunnable* mRunnable;
    bool mJoinable;
    pthread_t mThandle;
    pthread_mutex_t mMutex;
    int mRefCount;
    ~LocThreadDelegate();
    LocThreadDelegate(LocThread::tCreate creator, const char* threadName,
                      LocRunnable* runnable, bool joinable);
    void destroy();
public:
    static LocThreadDelegate* create(LocThread::tCreate creator,
            const char* threadName, LocRunnable* runnable, bool joinable);
    void stop();
    // bye() is for the parent thread to go away. if joinable,
    // parent must stop the spawned thread, join, and then
    // destroy(); if detached, the parent can go straight
    // ahead to destroy()
    inline void bye() { mJoinable ? stop() : destroy(); }
    inline bool isRunning() { return (NULL != mRunnable); }
    static void* threadMain(void* arg);
};

// it is important to note that internal members must be
// initialized to values as if pthread_create succeeds.
// This is to avoid the race condition between the threads,
// once the thread is created, some of these values will
// be check in the spawned thread, and must set correctly
// then and there.
// However, upon pthread_create failure, the data members
// must be set to  indicate failure, e.g. mRunnable, and
// threashold approprietly for destroy(), e.g. mRefCount.
LocThreadDelegate::LocThreadDelegate(LocThread::tCreate creator,
        const char* threadName, LocRunnable* runnable, bool joinable) :
    mRunnable(runnable), mJoinable(joinable), mThandle(NULL),
    mMutex(PTHREAD_MUTEX_INITIALIZER), mRefCount(2) {

    // set up thread name, if nothing is passed in
    if (!threadName) {
        threadName = "LocThread";
    }

    // create the thread here, then if successful
    // and a name is given, we set the thread name
    if (creator) {
        mThandle = creator(threadName, threadMain, this);
    } else if (pthread_create(&mThandle, NULL, threadMain, this)) {
        // pthread_create() failed
        mThandle = NULL;
    }

    if (mThandle) {
        // set thread name
        char lname[16];
        int len = (sizeof(lname)>sizeof(threadName)) ?
          (sizeof(threadName) -1):(sizeof(lname) - 1);
        memcpy(lname, threadName, len);
        lname[len] = 0;
        // set the thread name here
        pthread_setname_np(mThandle, lname);

        // detach, if not joinable
        if (!joinable) {
            pthread_detach(mThandle);
        }
    } else {
        // must set these values upon failure
        mRunnable = NULL;
        mJoinable = false;
        mRefCount = 1;
    }
}

inline
LocThreadDelegate::~LocThreadDelegate() {
    // at this point nothing should need done any more
}

// factory method so that we could return NULL upon failure
LocThreadDelegate* LocThreadDelegate::create(LocThread::tCreate creator,
        const char* threadName, LocRunnable* runnable, bool joinable) {
    LocThreadDelegate* thread = NULL;
    if (runnable) {
        thread = new LocThreadDelegate(creator, threadName, runnable, joinable);
        if (thread && !thread->isRunning()) {
            thread->destroy();
            thread = NULL;
        }
    }

    return thread;
}

// The order is importang
// NULLing mRunnalbe stops the while loop in threadMain()
// join() if mJoinble must come before destroy() call, as
// the obj must remain alive at this time so that mThandle
// remains valud.
void LocThreadDelegate::stop() {
    // mRunnable and mJoinable are reset on different triggers.
    // mRunnable may get nulled on the spawned thread's way out;
    //           or here.
    // mJouinable (if ever been true) gets falsed when client
    //            thread triggers stop, with either a stop()
    //            call or the client releases thread obj handle.
    if (mRunnable) {
        mRunnable = NULL;
    }
    if (mJoinable) {
        mJoinable = false;
        pthread_join(mThandle, NULL);
    }
    // call destroy() to possibly delete the obj
    destroy();
}

// method for clients to call to release the obj
// when it is a detached thread, the client thread
// and the spawned thread can both try to destroy()
// asynchronously. And we delete this obj when
// mRefCount becomes 0.
void LocThreadDelegate::destroy() {
    // else case shouldn't happen, unless there is a
    // leaking obj. But only our code here has such
    // obj, so if we test our code well, else case
    // will never happen
    if (mRefCount > 0) {
        // we need a flag on the stack
        bool callDelete = false;

        // critical section between threads
        pthread_mutex_lock(&mMutex);
        // last destroy() call
        callDelete = (1 == mRefCount--);
        pthread_mutex_unlock(&mMutex);

        // upon last destroy() call we delete this obj
        if (callDelete) {
            delete this;
        }
    }
}

void* LocThreadDelegate::threadMain(void* arg) {
    LocThreadDelegate* locThread = (LocThreadDelegate*)(arg);

    if (locThread) {
        LocRunnable* runnable = locThread->mRunnable;

        if (runnable) {
            if (locThread->isRunning()) {
                runnable->prerun();
            }

            while (locThread->isRunning() && runnable->run());

            if (locThread->isRunning()) {
                runnable->postrun();
            }

            // at this time, locThread->mRunnable may or may not be NULL
            // NULL it just to be safe and clean, as we want the field
            // in the released memory slot to be NULL.
            locThread->mRunnable = NULL;
            delete runnable;
        }
        locThread->destroy();
    }

    return NULL;
}

LocThread::~LocThread() {
    if (mThread) {
        mThread->bye();
        mThread = NULL;
    }
}

bool LocThread::start(tCreate creator, const char* threadName, LocRunnable* runnable, bool joinable) {
    bool success = false;
    if (!mThread) {
        mThread = LocThreadDelegate::create(creator, threadName, runnable, joinable);
        // true only if thread is created successfully
        success = (NULL != mThread);
    }
    return success;
}

void LocThread::stop() {
    if (mThread) {
        mThread->stop();
        mThread = NULL;
    }
}

#ifdef __LOC_DEBUG__

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

class LocRunnableTest1 : public LocRunnable {
    int mID;
public:
    LocRunnableTest1(int id) : LocRunnable(), mID(id) {}
    virtual bool run() {
        printf("LocRunnableTest1: %d\n", mID++);
        sleep(1);
        return true;
    }
};

// on linux command line:
// compile: g++ -D__LOC_HOST_DEBUG__ -D__LOC_DEBUG__ -g -std=c++0x -I. -I../../../../vendor/qcom/proprietary/gps-internal/unit-tests/fakes_for_host -I../../../../system/core/include -lpthread LocThread.cpp
// test detached thread: valgrind ./a.out 0
// test joinable thread: valgrind ./a.out 1
int main(int argc, char** argv) {
    LocRunnableTest1 test(10);

    LocThread thread;
    thread.start("LocThreadTest", test, atoi(argv[1]));

    sleep(10);

    thread.stop();

    sleep(5);

    return 0;
}

#endif