// Variation on task_firstpr_object. Test passing objects (and not just the pointers) as firstprivates

#include <iostream>
#include <omp.h>

#if !defined(NUM_THREADS)
#define NUM_THREADS 4
#endif

#if !defined(COUNT)
// 2: throw in the firstprivate copy constructor
// 1: throw too early, not as interesting
// 100: will not throw at all (not called that many times)
#define COUNT 2
#endif

#define KEEP

class MyObject {

public:
    int count;
    int firstprivate;
    MyObject* left;
    MyObject* right;

    explicit MyObject(int i) {
        count = i;
        firstprivate = 0;
        left = right = nullptr;
    }

    MyObject(const MyObject& mo) {
        count = mo.count;
        firstprivate = mo.firstprivate + 1;
        left = mo.left;
        right = mo.right;
#if defined(NOISY)
        std::cout << "MyObject copy " << count << std::endl;
#endif
        if ((count % 3 == 0) && (firstprivate == COUNT)) {
#if defined(NOISY)
            std::cout << "Let it throw " << count << std::endl;
#endif
            throw std::exception("let it throw");
        }
    }

    MyObject& operator= (const MyObject& mo) {
        count = mo.count;
        firstprivate = 0;
        left = mo.left;
        right = mo.right;
#if defined(NOISY)
        std::cout << "MyObject copyAssign" << std::endl;
#endif
        return *this;
    }

    MyObject& operator++ () {
        ++count;
#if defined(NOISY)
        std::cout << "++MyObject" << std::endl;
#endif
        return *this;
    }

    ~MyObject() {
#if defined(NOISY)
        std::cout << "~MyObject " << count << std::endl;
#endif
    }

    friend std::ostream& operator<<(std::ostream& os, const MyObject& obj) {
        os << " (";
        if (obj.left) {
            os << *(obj.left) << " <--";
        }
        os << obj.count;
        if (obj.right) {
            os << "--> " << *(obj.right);
        }
        os << ") ";

        return os;
    }
    };


void testInitHelper(MyObject** tree, int i)
{
    *tree = new MyObject(i);
    if (i < 7) {
        testInitHelper(&((*tree)->left), i + 2);
        testInitHelper(&((*tree)->right), i + 4);
    }
}

struct PhakeList {
	unsigned x;
	PhakeList* next;
};
	

// spend time here, a lot
void __declspec(noinline) doBunch(unsigned x, int i)
{
	
	PhakeList* p = nullptr;
	PhakeList** q = &p;
	
	for (unsigned y = 0; y < x; ++ y) {
		*q = new PhakeList;
		(*q)->x = y;
		(*q)->next = nullptr;
		q = &((*q)->next);
	}	

	PhakeList* next = p;
	for (unsigned y = 0; y < x; ++ y) {
		p = next;
		next = p->next;
		delete p;
	}		
#if defined(NOISY)
		printf("Was busy. i == %d\n", i);
#endif
}	

void main_test(MyObject* pTree, int i) {

    if (!pTree) return;
    if (i > 1) {
#if defined(NOISY)
		printf("New task. i == %d\n", i);
#endif
		doBunch(1000, i);
	}


#pragma omp parallel default(none) shared(i, pTree) num_threads(NUM_THREADS) 
#pragma omp single
    {
        while (pTree) {
            try {
                MyObject tree(*pTree);

#pragma omp task firstprivate(i, tree)
                {
                    tree.count *= 2; // this should not affect the tree
                    main_test(tree.right, i + 4);
                }
                if (tree.count != i) {
                    printf("Fail. Expect %d, got %d\n", i, tree.count);
                }

                pTree->count *= 3; // this should affect the tree
                pTree = tree.left;
            }
            catch (std::exception) {
#if defined(NOISY)
                printf("Catching\n");
#endif
                pTree = pTree->left;
            }
            i += 2;
        }
    }
}

int main()
{
    MyObject* tree;
    testInitHelper(&tree, 1);

#if defined(NOISY)
    std::cout << "Initial tree: " << std::endl;
    std::cout << *tree << std::endl;
#endif

	main_test(tree, 1);

    std::cout << "processed tree: " << std::endl;
    std::cout << *tree << std::endl;
}
