/**
 * This file is part of the Meeds project (https://meeds.io/).
 *
 * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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.
 */
package org.exoplatform.commons.utils;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.List;


/**
 * <p>
 * This class defines the common functionalities for the serializable subclasses of {@link PageList}. Note that itself it does
 * not implement the {@link java.io.Serializable} interface for the reason that it needs to define a no arg constructor in a non
 * serializble class (serialization constraint).
 * </p>
 *
 * <p>
 * The method defines an abstract {@link #connect()} method that is used to connect the the page list to the underlying data.
 * </p>
 *
 * <p>
 * The methods {@link #readState(java.io.ObjectInputStream)} and {@link #writeState(java.io.ObjectOutputStream)} are defined for
 * the subclasses and should be called by <code>void readObject(ObjectInputStream in)</code> and
 * <code>void writeObject(ObjectOutputStream out)</code> custom serialization protocol.
 * </p>
 *
 */
public abstract class AbstractSerializablePageList<E> extends PageList<E> {

    /** . */
    private static final Field pageSizeField;

    static {
        try {
            pageSizeField = PageList.class.getDeclaredField("pageSize_");
            pageSizeField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            throw new Error(e);
        }
    }

    /**
     * Builds a page list.
     *
     * @param pageSize the page size
     */
    protected AbstractSerializablePageList(int pageSize) {
        super(pageSize);
    }

    /**
     * This constructor should not be used by subclasses, it is only for serialization needs.
     */
    protected AbstractSerializablePageList() {
        super(10);
    }

    /** . */
    private LazyList<E> lazyList;

    protected abstract ListAccess<E> connect() throws Exception;

    private void ensureCorrectState() {
        if (lazyList == null) {
            try {
                lazyList = new LazyList<E>(connect(), super.getPageSize());
            } catch (Exception e) {
                throw new UndeclaredThrowableException(e);
            }

            // Save temporarily the current page
            int currentPage = currentPage_;

            // Refresh state
            super.setAvailablePage(lazyList.size());

            // Put back current page that was written by previous method call
            if (currentPage != -1) {
                currentPage_ = currentPage;
            }
        }
    }

    @Override
    protected final void populateCurrentPage(int page) throws Exception {
        // Make sure we have correct state
        ensureCorrectState();

        //
        int from = getFrom();
        int to = getTo();
        currentListPage_ = lazyList.subList(from, to);
    }

    @Override
    public final List<E> getAll() {
        ensureCorrectState();

        //
        return lazyList;
    }

    // Serialization : should be used by subclasses

    protected final void writeState(ObjectOutputStream out) throws IOException {
        int pageSize;
        try {
            pageSize = pageSizeField.getInt(this);
        } catch (IllegalAccessException e) {
            InvalidObjectException ioe = new InvalidObjectException("Cannot set page size");
            ioe.initCause(e);
            throw ioe;
        }

        //
        out.writeInt(pageSize);
        out.writeInt(currentPage_);
    }

    protected final void readState(ObjectInputStream in) throws IOException {
        try {
            pageSizeField.setInt(this, in.readInt());
        } catch (IllegalAccessException e) {
            InvalidObjectException ioe = new InvalidObjectException("Cannot set page size");
            ioe.initCause(e);
            throw ioe;
        }
        currentPage_ = in.readInt();
    }

    // Intercept all method calls

    @Override
    public final int getAvailablePage() {
        ensureCorrectState();
        return super.getAvailablePage();
    }

    @Override
    public final int getTo() {
        ensureCorrectState();
        return super.getTo();
    }

    @Override
    public final int getFrom() {
        ensureCorrectState();
        return super.getFrom();
    }

    @Override
    protected final void setAvailablePage(int available) {
        ensureCorrectState();
        super.setAvailablePage(available);
    }

    @Override
    protected final void checkAndSetPage(int page) throws Exception {
        ensureCorrectState();
        super.checkAndSetPage(page);
    }

    @Override
    public final List<E> getPage(int page) throws Exception {
        ensureCorrectState();
        return super.getPage(page);
    }

    @Override
    public final List<E> currentPage() throws Exception {
        ensureCorrectState();
        return super.currentPage();
    }

    @Override
    public final int getAvailable() {
        ensureCorrectState();
        return super.getAvailable();
    }

    @Override
    public final int getCurrentPage() {
        ensureCorrectState();
        return super.getCurrentPage();
    }

    @Override
    public final void setPageSize(int pageSize) {
        ensureCorrectState();
        super.setPageSize(pageSize);
    }

    @Override
    public final int getPageSize() {
        ensureCorrectState();
        return super.getPageSize();
    }
}
