/*========================== begin_copyright_notice ============================

Copyright (C) 2017 Intel Corporation

SPDX-License-Identifier: MIT

============================= end_copyright_notice ===========================*/

#ifndef CM_INTERNAL_EMU_H
#define CM_INTERNAL_EMU_H

#define MAX_MASK_WIDTH 32

#ifdef __GNUC__
#include <typeinfo>   //for "const type_info *t",
using std::type_info;  //otherwise "ISO C++ forbids declaration of 'type_info' with no type_info"
#endif

namespace __CMInternal__ {

    class Dlink {
    protected:
        virtual void vtable_stub() const {}

    public:
        Dlink() {
            _next = _prev = this;
        }
        virtual ~Dlink() {}

        void unlink()
            {
                _prev->_next=_next;
                _next->_prev=_prev;
                _next=_prev=this;
            }

        // insert "this" after "prev_link"
        void insertAfter(Dlink *prev_link) {
            assert(_prev==this);
            assert(_next==this);
            _prev = prev_link;
            _next = prev_link->_next;
            prev_link->_next = this;
            _next->_prev = this;
        }

        void insertBefore(Dlink *next_link) {
            assert(_prev==this);
            assert(_next==this);
            _next = next_link;
            _prev = next_link->_prev;
            next_link->_prev = this;
            _prev->_next = this;
        }

        Dlink *getNext()
            {
                return _next;
            }

        Dlink *getPrev()
            {
                return _prev;
            }

    protected:
        Dlink *_next, *_prev;

    };

    class stackImpl
    {
    public:

        stackImpl()
            {
                depth = 0;
            }

        void* pop() {
            if (isEmpty()) return NULL;
            stackElem* e = workList.next();
            void *elem = e->elem;
            free(e);
            depth --;
            return elem;
        }

        void  push(void *elem) {
            stackElem* se = new stackElem();
            se->elem = elem;
            se->insertAfter(&workList);
            depth ++;
        };

        void* top()
            {
                return (void *)(workList.next()->elem);
            }

        int getDepth()
            {
                return depth;
            }

        bool  isEmpty()
            {
                return workList.next() == &workList;
            }

    private:

        class stackElem : public Dlink
        {
        public:
            void*  elem;

            stackElem() : Dlink() {}

            stackElem* next()
                {
                    return (stackElem*)_next;
                }

            stackElem* prev()
                {
                    return (stackElem*)_prev;
                }
        };

        stackElem workList;
        int depth;

        void  free(stackElem* n) {
            n->unlink();
            delete n;
        }
    };

    class Stack : public stackImpl {
    public:
        Stack() : stackImpl() {}

        void* pop()
            {
                return (void*)stackImpl::pop();
            }

        void  push(void* elem)
            {
                stackImpl::push(elem);
            }

        void* top()
            {
                return (void*)stackImpl::top();
            }

        int getDepth()
            {
                return stackImpl::getDepth();
            }
    };

    class maskItem {
    public:
        maskItem(uint m, const type_info *t, int w)
            {
                assert(w <= MAX_MASK_WIDTH);
                width = w;
                mask = m;
                type = t;
                executed_mask = m;
            }

        uint getMask()
            {
                return mask;
            }

        uint getExecutedMask()
            {
                return executed_mask;
            }

        void setMask(uint m)
            {
                mask = m;
            }

        void setExecutedMask(uint m)
            {
                executed_mask |= m;
            }

        int getWidth()
            {
                return width;
            }

        void setWidth(int w)
            {
                width = w;
            }

        const type_info* getType()
            {
                return type;
            }

    private:
        int width;
        uint mask;
        uint executed_mask;
        const type_info* type;
    };

    class breakMaskItem : public maskItem
    {
    public:
        breakMaskItem(uint m, const type_info *t, int w) : maskItem(m, t, w) {workingDepth = 0;}

        int getWorkingDepth()
            {
                return workingDepth;
            }

        void setWorkingDepth(int depth)
            {
                workingDepth = depth;
            }

    private:
        int workingDepth;

    };

    CM_API Stack* getWorkingStack();
    CM_API void setWorkingStack(Stack *s);
    CM_API Stack* getBreakStack();
    CM_API void setBreakStack(Stack *s);
    CM_API uint getSIMDMarker();
    CM_API void setSIMDMarker(uint marker);

