/*
 * 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
 *
 *      http://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.
 */

package org.apache.ignite.internal.pagememory.datapage;

import static org.apache.ignite.internal.pagememory.util.PageIdUtils.itemId;
import static org.apache.ignite.internal.pagememory.util.PageIdUtils.pageId;

import org.apache.ignite.internal.pagememory.PageMemory;
import org.apache.ignite.internal.pagememory.io.AbstractDataPageIo;
import org.apache.ignite.internal.pagememory.io.DataPagePayload;
import org.apache.ignite.internal.pagememory.metric.IoStatisticsHolder;
import org.apache.ignite.lang.IgniteInternalCheckedException;
import org.jetbrains.annotations.Nullable;

/**
 * Contains logic for reading values (that cannot be fragmented, that it, occupy more than one page) from page memory data pages.
 */
public abstract class NonFragmentableDataPageReader<T> {
    private final PageMemory pageMemory;
    private final int groupId;
    private final IoStatisticsHolder statisticsHolder;

    /**
     * Constructs a new instance.
     *
     * @param pageMemory       page memory that will be used to lock and access memory
     * @param groupId          ID of the cache group with which the reader works (all pages must belong to this group)
     * @param statisticsHolder used to track statistics about operations
     */
    public NonFragmentableDataPageReader(PageMemory pageMemory, int groupId, IoStatisticsHolder statisticsHolder) {
        this.pageMemory = pageMemory;
        this.groupId = groupId;
        this.statisticsHolder = statisticsHolder;
    }

    /**
     * Returns a row by link. If it turns out the value was fragmented, an exception is thrown, as this implementation
     * cannot handle fragmented values.
     *
     * @param link Row link
     * @return row object assembled from the row bytes
     * @throws IgniteInternalCheckedException If failed
     * @see org.apache.ignite.internal.pagememory.util.PageIdUtils#link(long, int)
     */
    @Nullable
    public T getRowByLink(final long link) throws IgniteInternalCheckedException {
        assert link != 0;

        int pageSize = pageMemory.realPageSize(groupId);

        final long pageId = pageId(link);

        final long page = pageMemory.acquirePage(groupId, pageId, statisticsHolder);

        try {
            long pageAddr = pageMemory.readLock(groupId, pageId, page);

            assert pageAddr != 0L : link;

            try {
                AbstractDataPageIo<?> dataIo = pageMemory.ioRegistry().resolve(pageAddr);

                int itemId = itemId(link);

                if (handleNonExistentItemsGracefully() && !dataIo.itemExists(pageAddr, itemId, pageSize)) {
                    return rowForNonExistingItem(pageId, itemId);
                }

                DataPagePayload data = dataIo.readPayload(pageAddr, itemId, pageSize);

                if (data.hasMoreFragments()) {
                    throw new IllegalStateException("Value for link " + link + " is fragmented, which is not supported");
                }

                return readRowFromAddress(link, pageAddr + data.offset());

            } finally {
                pageMemory.readUnlock(groupId, pageId, page);
            }
        } finally {
            pageMemory.releasePage(groupId, pageId, page);
        }
    }

    /**
     * Reads row object from a contiguous region of memory represented by its starting address. The memory region contains exactly
     * the bytes representing the row.
     *
     * @param link            row link
     * @param pageAddr        address of memory containing all row bytes
     * @return row object
     * @see org.apache.ignite.internal.pagememory.util.PageIdUtils#link(long, int)
     */
    protected abstract T readRowFromAddress(long link, long pageAddr);

    /**
     * Returns {@code true} if graceful handling of non-existent items should be enabled.
     * If it is enabled and a link with a non-existent item ID is provided, {@link #getRowByLink(long)} will return
     * the result of {@link #rowForNonExistingItem(long, long)} invocation.
     * If it is disabled, then an assertion will fail.
     *
     * @return {@code true} if graceful handling of non-existent items should be enabled
     */
    protected boolean handleNonExistentItemsGracefully() {
        return false;
    }

    /**
     * Returns a special row value that can be used as a marker to specify that the given itemId does not actually exist.
     *
     * @param pageId ID of the page
     * @param itemId ID of the item
     * @return special row value
     */
    @Nullable
    protected T rowForNonExistingItem(long pageId, long itemId) {
        throw new IllegalStateException("Item is invalid for pageId=" + pageId + ", itemId=" + itemId);
    }
}
