/*
    SuperCollider real time audio synthesis system
    Copyright (c) 2002 James McCartney. All rights reserved.
    http://www.audiosynth.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/
/*

An object archiving system for SuperCollider.

*/


#pragma once

#include "PyrObject.h"
#include "SC_AllocPool.h"
#include "InitAlloc.h"

#include "PyrKernel.h"
#include "PyrPrimitive.h"
#include "VMGlobals.h"
#include "GC.h"

const int32 kDeepCopierObjectArrayInitialCapacity = 32;

class PyrDeepCopier {
public:
    PyrDeepCopier(VMGlobals* inG):
        g(inG),
        objectArray(initialObjectArray),
        numObjects(0),
        objectArrayCapacity(kDeepCopierObjectArrayInitialCapacity) {}

    ~PyrDeepCopier() {
        if (objectArrayCapacity > kDeepCopierObjectArrayInitialCapacity) {
            g->allocPool->Free(objectArray);
        }
    }

    std::int64_t doDeepCopy(PyrSlot* objectSlot) {
        std::int64_t err = errNone;

        try {
            if (IsObj(objectSlot)) {
                constructObjectArray(slotRawObject(objectSlot));
                for (int i = 0; i < numObjects; ++i) {
                    fixSlots(objectArray[i]);
                }
                fixObjSlot(objectSlot);
                for (int i = 0; i < numObjects; ++i) {
                    objectArray[i]->ClearMark();
                }
            }
        } catch (std::exception& ex) {
            error(ex.what());
            err = errFailed;
        }
        return err;
    }

private:
    void recurse(PyrObject* obj, size_t n) {
        // post("->recurse %s %08X\n", obj->classptr->name.us->name, obj);
        PyrSlot* slot = obj->slots;
        for (size_t i = 0; i < n; ++i, ++slot) {
            if (IsObj(slot))
                constructObjectArray(slotRawObject(slot));
        }
        // post("<-recurse %s %08X\n", obj->classptr->name.us->name, obj);
    }

    void growObjectArray() {
        int32 newObjectArrayCapacity = objectArrayCapacity << 1;

        int32 newSize = newObjectArrayCapacity * sizeof(PyrObject*);
        PyrObject** newArray = (PyrObject**)g->allocPool->Alloc(newSize);
        MEMFAIL(newArray);
        memcpy(newArray, objectArray, numObjects * sizeof(PyrObject*));
        if (objectArrayCapacity > kDeepCopierObjectArrayInitialCapacity) {
            g->allocPool->Free(objectArray);
        }
        objectArrayCapacity = newObjectArrayCapacity;
        objectArray = newArray;
    }

    void putSelf(PyrObject* obj) {
        obj->SetMark();
        obj->scratch1 = numObjects;

        // expand array if needed
        if (numObjects >= objectArrayCapacity)
            growObjectArray();

        // post("putSelf %d %08X\n", numObjects, obj);
        // add to array
        objectArray[numObjects++] = obj;
    }

    void putCopy(PyrObject* obj) {
        obj->SetMark();
        obj->scratch1 = numObjects;

        // expand array if needed
        if (numObjects + 2 >= objectArrayCapacity)
            growObjectArray();

        // add a shallow copy to object array
        PyrObject* copy = copyObject(g->gc, obj, false);
        copy->ClearMark();

        // post("putCopy %d %08X\n", numObjects, copy);

        // add to array
        objectArray[numObjects++] = copy;
        objectArray[numObjects++] = obj;
    }

    void constructObjectArray(PyrObject* obj) {
        // post("->constructObjectArray %s %08X\n", obj->classptr->name.us->name, obj);
        if (!obj->IsMarked()) {
            if (obj->obj_flags & (obj_immutable) && obj->obj_flags & (obj_permanent)) {
                putSelf(obj);
            } else if (isKindOf(obj, class_class)) {
                putSelf(obj);
            } else if (isKindOf(obj, class_process)) {
                putSelf(obj);
            } else if (isKindOf(obj, s_interpreter->u.classobj)) {
                putSelf(obj);
            } else if (isKindOf(obj, class_rawarray)) {
                putCopy(obj);
            } else if (isKindOf(obj, class_array)) {
                putCopy(obj);
                recurse(obj, obj->size);
            } else if (isKindOf(obj, class_func)) {
                putSelf(obj);
            } else if (isKindOf(obj, class_method)) {
                putSelf(obj);
            } else if (isKindOf(obj, class_thread)) {
                putSelf(obj);
            } else if (isKindOf(obj, class_server_shm_interface)) {
                putSelf(obj);
            } else {
                putCopy(obj);
                recurse(obj, obj->size);
            }
        }
        // post("<-constructObjectArray %s %08X\n", obj->classptr->name.us->name, obj);
    }

    void fixObjSlot(PyrSlot* slot) {
        // post("fixObjSlot %s %08X %d %08X\n", slotRawObject(slot)->classptr->name.us->name, slotRawObject(slot),
        // slot->uo->scratch1, objectArray[slot->uo->scratch1]);
        SetRaw(slot, objectArray[slotRawObject(slot)->scratch1]);
    }

    void fixSlots(PyrObject* obj) {
        // post("fixSlots %s %08X %d\n", obj->classptr->name.us->name, obj, obj->IsMarked());
        if (!obj->IsMarked() && obj->obj_format <= obj_slot) { // it is a copy
            PyrSlot* slot = obj->slots;
            for (int i = 0; i < obj->size; ++i, ++slot) {
                if (IsObj(slot))
                    fixObjSlot(slot);
            }
        }
    }

    VMGlobals* g;

    PyrObject** objectArray;
    int32 numObjects;
    int32 objectArrayCapacity;

    PyrObject* initialObjectArray[kDeepCopierObjectArrayInitialCapacity];
};