    CM_API uint __cm_internal_simd();
    CM_API uint __cm_internal_simd_then_end();
    CM_API uint __cm_internal_simd_else_begin();
    CM_API uint __cm_internal_simd_if_end();
    CM_API uint __cm_internal_simd_if_join();
    CM_API void __cm_internal_simd_do_while_before();
    CM_API uint __cm_internal_simd_do_while_begin();
    CM_API uint __cm_internal_before_do_while_end();
    CM_API uint __cm_internal_simd_after_do_while_end();
    CM_API uint __cm_internal_simd_break();
    CM_API uint __cm_internal_simd_continue();

    CM_API uint __cm_internal_simd_do_while_end(bool cond);
    CM_API uint __cm_internal_simd_do_while_end(int cond);
    CM_API uint __cm_internal_simd_do_while_end(unsigned int cond);
    CM_API uint __cm_internal_simd_do_while_end(char cond);
    CM_API uint __cm_internal_simd_do_while_end(unsigned char cond);
    CM_API uint __cm_internal_simd_do_while_end(int cond);
    CM_API uint __cm_internal_simd_do_while_end(unsigned int cond);

    template <typename T, uint R, uint C>
    CM_API uint
    __cm_internal_simd(const matrix<T,R,C> &cond)
    {
        return cond.any();
    }

    template <typename T, uint R, uint C>
    CM_API uint
    __cm_internal_simd(const matrix_ref<T,R,C> cond)
    {
        return cond.any();
    }

    template <typename T, uint S>
    CM_API uint
    __cm_internal_simd(const vector<T, S> &cond)
    {
        return cond.any();
    }

    template <typename T, uint S>
    CM_API uint
    __cm_internal_simd(const vector_ref<T, S> cond)
    {
        return cond.any();
    }

    template <typename T>
    CM_API uint
    __cm_internal_simd(T cond)
    {
        return uint(cond);
    }

    // ------------------------------------------------------------------------
    // For SIMD IF/THEN/ELSE/END
    // ------------------------------------------------------------------------
#define simd_if_begin_common(T,W,cond)                                  \
    {                                                                   \
        uint simd_mask = 0;                                           \
                                                                        \
        assert(W <= MAX_MASK_WIDTH);                                    \
        for (int i = 1; i <= W; i++) {                                  \
            T e = cond.get(i - 1);                                      \
            if (e) {                                                    \
                simd_mask |= 1 << (MAX_MASK_WIDTH - i); }               \
        }                                                               \
        if (!getWorkingStack())                                         \
            setWorkingStack(new Stack());                               \
        if (!getWorkingStack()->isEmpty())                              \
            simd_mask &= ((maskItem *)getWorkingStack()->top())->getMask(); \
                                                                        \
        maskItem *mi = new maskItem(simd_mask, &typeid(T), W);          \
        getWorkingStack()->push(mi);                                    \
                                                                        \
        return simd_mask;                                               \
    }

    //Matrix
    template <typename T, uint R, uint C>
    CM_API uint
    __cm_internal_simd_if_begin(const matrix<T,R,C> &cond)
    {
        int width = R * C;
        simd_if_begin_common(T,width,cond);
    }

    template <typename T, uint R, uint C>
    CM_API uint
    __cm_internal_simd_if_begin(const matrix_ref<T,R,C> cond)
    {
        int width = R * C;
        simd_if_begin_common(T,width,cond);
    }

    //Vector
    template <typename T, uint SZ>
    CM_API uint
    __cm_internal_simd_if_begin(const vector<T,SZ> &cond)
    {
        int width = SZ;
        simd_if_begin_common(T,width,cond);
    }

    template <typename T, uint SZ>
    CM_API uint
    __cm_internal_simd_if_begin(const vector_ref<T,SZ> cond)
    {
        int width = SZ;
        simd_if_begin_common(T,width,cond);
    }

