/*
 * Copyright 2018-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.odtn.internal;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.ElementId;
import org.onosproject.odtn.TapiResolver;
import org.onosproject.odtn.utils.tapi.TapiNepRef;
import org.onosproject.odtn.utils.tapi.TapiNodeRef;
import org.slf4j.Logger;

import static org.slf4j.LoggerFactory.getLogger;

/**
 * OSGi Component for ODTN TAPI resolver application.
 */
@Component(immediate = true)
@Service
public class DefaultTapiResolver implements TapiResolver {

    private final Logger log = getLogger(getClass());

    protected TapiDataProducer dataProvider = new DcsBasedTapiDataProducer();

    private List<TapiNodeRef> tapiNodeRefList = new CopyOnWriteArrayList<>();
    private List<TapiNepRef> tapiNepRefList = new CopyOnWriteArrayList<>();

    /**
     * When source (e.g. DCS) is updated, set true
     * When cache update completed successfully, set false
     * <p>
     * This flag takes effect when cache update failed with exception,
     * this remains to be true so the cache update process conducts again
     */
    private Boolean isDirty = false;

    /**
     * When source (e.g. DCS) is updated, set true
     * When cache update started, set false
     * <p>
     * This flag takes effect when source updated during cache updating
     * this forces cache update again at the next request
     */
    private Boolean sourceUpdated = false;

    @Activate
    public void activate() {
        log.info("Started");
        dataProvider.init();
    }

    @Deactivate
    public void deactivate() {
        log.info("Stopped");
    }

    @Override
    public boolean hasNodeRef(ElementId deviceId) {
        updateCache();
        return tapiNodeRefList.stream()
                .anyMatch(node -> node.getDeviceId().equals(deviceId));
    }

    @Override
    public boolean hasNepRef(ConnectPoint cp) {
        updateCache();
        return tapiNepRefList.stream()
                .anyMatch(nep -> nep.getConnectPoint().equals(cp));
    }

    @Override
    public boolean hasNepRef(String sipId) {
        updateCache();
        return tapiNepRefList.stream()
                .anyMatch(nep -> nep.getSipId().equals(sipId));
    }

    @Override
    public TapiNodeRef getNodeRef(TapiNodeRef nodeRef) throws NoSuchElementException {
        updateCache();
        TapiNodeRef ret = null;
        try {
            ret = tapiNodeRefList.stream()
                    .filter(nodeRef::equals)
                    .findFirst().get();
        } catch (NoSuchElementException e) {
            log.error("Node not found of {}", nodeRef);
            throw e;
        }
        return ret;
    }

    @Override
    public TapiNodeRef getNodeRef(ElementId deviceId) throws NoSuchElementException {
        updateCache();
        TapiNodeRef ret = null;
        try {
            ret = tapiNodeRefList.stream()
                    .filter(node -> node.getDeviceId() != null && node.getDeviceId().equals(deviceId))
                    .findFirst().get();
        } catch (NoSuchElementException e) {
            log.error("Node not found associated with {}", deviceId);
            throw e;
        }
        return ret;
    }

    @Override
    public List<TapiNodeRef> getNodeRefs() {
        updateCache();
        return new ArrayList<>(tapiNodeRefList);
    }

    @Override
    public List<TapiNodeRef> getNodeRefs(Map<String, String> filter) {
        updateCache();
        Stream<TapiNodeRef> filterStream = tapiNodeRefList.stream();
        for (String key : filter.keySet()) {
            filterStream = filterStream.filter(nodeRef -> nodeRef.is(key, filter.get(key)));
        }
        return filterStream.collect(Collectors.toList());
    }

    @Override
    public TapiNepRef getNepRef(TapiNepRef nepRef) throws NoSuchElementException {
        updateCache();
        TapiNepRef ret = null;
        try {
            ret = tapiNepRefList.stream()
                    .filter(nepRef::equals)
                    .findFirst().get();
        } catch (NoSuchElementException e) {
            log.error("Nep not found of {}", nepRef);
            throw e;
        }
        return ret;
    }

    @Override
    public TapiNepRef getNepRef(ConnectPoint cp) throws NoSuchElementException {
        updateCache();
        TapiNepRef ret = null;
        try {
            ret = tapiNepRefList.stream()
                    .filter(nep -> nep.getConnectPoint() != null && nep.getConnectPoint().equals(cp))
                    .findFirst().get();
        } catch (NoSuchElementException e) {
            log.error("Nep not found associated with {}", cp);
            throw e;
        }
        return ret;
    }

    @Override
    public TapiNepRef getNepRef(String sipId) throws NoSuchElementException {
        updateCache();
        TapiNepRef ret = null;
        try {
            ret = tapiNepRefList.stream()
                    .filter(nep -> nep.getSipId() != null && nep.getSipId().equals(sipId))
                    .findFirst().get();
        } catch (NoSuchElementException e) {
            log.error("Nep not found associated with {}", sipId);
            throw e;
        }
        return ret;
    }

    @Override
    public List<TapiNepRef> getNepRefs() {
        updateCache();
        return new ArrayList<>(tapiNepRefList);
    }

    @Override
    public List<TapiNepRef> getNepRefs(Map<String, String> filter) {
        updateCache();
        Stream<TapiNepRef> filterStream = tapiNepRefList.stream();
        for (String key : filter.keySet()) {
            filterStream = filterStream.filter(nepRef -> nepRef.is(key, filter.get(key)));
        }
        return filterStream.collect(Collectors.toList());
    }

    @Override
    public void makeDirty() {
        sourceUpdated = true;
        isDirty = true;
    }

    protected void addNodeRef(TapiNodeRef nodeRef) {
        tapiNodeRefList.add(nodeRef);
        log.info("Nodes: {}", tapiNodeRefList);
    }

    protected void addNepRef(TapiNepRef nepRef) {
        tapiNepRefList.add(nepRef);
        log.info("Neps: {}", tapiNepRefList);
    }

    protected void addNodeRefList(List<TapiNodeRef> nodes) {
        tapiNodeRefList = nodes;
        log.info("Nodes: {}", tapiNodeRefList);
    }

    protected void addNepRefList(List<TapiNepRef> neps) {
        tapiNepRefList = neps;
        log.info("Neps: {}", tapiNepRefList);
    }

    private void updateCache() {
        log.info("Dirty: {}, Source updated: {}", isDirty, sourceUpdated);
        if (isDirty || sourceUpdated) {
            sourceUpdated = false;
            clearCache();
            dataProvider.updateCacheRequest(this);
            log.info("Update completed: {}", tapiNodeRefList);
            isDirty = false;
        }
    }

    private void clearCache() {
        tapiNodeRefList.clear();
        tapiNepRefList.clear();
    }
}

@@ -3194,32 +3194,58 @@
 .anyMatch(nep -%3E
+ nep.getSipId() != null &&
  nep.getSipId().

The nep.getSipId() should be checked if it is null.