/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef PTHREADMOCKS_H_
#define PTHREADMOCKS_H_

#include <pthread.h>
#include <string.h>
#include <errno.h>

#include "src/zk_adaptor.h"

#include "Util.h"
#include "MocksBase.h"
#include "LibCSymTable.h"
#include "ThreadingUtil.h"

// an ABC for pthreads
class MockPthreadsBase: public Mock
{
public:
    MockPthreadsBase(){mock_=this;}
    virtual ~MockPthreadsBase(){mock_=0;}
    
    virtual int pthread_create(pthread_t * t, const pthread_attr_t *a,
            void *(*f)(void *), void *d) =0;
    virtual int pthread_join(pthread_t t, void ** r) =0;
    virtual int pthread_detach(pthread_t t) =0;
    virtual int pthread_cond_broadcast(pthread_cond_t *c) =0;
    virtual int pthread_cond_destroy(pthread_cond_t *c) =0;
    virtual int pthread_cond_init(pthread_cond_t *c, const pthread_condattr_t *a) =0;
    virtual int pthread_cond_signal(pthread_cond_t *c) =0;
    virtual int pthread_cond_timedwait(pthread_cond_t *c,
            pthread_mutex_t *m, const struct timespec *t) =0;
    virtual int pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m) =0;
    virtual int pthread_mutex_destroy(pthread_mutex_t *m) =0;
    virtual int pthread_mutex_init(pthread_mutex_t *m, const pthread_mutexattr_t *a) =0;
    virtual int pthread_mutex_lock(pthread_mutex_t *m) =0;
    virtual int pthread_mutex_trylock(pthread_mutex_t *m) =0;
    virtual int pthread_mutex_unlock(pthread_mutex_t *m) =0;
    
    static MockPthreadsBase* mock_;
};

// all pthread functions simply return an error code
// and increment their invocation counter. No actual threads are spawned.
class MockPthreadsNull: public MockPthreadsBase
{
public:
    MockPthreadsNull():
    pthread_createReturns(0),pthread_createCounter(0),
    pthread_joinReturns(0),pthread_joinCounter(0),pthread_joinResultReturn(0),
    pthread_detachReturns(0),pthread_detachCounter(0),
    pthread_cond_broadcastReturns(0),pthread_cond_broadcastCounter(0),
    pthread_cond_destroyReturns(0),pthread_cond_destroyCounter(0),
    pthread_cond_initReturns(0),pthread_cond_initCounter(0),
    pthread_cond_signalReturns(0),pthread_cond_signalCounter(0),
    pthread_cond_timedwaitReturns(0),pthread_cond_timedwaitCounter(0),
    pthread_cond_waitReturns(0),pthread_cond_waitCounter(0),
    pthread_mutex_destroyReturns(0),pthread_mutex_destroyCounter(0),
    pthread_mutex_initReturns(0),pthread_mutex_initCounter(0),
    pthread_mutex_lockReturns(0),pthread_mutex_lockCounter(0),
    pthread_mutex_trylockReturns(0),pthread_mutex_trylockCounter(0),
    pthread_mutex_unlockReturns(0),pthread_mutex_unlockCounter(0)
    {
        memset(threads,0,sizeof(threads));
    }
    
    short threads[512];
    
    int pthread_createReturns;
    int pthread_createCounter;
    virtual int pthread_create(pthread_t * t, const pthread_attr_t *a,
            void *(*f)(void *), void *d){
        char* p=(char*)&threads[pthread_createCounter++];
        p[0]='i'; // mark as created
        *t=(pthread_t)p;
        return pthread_createReturns; 
    }
    int pthread_joinReturns;
    int pthread_joinCounter;
    void* pthread_joinResultReturn;
    virtual int pthread_join(pthread_t t, void ** r){
        pthread_joinCounter++;
        if(r!=0)
            *r=pthread_joinResultReturn;
        char* p=(char*)t;
        p[0]='x';p[1]+=1;
        return pthread_joinReturns; 
    }
    int pthread_detachReturns;
    int pthread_detachCounter;        
    virtual int pthread_detach(pthread_t t){
        pthread_detachCounter++;
        char* p=(char*)t;
        p[0]='x';p[1]+=1;        
        return pthread_detachReturns;
    }

