/*
 * 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
 *
 *   https://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.
 *
 *   SPDX-License-Identifier: Apache-2.0
 */

package org.apache.jena.dboe.trans.bplustree;

import static org.apache.jena.dboe.base.block.BlockType.BPTREE_BRANCH;
import static org.apache.jena.dboe.base.block.BlockType.BPTREE_LEAF;
import static org.apache.jena.dboe.base.block.BlockType.RECORD_BLOCK;

import java.nio.ByteBuffer;

import org.apache.jena.dboe.base.block.Block;
import org.apache.jena.dboe.base.block.BlockMgr;
import org.apache.jena.dboe.base.block.BlockType;
import org.apache.jena.dboe.base.buffer.PtrBuffer;
import org.apache.jena.dboe.base.buffer.RecordBuffer;
import org.apache.jena.dboe.base.page.BlockConverter;
import org.apache.jena.dboe.base.page.PageBlockMgr;

/** BPlusTreePageMgr = BPlusTreeNode manager */
public final class BPTreeNodeMgr extends PageBlockMgr<BPTreeNode>
{
    // Only "public" for external very low level tools in development to access this class.
    // Assume package access.

    public BPTreeNodeMgr(BPlusTree bpTree, BlockMgr blockMgr) {
        super(new Block2BPTreeNode(bpTree), blockMgr);
    }

    /** Allocate space for a fresh node. */
    public BPTreeNode createNode(int parent) {
        BPTreeNode n = create(BPTREE_BRANCH);
        n.setIsLeaf(false);
        n.setParent(parent);
        return n;
    }

    @Override
    public BPTreeNode getWrite(int id) {
        return super.getWrite(id, BPlusTreeParams.UnsetParent);
    }

    @Override
    public BPTreeNode getRead(int id) {
        return super.getRead(id, BPlusTreeParams.UnsetParent);
    }

    /** Fetch a block - fill in the parent id, which is not in the on-disk bytes */
    @Override
    public BPTreeNode getRead(int id, int parent) {
        BPTreeNode n = super.getRead$(id);
        n.setParent(parent);
        return n;
    }

    /** Fetch a block - fill in the parent id, which is not in the on-disk bytes */
    @Override
    public BPTreeNode getWrite(int id, int parent) {
        BPTreeNode n = super.getWrite$(id);
        n.setParent(parent);
        return n;
    }

    boolean isWritable(int id) {
        return false;
//      return bpTree.state.modifiableNodeBlock(id);
    }

    private static class Block2BPTreeNode implements BlockConverter<BPTreeNode> {
        private final BPlusTree bpTree;

        Block2BPTreeNode(BPlusTree bpTree) { this.bpTree = bpTree; }

        @Override
        public BPTreeNode createFromBlock(Block block, BlockType bType) {
            return overlay(bpTree, block, bType == RECORD_BLOCK, 0);
        }

        @Override
        public BPTreeNode fromBlock(Block block) {
            // synchronized - needed for multiple reader?
            synchronized (block) {
                int x = block.getByteBuffer().getInt(0);
                BlockType type = getType(x);

                if ( type != BPTREE_BRANCH && type != BPTREE_LEAF )
                    throw new BPTreeException("Wrong block type: " + type);
                int count = decodeCount(x);
                return overlay(bpTree, block, (type == BPTREE_LEAF), count);
            }
        }

        @Override
        public Block toBlock(BPTreeNode node) {
            // It's manipulated in-place so no conversion needed,
            // Just the count needs to be fixed up.
//            ByteBuffer bb = node.getBackingByteBuffer();
//            BlockType bType = (node.isLeaf ? BPTREE_LEAF : BPTREE_BRANCH );

            Block block = node.getBackingBlock();
            BlockType bType = (node.isLeaf() ? BPTREE_LEAF : BPTREE_BRANCH );

            int c = encodeCount(bType, node.getCount());
            block.getByteBuffer().putInt(0, c);
            return block;
        }
    }

//    // Leaves have a count of -(count+1)
//    // (same as the binary search encoding of "not found")
//    private static final int encCount(int i)     { return -(i+1); }
//    private static final int decCount(int i)     { return -i-1; }

    // ----
    private static final BlockType getType(int x) {
        return BlockType.extract(x >>> 24);
    }

    private static final int encodeCount(BlockType type, int i) {
        return (type.id() << 24) | (i & 0x00FFFFFF);
    }

    private static final int decodeCount(int i) {
        return i & 0x00FFFFFF;
    }

    /** byte[] layout.
     *
     * New:
     *  0: Block type
     *  1-3: Count
     *  Internal nodes:
     *    4-X:        Records: b+tree.MaxRec*record length
     *    X- :        Pointers: b+tree.MaxPtr*ptr length
     */
    private static BPTreeNode overlay(BPlusTree bpTree, Block block, boolean asLeaf, int count) {
        // if ( byteBuffer.order() != Const.NetworkOrder )
        // throw new BTreeException("ByteBuffer in wrong order");

        // Fix up the id later.
        BPTreeNode n = new BPTreeNode(bpTree);
        // The count is zero at the root only.
        // When the root is zero, it's a leaf.
        formatBPTreeNode(n, bpTree, block, asLeaf, BPlusTreeParams.NoParent, count);
        return n;
    }

    static void formatBPTreeNode(BPTreeNode n, BPlusTree bpTree, Block block, boolean leaf, int parent, int count) {
        BPlusTreeParams params = bpTree.getParams();

        int ptrBuffLen = params.MaxPtr * params.getPtrLength();

        // Allocate space for record, key and value, despite slight over allocation.
        int recBuffLen = params.MaxRec * params.getRecordLength();

        n.id = block.getId().intValue();
        n.setParent(parent);
        n.setCount(count);
        n.setIsLeaf(leaf);

        int header = BPlusTreeParams.BlockHeaderSize;
        int rStart = header;
        int pStart = header + recBuffLen;

        // Find the number of pointers.
        int numPtrs = -1;

        // The root can have count zero - which means one pointer always.
        // Junk when creating a new new node.
        if ( n.getCount() < 0 ) {
            numPtrs = 0;
            n.setCount(decodeCount(n.getCount()));
        } else
            numPtrs = n.getCount() + 1;

        // Block dependent

        n.block = block;
        ByteBuffer byteBuffer = block.getByteBuffer();

        // -- Records area
        byteBuffer.position(rStart);
        byteBuffer.limit(rStart + recBuffLen);
        ByteBuffer bbr = byteBuffer.slice();
        // bbr.limit(recBuffLen);
        n.setRecordBuffer(new RecordBuffer(bbr, n.params.keyFactory, n.getCount()));

        // -- Pointers area
        byteBuffer.position(pStart);
        byteBuffer.limit(pStart + ptrBuffLen);

        ByteBuffer bbi = byteBuffer.slice();
        // bbi.limit(ptrBuffLen);
        n.setPtrBuffer(new PtrBuffer(bbi, numPtrs));

        // Reset
        byteBuffer.rewind();
    }

    static final void formatForRoot(BPTreeNode n, boolean asLeaf) {
        BPTreeNodeMgr.formatBPTreeNode(n, n.bpTree, n.getBackingBlock(), asLeaf, BPlusTreeParams.RootParent, 0);
        // Tweak for the root-specials. The node is not consistent yet.
        // Has one dangling pointer.
    }
}