    template <typename T>
    CM_API uint
    __cm_internal_simd_if_begin(const T cond)
    {
        int width = MAX_MASK_WIDTH;
        vector<unsigned int, MAX_MASK_WIDTH> v = (unsigned int) cond;
        simd_if_begin_common(unsigned int,MAX_MASK_WIDTH,v);
    }

#define simd_elseif_begin_common(T,W,cond)                              \
    {                                                                   \
        uint simd_mask = 0;                                           \
                                                                        \
        assert(W <= MAX_MASK_WIDTH);                                    \
        for (int i = 1; i <= W; i++) {                                  \
            T e = cond.get(i - 1);                                      \
            if (e) {                                                    \
                simd_mask |= 1 << (MAX_MASK_WIDTH - i);                 \
            }                                                           \
        }                                                               \
        assert(getWorkingStack());                                      \
        assert(!getWorkingStack()->isEmpty());                          \
        simd_mask &= ((maskItem *)getWorkingStack()->top())->getMask(); \
        ((maskItem *)getWorkingStack()->top())->setMask(simd_mask);     \
        ((maskItem *)getWorkingStack()->top())->setExecutedMask(simd_mask);\
        return simd_mask;                                               \
    }

    //Matrix
    template <typename T, uint R, uint C>
    CM_API uint
    __cm_internal_simd_elseif_begin(const matrix<T,R,C> &cond)
    {
        int width = R * C;
        simd_elseif_begin_common(T,width,cond);
    }

    template <typename T, uint R, uint C>
    CM_API uint
    __cm_internal_simd_elseif_begin(const matrix_ref<T,R,C> cond)
    {
        int width = R * C;
        simd_elseif_begin_common(T,width,cond);
    }

    //Vector
    template <typename T, uint SZ>
    CM_API uint
    __cm_internal_simd_elseif_begin(const vector<T,SZ> &cond)
    {
        int width = SZ;
        simd_elseif_begin_common(T,width,cond);
    }

    template <typename T, uint SZ>
    CM_API uint
    __cm_internal_simd_elseif_begin(const vector_ref<T,SZ> cond)
    {
        int width = SZ;
        simd_elseif_begin_common(T,width,cond);
    }

    template <typename T>
    CM_API uint
    __cm_internal_simd_elseif_begin(T cond)
    {
        int width = MAX_MASK_WIDTH;
        vector<unsigned int, MAX_MASK_WIDTH> v = (unsigned int) cond;
        simd_elseif_begin_common(unsigned int,MAX_MASK_WIDTH,v);
    }

#define simd_do_while_end_common(T,W,cond)                              \
    {                                                                   \
        unsigned int simd_mask = 0;                                   \
                                                                        \
        assert(W <= MAX_MASK_WIDTH);                                    \
        for (int i = 1; i <= W; i++) {                                  \
            T e = cond.get(i - 1);                                      \
            if (e)                                                      \
                simd_mask |= 1 << (MAX_MASK_WIDTH - i);                 \
        }                                                               \
        assert(!getBreakStack()->isEmpty());                            \
                                                                        \
        simd_mask &= ((maskItem *)(getBreakStack()->top()))->getMask(); \
        ((maskItem *)(getBreakStack()->top()))->setMask(simd_mask);     \
        ((maskItem *)(getWorkingStack()->top()))->setMask(simd_mask);   \
        return simd_mask;                                               \
    }

    template <typename T, uint R, uint C>
    CM_API uint
    __cm_internal_simd_do_while_end(const matrix<T,R,C> &cond)
    {
        int width = R * C;
        simd_do_while_end_common(T,width,cond);
    }

    template <typename T, uint R, uint C>
    CM_API uint
    __cm_internal_simd_do_while_end(const matrix_ref<T,R,C> cond)
    {
        int width = R * C;
        simd_do_while_end_common(T,width,cond);
    }

    template <typename T, uint SZ>
    CM_API uint
    __cm_internal_simd_do_while_end(const vector<T,SZ> &cond)
    {
        int width = SZ;
        simd_do_while_end_common(T,width,cond);
    }

    template <typename T, uint SZ>
    CM_API uint
    __cm_internal_simd_do_while_end(const vector_ref<T,SZ> cond)
    {
        int width = SZ;
        simd_do_while_end_common(T,width,cond);
    }

};

#endif /* CM_INTERNAL_EMU_H */