    template<class T>
    static bool isInitialized(const T& t){
        return ((char*)t)[0]=='i';
    }
    template<class T>
    static bool isDestroyed(const T& t){
        return ((char*)t)[0]=='x';
    }
    template<class T>
    static int getDestroyCounter(const T& t){
        return ((char*)t)[1];
    }
    template<class T>
    static int getInvalidAccessCounter(const T& t){
        return ((char*)t)[2];
    }
    int pthread_cond_broadcastReturns;
    int pthread_cond_broadcastCounter;
    virtual int pthread_cond_broadcast(pthread_cond_t *c){
        pthread_cond_broadcastCounter++;
        if(isDestroyed(c))((char*)c)[2]++;
        return pthread_cond_broadcastReturns; 
    }
    int pthread_cond_destroyReturns;
    int pthread_cond_destroyCounter;
    virtual int pthread_cond_destroy(pthread_cond_t *c){
        pthread_cond_destroyCounter++;
        char* p=(char*)c;
        p[0]='x';p[1]+=1;
        return pthread_cond_destroyReturns; 
    }
    int pthread_cond_initReturns;
    int pthread_cond_initCounter;
    virtual int pthread_cond_init(pthread_cond_t *c, const pthread_condattr_t *a){
        pthread_cond_initCounter++;
        char* p=(char*)c;
        p[0]='i'; // mark as created
        p[1]=0;   // destruction counter
        p[2]=0;   // access after destruction counter
        return pthread_cond_initReturns; 
    }
    int pthread_cond_signalReturns;
    int pthread_cond_signalCounter;
    virtual int pthread_cond_signal(pthread_cond_t *c){
        pthread_cond_signalCounter++;
        if(isDestroyed(c))((char*)c)[2]++;
        return pthread_cond_signalReturns; 
    }
    int pthread_cond_timedwaitReturns;
    int pthread_cond_timedwaitCounter;
    virtual int pthread_cond_timedwait(pthread_cond_t *c,
            pthread_mutex_t *m, const struct timespec *t){
        pthread_cond_timedwaitCounter++;
        if(isDestroyed(c))((char*)c)[2]++;
        return pthread_cond_timedwaitReturns; 
    }
    int pthread_cond_waitReturns;
    int pthread_cond_waitCounter;
    virtual int pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m){
        pthread_cond_waitCounter++;
        if(isDestroyed(c))((char*)c)[2]++;
        return pthread_cond_waitReturns; 
    }
    int pthread_mutex_destroyReturns;
    int pthread_mutex_destroyCounter;
    virtual int pthread_mutex_destroy(pthread_mutex_t *m){
        pthread_mutex_destroyCounter++;
        char* p=(char*)m;
        p[0]='x';p[1]+=1;
        return pthread_mutex_destroyReturns; 
    }
    int pthread_mutex_initReturns;
    int pthread_mutex_initCounter;
    virtual int pthread_mutex_init(pthread_mutex_t *m, const pthread_mutexattr_t *a){
        pthread_mutex_initCounter++;
        char* p=(char*)m;
        p[0]='i'; // mark as created
        p[1]=0;   // destruction counter
        p[2]=0;   // access after destruction counter
        return pthread_mutex_initReturns; 
    }
    int pthread_mutex_lockReturns;
    int pthread_mutex_lockCounter;
    virtual int pthread_mutex_lock(pthread_mutex_t *m){
        pthread_mutex_lockCounter++;
        if(isDestroyed(m))((char*)m)[2]++;
        return pthread_mutex_lockReturns; 
    }
    int pthread_mutex_trylockReturns;
    int pthread_mutex_trylockCounter;
    virtual int pthread_mutex_trylock(pthread_mutex_t *m){
        pthread_mutex_trylockCounter++;
        if(isDestroyed(m))((char*)m)[2]++;
        return pthread_mutex_trylockReturns; 
    }
    int pthread_mutex_unlockReturns;
    int pthread_mutex_unlockCounter;
    virtual int pthread_mutex_unlock(pthread_mutex_t *m){
        pthread_mutex_unlockCounter++;
        if(isDestroyed(m))((char*)m)[2]++;
        return pthread_mutex_unlockReturns; 
    }
};

