"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DynamicRefFuncGen = void 0;
const generator_1 = require("../generator");
const types_1 = require("../../types");
class DynamicRefFuncGen {
    gen(schema) {
        if (schema.hasLiteRef() && schema.liteRefArrayLength(0) === 0) {
            if (schema.features.includes(types_1.Feature.DYNAMICREFLIBRARY)) {
                return (0, generator_1.ind)(4, `
function addReference(uint256 ourTokenId, uint64 liteRef) public override {
    if (!_checkTokenWriteAuth(ourTokenId)) {
        revert IPatchworkProtocol.NotAuthorized(msg.sender);
    }
    PatchworkDynamicRefs.addReference(liteRef, _dynamicLiterefStorage[ourTokenId]);
}

function addReferenceBatch(uint256 ourTokenId, uint64[] calldata liteRefs) public override {
    if (!_checkTokenWriteAuth(ourTokenId)) {
        revert IPatchworkProtocol.NotAuthorized(msg.sender);
    }
    PatchworkDynamicRefs.addReferenceBatch(liteRefs,  _dynamicLiterefStorage[ourTokenId]);
}

function removeReference(uint256 ourTokenId, uint64 liteRef) public override {
    if (!_checkTokenWriteAuth(ourTokenId)) {
        revert IPatchworkProtocol.NotAuthorized(msg.sender);
    }
    PatchworkDynamicRefs.removeReference(liteRef, _dynamicLiterefStorage[ourTokenId]);
}

function addReference(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override {
    if (targetMetadataId != 0) {
        revert UnsupportedMetadataId();
    }
    addReference(tokenId, liteRef);
}


function removeReference(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override {
    if (targetMetadataId != 0) {
        revert UnsupportedMetadataId();
    }
    removeReference(tokenId, liteRef);
}

function addReferenceBatch(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) public override {
    if (targetMetadataId != 0) {
        revert UnsupportedMetadataId();
    }
    addReferenceBatch(tokenId, liteRefs);
}

function loadReferenceAddressAndTokenId(uint256 ourTokenId, uint256 idx) public view returns (address addr, uint256 tokenId) {
    uint64 ref = PatchworkDynamicRefs.loadRef(idx, _dynamicLiterefStorage[ourTokenId]);
    (addr, tokenId) = getReferenceAddressAndTokenId(ref);
}

function getDynamicReferenceCount(uint256 tokenId) public view override returns (uint256 count) {
    count = PatchworkDynamicRefs.getDynamicReferenceCount(_dynamicLiterefStorage[tokenId]);
}

function loadDynamicReferencePage(uint256 tokenId, uint256 offset, uint256 count) public view override returns (address[] memory addresses, uint256[] memory tokenIds) {
    uint64[] memory refs = PatchworkDynamicRefs.loadRefPage(offset, count, _dynamicLiterefStorage[tokenId]);
    addresses = new address[](refs.length);
    tokenIds = new uint256[](refs.length);
    for (uint256 i = 0; i < refs.length; i++) {
        (address attributeAddress, uint256 attributeTokenId) = getReferenceAddressAndTokenId(refs[i]);
        addresses[i] = attributeAddress;
        tokenIds[i] = attributeTokenId;
    }
}\n\n`);
            }
            else {
                return (0, generator_1.ind)(4, `
function addReference(uint256 ourTokenId, uint64 liteRef) public override {
    if (!_checkTokenWriteAuth(ourTokenId)) {
        revert IPatchworkProtocol.NotAuthorized(msg.sender);
    }
    // to append: find last slot, if it's not full, add, otherwise start a new slot.
    DynamicLiteRefs storage store = _dynamicLiterefStorage[ourTokenId];
    uint256 slotsLen = store.slots.length;
    if (slotsLen == 0) {
        store.slots.push(uint256(liteRef));
        store.idx[liteRef] = 0;
    } else {
        uint256 slot = store.slots[slotsLen-1];
        if (slot >= (1 << 192)) {
            // new slot (pos 1)
            store.slots.push(uint256(liteRef));
            store.idx[liteRef] = slotsLen;
        } else {
            store.idx[liteRef] = slotsLen-1;
            // Reverse search for the next empty subslot
            for (uint256 i = 3; i > 0; i--) {
                if (slot >= (1 << ((i-1) * 64))) {
                    // pos 4 through 2
                    store.slots[slotsLen-1] = slot | uint256(liteRef) << (i*64);
                    break;
                }
            }
        }
    }
}

function addReferenceBatch(uint256 ourTokenId, uint64[] calldata liteRefs) public override {
    if (!_checkTokenWriteAuth(ourTokenId)) {
        revert IPatchworkProtocol.NotAuthorized(msg.sender);
    }
    // do in batches of 4 with 1 remainder pass
    DynamicLiteRefs storage store = _dynamicLiterefStorage[ourTokenId];
    uint256 slotsLen = store.slots.length;
    if (slotsLen > 0) {
        revert AlreadyLoaded();
    }
    uint256 fullBatchCount = liteRefs.length / 4;
    uint256 remainder = liteRefs.length % 4;
    for (uint256 batch = 0; batch < fullBatchCount; batch++) {
        uint256 refIdx = batch * 4;
        uint256 slot = uint256(liteRefs[refIdx]) | (uint256(liteRefs[refIdx+1]) << 64) | (uint256(liteRefs[refIdx+2]) << 128) | (uint256(liteRefs[refIdx+3]) << 192);
        store.slots.push(slot);
        for (uint256 i = 0; i < 4; i++) {
            store.idx[liteRefs[refIdx + i]] = batch;
        }
    }
    uint256 rSlot;
    for (uint256 i = 0; i < remainder; i++) {
        uint256 idx = (fullBatchCount * 4) + i;
        rSlot = rSlot | (uint256(liteRefs[idx]) << (i * 64));
        store.idx[liteRefs[idx]] = fullBatchCount;
    }
    store.slots.push(rSlot);
}

function removeReference(uint256 ourTokenId, uint64 liteRef) public override {
    if (!_checkTokenWriteAuth(ourTokenId)) {
        revert IPatchworkProtocol.NotAuthorized(msg.sender);
    }
    DynamicLiteRefs storage store = _dynamicLiterefStorage[ourTokenId];
    uint256 slotsLen = store.slots.length;
    if (slotsLen == 0) {
        revert NotFound();
    }

    uint256 count = getDynamicReferenceCount(ourTokenId);
    if (count == 1) {
        if (store.slots[0] == liteRef) {
            store.slots.pop();
            delete store.idx[liteRef];
        } else {
            revert NotFound();
        }
    } else {
        // remember and remove the last ref
        uint256 lastIdx = slotsLen-1;
        uint256 slot = store.slots[lastIdx];
        uint64 lastRef;

        for (uint256 i = 3; i > 0; i--) {
            uint256 shift = i * 64;
            if (slot >= (1 << shift)) {
                // pos 4 through 2
                lastRef = uint64(slot >> shift);
                uint256 mask = ~uint256(0) >> (256 - shift);
                store.slots[lastIdx] = slot & mask;
                break;
            }
        }
        if (lastRef == 0) {
            // pos 1
            lastRef = uint64(slot);
            store.slots.pop();
        }

        if (lastRef == liteRef) {
            // it was the last ref. No need to replace anything. It's already cleared so just clear the index
            delete store.idx[liteRef];
        } else {
            // Find the ref and replace it with lastRef then update indexes
            uint256 refSlotIdx = store.idx[liteRef];
            slot = store.slots[refSlotIdx];
            uint256 oldSlot = slot;
            for (uint256 i = 4; i > 0; i--) {
                uint256 shift = (i-1) * 64;
                if (uint64(slot >> shift) == liteRef) {
                    uint256 mask = ~(uint256(0xFFFFFFFFFFFFFFFF) << shift);
                    slot = (slot & mask) | (uint256(lastRef) << shift);
                    break;
                }
            }
            if (oldSlot == slot) {
                revert StorageIntegrityError();
            }
            store.slots[refSlotIdx] = slot;
            store.idx[lastRef] = refSlotIdx;
            delete store.idx[liteRef];
        }
    }
}

function addReference(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override {
    if (targetMetadataId != 0) {
        revert UnsupportedMetadataId();
    }
    addReference(tokenId, liteRef);
}


function removeReference(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override {
    if (targetMetadataId != 0) {
        revert UnsupportedMetadataId();
    }
    removeReference(tokenId, liteRef);
}

function addReferenceBatch(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) public override {
    if (targetMetadataId != 0) {
        revert UnsupportedMetadataId();
    }
    addReferenceBatch(tokenId, liteRefs);
}

function loadReferenceAddressAndTokenId(uint256 ourTokenId, uint256 idx) public view returns (address addr, uint256 tokenId) {
    uint256[] storage slots = _dynamicLiterefStorage[ourTokenId].slots;
    uint slotNumber = idx / 4; // integer division will get the correct slot number
    uint shift = (idx % 4) * 64; // the remainder will give the correct shift
    uint64 ref = uint64(slots[slotNumber] >> shift);
    (addr, tokenId) = getReferenceAddressAndTokenId(ref);
}

function getDynamicReferenceCount(uint256 tokenId) public view override returns (uint256 count) {
    DynamicLiteRefs storage store = _dynamicLiterefStorage[tokenId];
    uint256 slotsLen = store.slots.length;
    if (slotsLen == 0) {
        return 0;
    } else {
        uint256 slot = store.slots[slotsLen-1];
        for (uint256 i = 4; i > 1; i--) {
            uint256 shift = (i-1) * 64;
            if (slot >= (1 << shift)) {
                return (slotsLen-1) * 4 + i;
            }
        }
        return (slotsLen-1) * 4 + 1;
    }
}

function loadDynamicReferencePage(uint256 tokenId, uint256 offset, uint256 count) public view override returns (address[] memory addresses, uint256[] memory tokenIds) {
    uint256 refCount = getDynamicReferenceCount(tokenId);
    if (offset >= refCount) {
        return (new address[](0), new uint256[](0));
    }
    uint256 realCount = refCount - offset;
    if (realCount > count) {
        realCount = count;
    }
    addresses = new address[](realCount);
    tokenIds = new uint256[](realCount);
    uint256[] storage slots = _dynamicLiterefStorage[tokenId].slots;
    // start at offset
    for (uint256 i = 0; i < realCount; i++) {
        uint256 idx = offset + i;
        uint slotNumber = idx / 4; // integer division will get the correct slot number
        uint shift = (idx % 4) * 64; // the remainder will give the correct shift
        uint64 ref = uint64(slots[slotNumber] >> shift);
        (address attributeAddress, uint256 attributeTokenId) = getReferenceAddressAndTokenId(ref);
        addresses[i] = attributeAddress;
        tokenIds[i] = attributeTokenId;
    }
}\n\n`);
            }
            ;
        }
        return "";
    }
}
exports.DynamicRefFuncGen = DynamicRefFuncGen;
