/*
 * Copyright 2017-present Open Networking Foundation
 *
 * Licensed 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.onosproject.drivers.p4runtime;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.onosproject.net.device.DefaultPortStatistics;
import org.onosproject.net.device.PortStatistics;
import org.onosproject.net.device.PortStatisticsDiscovery;
import org.onosproject.net.pi.runtime.PiCounterCellData;
import org.onosproject.net.pi.runtime.PiCounterCellId;
import org.onosproject.net.pi.runtime.PiCounterId;
import org.onosproject.net.pi.runtime.PiIndirectCounterCellId;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import static org.onosproject.net.pi.runtime.PiCounterType.INDIRECT;

/**
 * Implementation of a PortStatisticsBehaviour that can be used for any P4 program based on default.p4 (i.e. those
 * under onos/tools/test/p4src).
 */
public class DefaultP4PortStatisticsDiscovery extends AbstractP4RuntimeHandlerBehaviour
        implements PortStatisticsDiscovery {

    // FIXME: hard-coding the scope here will break support for the P4_14 version of the program.
    // With P4_14, counter names in the generated P4Info won't have any scope.
    // A solution could be that of dynamically building counter IDs based on the P4Info (as in DefaultP4Interpreter).
    private static final String DEFAULT_SCOPE = "port_counters_control";
    private static final String INGRESS_COUNTER = "ingress_port_counter";
    private static final String EGRESS_COUNTER = "egress_port_counter";

    /**
     * Returns the scope string to be used for the counter IDs.
     *
     * @return scope string
     */
    public String scope() {
        return DEFAULT_SCOPE;
    }

    @Override
    public Collection<PortStatistics> discoverPortStatistics() {

        if (!super.setupBehaviour()) {
            return Collections.emptyList();
        }

        final PiCounterId ingressCounterId = PiCounterId.of(scope(), INGRESS_COUNTER, INDIRECT);
        final PiCounterId egressCounterId = PiCounterId.of(scope(), EGRESS_COUNTER, INDIRECT);

        Map<Long, DefaultPortStatistics.Builder> portStatBuilders = Maps.newHashMap();

        deviceService.getPorts(deviceId)
                .forEach(p -> portStatBuilders.put(p.number().toLong(),
                                                   DefaultPortStatistics.builder()
                                                           .setPort(p.number())
                                                           .setDeviceId(deviceId)));

        Set<PiCounterCellId> counterCellIds = Sets.newHashSet();
        portStatBuilders.keySet().forEach(p -> {
            // Counter cell/index = port number.
            counterCellIds.add(PiIndirectCounterCellId.of(ingressCounterId, p));
            counterCellIds.add(PiIndirectCounterCellId.of(egressCounterId, p));
        });

        Collection<PiCounterCellData> counterEntryResponse;
        try {
            counterEntryResponse = client.readCounterCells(counterCellIds, pipeconf).get();
        } catch (InterruptedException | ExecutionException e) {
            log.warn("Exception while reading port counters from {}: {}", deviceId, e.toString());
            log.debug("", e);
            return Collections.emptyList();
        }

        counterEntryResponse.forEach(counterData -> {
            if (counterData.cellId().type() != INDIRECT) {
                log.warn("Invalid counter data type {}, skipping", counterData.cellId().type());
                return;
            }
            PiIndirectCounterCellId indCellId = (PiIndirectCounterCellId) counterData.cellId();
            if (!portStatBuilders.containsKey(indCellId.index())) {
                log.warn("Unrecognized counter index {}, skipping", counterData);
                return;
            }
            DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(indCellId.index());
            if (counterData.cellId().counterId().equals(ingressCounterId)) {
                statsBuilder.setPacketsReceived(counterData.packets());
                statsBuilder.setBytesReceived(counterData.bytes());
            } else if (counterData.cellId().counterId().equals(egressCounterId)) {
                statsBuilder.setPacketsSent(counterData.packets());
                statsBuilder.setBytesSent(counterData.bytes());
            } else {
                log.warn("Unrecognized counter ID {}, skipping", counterData);
            }
        });

        return portStatBuilders
                .values()
                .stream()
                .map(DefaultPortStatistics.Builder::build)
                .collect(Collectors.toList());
    }
}

@@ -2072,196 +2072,816 @@
 nal 
-String INGRESS_COUNTER = %22ingress_port_counter%22;%0A    private static final String EGRESS_COUNTER = %22egress_port_counter%22;%0A%0A    /**%0A     * Returns the scope string to be used for the
+PiCounterId DEFAULT_INGRESS_COUNTER_ID = PiCounterId.of(DEFAULT_SCOPE,%0A                                                                                 %22ingress_port_counter%22,%0A                                                                                 INDIRECT);%0A    private static final PiCounterId DEFAULT_EGRESS_COUNTER_ID = PiCounterId.of(DEFAULT_SCOPE,%0A                                                                                %22egress_port_counter%22,%0A                                                                                INDIRECT);%0A%0A    /**%0A     * Returns the ID of the ingress port counter.%0A     *%0A     * @return counter ID%0A     */%0A    public PiCounterId ingressCounterId() %7B%0A        return DEFAULT_INGRESS_COUNTER_ID;%0A    %7D%0A%0A    /**%0A     * Returns the ID of the egress port
  counter
  IDs
@@ -2876,20 +2876,16 @@
  counter
- IDs
 .%0A     *
@@ -2904,20 +2904,18 @@
 urn 
-s
 co
-pe string
+unter ID
 %0A   
@@ -2934,20 +2934,35 @@
 lic 
-String scope
+PiCounterId egressCounterId
 () %7B
@@ -2985,21 +2985,33 @@
 DEFAULT_
-SCOPE
+EGRESS_COUNTER_ID
 ;%0A    %7D%0A
@@ -3189,201 +3189,8 @@
  %7D%0A%0A
-        final PiCounterId ingressCounterId = PiCounterId.of(scope(), INGRESS_COUNTER, INDIRECT);%0A        final PiCounterId egressCounterId = PiCounterId.of(scope(), EGRESS_COUNTER, INDIRECT);%0A%0A
     
@@ -3864,32 +3864,34 @@
 ingressCounterId
+()
 , p));%0A         
@@ -3954,16 +3954,18 @@
 ounterId
+()
 , p));%0A 
@@ -5084,32 +5084,34 @@
 ingressCounterId
+()
 )) %7B%0A           
@@ -5317,16 +5317,18 @@
 ounterId
+()
 )) %7B%0A   

Allow for arbitrary counter IDs in DefaultP4PortStatisticsDiscovery