// simulates the way zookeeper threads make use of api_prolog/epilog and
// 
class MockPthreadZKNull: public MockPthreadsNull
{
    typedef std::map<pthread_t,zhandle_t*> Map;
    Map map_;
public:
    virtual int pthread_create(pthread_t * t, const pthread_attr_t *a,
            void *(*f)(void *), void *d){
        int ret=MockPthreadsNull::pthread_create(t,a,f,d);
        zhandle_t* zh=(zhandle_t*)d;
        adaptor_threads* ad=(adaptor_threads*)zh->adaptor_priv;
        api_prolog(zh);
        ad->threadsToWait--;
        putValue(map_,*t,zh);
        return ret;
    }
    virtual int pthread_join(pthread_t t, void ** r){
        zhandle_t* zh=0;
        if(getValue(map_,t,zh))
            api_epilog(zh,0);
        return MockPthreadsNull::pthread_join(t,r);
    }
};

struct ThreadInfo{
    typedef enum {RUNNING,TERMINATED} ThreadState;
    
    ThreadInfo():
        destructionCounter_(0),invalidAccessCounter_(0),state_(RUNNING)
    {
    }
    
    ThreadInfo& incDestroyed() {
        destructionCounter_++;
        return *this;
    }
    ThreadInfo& incInvalidAccess(){
        invalidAccessCounter_++;
        return *this;
    }
    ThreadInfo& setTerminated(){
        state_=TERMINATED;
        return *this;
    }
    int destructionCounter_;
    int invalidAccessCounter_;
    ThreadState state_;
};

class CheckedPthread: public MockPthreadsBase
{
    // first => destruction counter
    // second => invalid access counter
    //typedef std::pair<int,int> Entry;
    typedef ThreadInfo Entry;
    typedef std::map<pthread_t,Entry> ThreadMap;
    static ThreadMap tmap_;
    static ThreadMap& getMap(const TypeOp<pthread_t>::BareT&){return tmap_;}
    typedef std::map<pthread_mutex_t*,Entry> MutexMap;
    static MutexMap mmap_;
    static MutexMap& getMap(const TypeOp<pthread_mutex_t>::BareT&){return mmap_;}
    typedef std::map<pthread_cond_t*,Entry> CVMap;
    static CVMap cvmap_;
    static CVMap& getMap(const TypeOp<pthread_cond_t>::BareT&){return cvmap_;}
    
    static Mutex mx;
    
    template<class T>
    static void markDestroyed(T& t){
        typedef typename TypeOp<T>::BareT Type;
        Entry e;
        synchronized(mx);
        if(getValue(getMap(Type()),t,e)){
            putValue(getMap(Type()),t,Entry(e).incDestroyed());
        }else{
            putValue(getMap(Type()),t,Entry().incDestroyed());
        }
    }
    template<class T>
    static void markCreated(T& t){
        typedef typename TypeOp<T>::BareT Type;
        Entry e;
        synchronized(mx);
        if(!getValue(getMap(Type()),t,e))
            putValue(getMap(Type()),t,Entry());
    }
    template<class T>
    static void checkAccessed(T& t){
        typedef typename TypeOp<T>::BareT Type;
        Entry e;
        synchronized(mx);
        if(getValue(getMap(Type()),t,e) && e.destructionCounter_>0)
            putValue(getMap(Type()),t,Entry(e).incInvalidAccess());
    }
    static void setTerminated(pthread_t t){
        Entry e;
        synchronized(mx);
        if(getValue(tmap_,t,e))
            putValue(tmap_,t,Entry(e).setTerminated());
    }
public:
    bool verbose;
    CheckedPthread():verbose(false){
        tmap_.clear();
        mmap_.clear();
        cvmap_.clear();
        mx.release();
    }
    template <class T>
    static bool isInitialized(const T& t){
        typedef typename TypeOp<T>::BareT Type;
        Entry e;
        synchronized(mx);
        return getValue(getMap(Type()),t,e) && e.destructionCounter_==0;
    }
    template <class T>
    static bool isDestroyed(const T& t){
        typedef typename TypeOp<T>::BareT Type;
        Entry e;
        synchronized(mx);
        return getValue(getMap(Type()),t,e) && e.destructionCounter_>0;
    }
    static bool isTerminated(pthread_t t){
        Entry e;
        synchronized(mx);
        return getValue(tmap_,t,e) && e.state_==ThreadInfo::TERMINATED;        
    }
    template <class T>
    static int getDestroyCounter(const T& t){
        typedef typename TypeOp<T>::BareT Type;
        Entry e;
        synchronized(mx);
        return getValue(getMap(Type()),t,e)?e.destructionCounter_:-1;
    }
    template<class T>
    static int getInvalidAccessCounter(const T& t){
        typedef typename TypeOp<T>::BareT Type;
        Entry e;
        synchronized(mx);
        return getValue(getMap(Type()),t,e)?e.invalidAccessCounter_:-1;
    }

