/*_############################################################################
  _## 
  _##  SNMP4J-AgentX - AgentXPendingGet.java  
  _## 
  _##  Copyright (C) 2005-2026  Frank Fock (SNMP4J.org)
  _##  
  _##  This program is free software; you can redistribute it and/or modify
  _##  it under the terms of the GNU General Public License version 2 as 
  _##  published by the Free Software Foundation.
  _##
  _##  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
  _##  
  _##########################################################################*/

package org.snmp4j.agent.agentx.master;

import org.snmp4j.agent.agentx.AgentXGetNextPDU;
import org.snmp4j.agent.agentx.subagent.AgentXRequest;
import org.snmp4j.agent.request.*;

import java.io.Serial;
import java.util.LinkedList;

import org.snmp4j.smi.Address;
import org.snmp4j.smi.OID;
import org.snmp4j.agent.MOScope;
import java.util.Collection;
import org.snmp4j.PDU;
import org.snmp4j.agent.agentx.AgentXGetBulkPDU;
import java.util.List;
import java.util.function.Function;

import org.snmp4j.agent.agentx.AgentXGetPDU;
import org.snmp4j.agent.agentx.AgentXPDU;
import org.snmp4j.log.LogFactory;
import org.snmp4j.log.LogAdapter;

/**
 * {@link AgentXPendingGet} implements an AgentX GET request.
 * @param <A>
 *    the address type.
 */
public class AgentXPendingGet<A extends Address> extends AbstractAgentXPending<A> {

    private static final LogAdapter logger =
            LogFactory.getLogger(AgentXPendingGet.class);
    @Serial
    private static final long serialVersionUID = -8370197543450517767L;

    /**
     * The AgentX request PDU (Get, GetNext, or GetBulk) sent to the sub-agent.
     */
    private AgentXPDU agentXRequestPDU;
    /**
     * The search ranges of the request that are dispatched to the sub-agent.
     */
    private final List<AgentXSearchRange> searchRanges = new LinkedList<AgentXSearchRange>();

    /**
     * The number of non-repeater variable bindings for a GetBulk request.
     */
    private short nonRepeater = 0;

    /**
     * Optional filter to remove objects returned by an AgentX sub-agent from the result of GetNext or GetBulk
     * operations. Typically, this can be VACM filtering. Only if the provided function returns {@code true}, the
     * retrieved OID will be returned to the command generator.
     * @since 4.1.0
     */
    private Function<OID, Boolean> oidFilter;

    /**
     * Creates a {@link AgentXPendingGet} request.
     * @param registration
     *    the registration used processing the request.
     * @param request
     *    the request.
     * @param searchRange
     *    the current search range for the request.
     */
    public AgentXPendingGet(AgentXRegEntry<A> registration, SnmpRequest request, AgentXSearchRange searchRange) {
        super(registration, request);
        this.searchRanges.add(searchRange);
    }

    /**
     * Creates a {@link AgentXPendingGet} request.
     * @param registration
     *    the registration used processing the request.
     * @param request
     *    the request.
     * @param searchRange
     *    the current search range for the request.
     * @param oidFilter
     *    an optional filter to remove objects returned by an AgentX sub-agent from the result of GetNext or GetBulk
     *    operations. Typically, this can be VACM filtering. Only if the provided function is {@code null} or
     *    returns {@code true}, the retrieved OID will be returned to the command generator.
     * @since 4.1.0
     */
    public AgentXPendingGet(AgentXRegEntry<A> registration, SnmpRequest request, AgentXSearchRange searchRange,
                            Function<OID, Boolean> oidFilter) {
        this(registration, request, searchRange);
        this.oidFilter = oidFilter;
    }

    /**
     * Adds a search range to the request.
     * @param searchRange
     *    an additional search range.
     */
    public synchronized void addSearchRange(AgentXSearchRange searchRange) {
        this.searchRanges.add(searchRange);
    }

    /**
     * Sets the non-repeater count, i.e. marks the first n search ranges as non-repeating.
     * @param nonRepeater
     *   number of non-repeater search ranges.
     */
    public void setNonRepeater(short nonRepeater) {
        this.nonRepeater = nonRepeater;
    }

    /**
     * Increments the number of non-repeaters in the request.
     */
    public synchronized void incNonRepeater() {
        nonRepeater++;
    }

    @Override
    public AgentXPDU getAgentXPDU() {
        if (agentXRequestPDU == null) {
            createRequestPDU();
        }
        return agentXRequestPDU;
    }

    /**
     * Gets the search ranges of the GET type request.
     * @return
     *    a {@link Collection} of search ranges.
     */
    public Collection<AgentXSearchRange> getSearchRanges() {
        return searchRanges;
    }

    /**
     * Gets the number of non-repeaters in the request.
     * @return
     *    number of non-repeater.
     */
    public short getNonRepeater() {
        return nonRepeater;
    }

    /**
     * Get the optional filter to remove objects returned by an AgentX sub-agent from the result of GetNext or GetBulk
     * operations. Typically, this can be VACM filtering. Only if the provided function returns {@code true}, the
     * retrieved OID will be returned to the command generator.
     * @return
     *    the filter function.
     * @since 4.1.0
     */
    public Function<OID, Boolean> getOidFilter() {
        return oidFilter;
    }

    /**
     * Creates the AgentX request PDU from the search ranges.
     */
    private void createRequestPDU() {
        SnmpRequest request = searchRanges.get(0).getReferenceSubRequest().getRequest();
        PDU requestPDU = request.getSource().getPDU();
        switch (requestPDU.getType()) {
            case PDU.GETBULK: {
                short maxRep = getMaxRepetitions(request, requestPDU);
                agentXRequestPDU =
                        new AgentXGetBulkPDU(request.getContext(), maxRep, nonRepeater, searchRanges.toArray(new MOScope[0]));
                break;
            }
            case PDU.GET: {
                OID[] oids = new OID[searchRanges.size()];
                for (int i = 0; i < oids.length; i++) {
                    AgentXSearchRange sr = searchRanges.get(i);
                    oids[i] = sr.getLowerBound();
                }
                agentXRequestPDU = new AgentXGetPDU(request.getContext(), oids);
                break;
            }
            case PDU.GETNEXT: {
                agentXRequestPDU =
                        new AgentXGetNextPDU(request.getContext(), searchRanges.toArray(new MOScope[0]));
                break;
            }
            default: {
                logger.error("Failed to create AgentX PDU for PDU type " + requestPDU.getType());
            }
        }
    }

    private static short getMaxRepetitions(SnmpRequest request, PDU requestPDU) {
        int maxRep =
                requestPDU.getMaxRepetitions() - request.getCompleteRepetitions();
        maxRep = Math.max(1, maxRep);
        maxRep =
                Math.min(maxRep, AgentXMasterAgent.getMaxGetBulkRepetitions());
        return (short) maxRep;
    }

    @Override
    public AgentXMasterSession<A> getSession() {
        return registration.getSession();
    }

    @Override
    public SubRequestIterator<SnmpRequest.SnmpSubRequest> getReferences() {
        return new GetSubRequestIterator();
    }

    @Override
    public String toString() {
        return getClass().getName()+"["+super.toStringMembers()+",searchRanges="+
                searchRanges+"]";
    }

    class GetSubRequestIterator extends SubRequestIteratorSupport<SnmpRequest.SnmpSubRequest> {

        protected GetSubRequestIterator() {
            super(searchRanges.iterator());
        }

        protected SnmpRequest.SnmpSubRequest mapToSubRequest(Object element) {
            return ((AgentXSearchRange)element).getReferenceSubRequest();
        }
    }
}