    struct ThreadContext{
        typedef void *(*ThreadFunc)(void *);
        
        ThreadContext(ThreadFunc func,void* param):func_(func),param_(param){}
        ThreadFunc func_;
        void* param_;        
    };
    static void* threadFuncWrapper(void* v){
        ThreadContext* ctx=(ThreadContext*)v;
        pthread_t t=pthread_self();
        markCreated(t);
        void* res=ctx->func_(ctx->param_);
        setTerminated(pthread_self());
        delete ctx;
        return res;
    }
    virtual int pthread_create(pthread_t * t, const pthread_attr_t *a,
            void *(*f)(void *), void *d)
    {
        int ret=LIBC_SYMBOLS.pthread_create(t,a,threadFuncWrapper,
                new ThreadContext(f,d));
        if(verbose)
            TEST_TRACE("thread created %p",*t);
        return ret;
    }
    virtual int pthread_join(pthread_t t, void ** r){
        if(verbose) TEST_TRACE("thread joined %p",t);
        int ret=LIBC_SYMBOLS.pthread_join(t,r);
        if(ret==0)
            markDestroyed(t);
        return ret;
    }
    virtual int pthread_detach(pthread_t t){
        if(verbose) TEST_TRACE("thread detached %p",t);
        int ret=LIBC_SYMBOLS.pthread_detach(t);
        if(ret==0)
            markDestroyed(t);
        return ret;
    }
    virtual int pthread_cond_broadcast(pthread_cond_t *c){
        checkAccessed(c);
        return LIBC_SYMBOLS.pthread_cond_broadcast(c);
    }
    virtual int pthread_cond_destroy(pthread_cond_t *c){
        markDestroyed(c);
        return LIBC_SYMBOLS.pthread_cond_destroy(c);
    }
    virtual int pthread_cond_init(pthread_cond_t *c, const pthread_condattr_t *a){
        markCreated(c);
        return LIBC_SYMBOLS.pthread_cond_init(c,a);
    }
    virtual int pthread_cond_signal(pthread_cond_t *c){
        checkAccessed(c);
        return LIBC_SYMBOLS.pthread_cond_signal(c);
    }
    virtual int pthread_cond_timedwait(pthread_cond_t *c,
            pthread_mutex_t *m, const struct timespec *t){
        checkAccessed(c);
        return LIBC_SYMBOLS.pthread_cond_timedwait(c,m,t);
    }
    virtual int pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m){
        checkAccessed(c);
        return LIBC_SYMBOLS.pthread_cond_wait(c,m);
    }
    virtual int pthread_mutex_destroy(pthread_mutex_t *m){
        markDestroyed(m);
        return LIBC_SYMBOLS.pthread_mutex_destroy(m);
    }
    virtual int pthread_mutex_init(pthread_mutex_t *m, const pthread_mutexattr_t *a){
        markCreated(m);
        return LIBC_SYMBOLS.pthread_mutex_init(m,a);
    }
    virtual int pthread_mutex_lock(pthread_mutex_t *m){
        checkAccessed(m);
        return LIBC_SYMBOLS.pthread_mutex_lock(m);
    }
    virtual int pthread_mutex_trylock(pthread_mutex_t *m){
        checkAccessed(m);
        return LIBC_SYMBOLS.pthread_mutex_trylock(m);
    }
    virtual int pthread_mutex_unlock(pthread_mutex_t *m){
        checkAccessed(m);
        return LIBC_SYMBOLS.pthread_mutex_unlock(m);
    }    
};

#endif /*PTHREADMOCKS_H_*/

