/*
 * Copyright 2014 Open Networking Laboratory
 *
 * 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.net.intent.impl.installer;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
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.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.core.DefaultGroupId;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Link;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleOperation;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.intent.Constraint;
import org.onosproject.net.intent.IntentExtensionService;
import org.onosproject.net.intent.IntentInstaller;
import org.onosproject.net.intent.PathIntent;
import org.onosproject.net.resource.DefaultLinkResourceRequest;
import org.onosproject.net.resource.LinkResourceAllocations;
import org.onosproject.net.resource.LinkResourceRequest;
import org.onosproject.net.resource.LinkResourceService;
import org.slf4j.Logger;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import static org.onosproject.net.flow.DefaultTrafficTreatment.builder;
import static org.slf4j.LoggerFactory.getLogger;

/**
 * Installer for {@link PathIntent packet path connectivity intents}.
 */
@Component(immediate = true)
public class PathIntentInstaller implements IntentInstaller<PathIntent> {

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

    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
    protected IntentExtensionService intentManager;

    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
    protected CoreService coreService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
    protected LinkResourceService resourceService;

    protected ApplicationId appId;

    @Activate
    public void activate() {
        appId = coreService.registerApplication("org.onosproject.net.intent");
        intentManager.registerInstaller(PathIntent.class, this);
    }

    @Deactivate
    public void deactivate() {
        intentManager.unregisterInstaller(PathIntent.class);
    }

    @Override
    public List<Collection<FlowRuleOperation>> install(PathIntent intent) {
        LinkResourceAllocations allocations = allocateResources(intent);

        TrafficSelector.Builder builder =
                DefaultTrafficSelector.builder(intent.selector());
        Iterator<Link> links = intent.path().links().iterator();
        ConnectPoint prev = links.next().dst();
        List<FlowRuleOperation> rules = Lists.newLinkedList();
        // TODO Generate multiple batches
        while (links.hasNext()) {
            builder.matchInPort(prev.port());
            Link link = links.next();
            // if this is the last flow rule, apply the intent's treatments
            TrafficTreatment treatment =
                    (links.hasNext() ? builder() : builder(intent.treatment()))
                    .setOutput(link.src().port()).build();

            FlowRule rule = new DefaultFlowRule(link.src().deviceId(),
                    builder.build(), treatment, intent.priority(),
                    appId,
                    new DefaultGroupId((short) (intent.id().fingerprint() & 0xffff)),
                    0, true);
            rules.add(new FlowRuleOperation(rule, FlowRuleOperation.Type.ADD));
            prev = link.dst();
        }

        return Lists.newArrayList(ImmutableSet.of(rules));
    }

    @Override
    public List<Collection<FlowRuleOperation>> uninstall(PathIntent intent) {
        deallocateResources(intent);
        TrafficSelector.Builder builder =
                DefaultTrafficSelector.builder(intent.selector());
        Iterator<Link> links = intent.path().links().iterator();
        ConnectPoint prev = links.next().dst();
        List<FlowRuleOperation> rules = Lists.newLinkedList();
        // TODO Generate multiple batches
        while (links.hasNext()) {
            builder.matchInPort(prev.port());
            Link link = links.next();
            // if this is the last flow rule, apply the intent's treatments
            TrafficTreatment treatment =
                    (links.hasNext() ? builder() : builder(intent.treatment()))
                            .setOutput(link.src().port()).build();
            FlowRule rule = new DefaultFlowRule(link.src().deviceId(),
                    builder.build(), treatment, intent.priority(), appId,
                    new DefaultGroupId((short) (intent.id().fingerprint() & 0xffff)),
                    0, true);
            rules.add(new FlowRuleOperation(rule, FlowRuleOperation.Type.REMOVE));
            prev = link.dst();
        }
        // FIXME this should change to new api
        return Lists.newArrayList(ImmutableSet.of(rules));
    }

    @Override
    public List<Collection<FlowRuleOperation>> replace(PathIntent oldIntent, PathIntent newIntent) {
        // FIXME: implement this
        List<Collection<FlowRuleOperation>> batches = Lists.newArrayList();
        batches.addAll(uninstall(oldIntent));
        batches.addAll(install(newIntent));
        return batches;
    }

    /**
     * Allocate resources required for an intent.
     *
     * @param intent intent to allocate resource for
     * @return allocated resources if any are required, null otherwise
     */
    private LinkResourceAllocations allocateResources(PathIntent intent) {
        LinkResourceRequest.Builder builder =
                DefaultLinkResourceRequest.builder(intent.id(), intent.path().links());
        for (Constraint constraint : intent.constraints()) {
            builder.addConstraint(constraint);
        }
        LinkResourceRequest request = builder.build();
        return request.resources().isEmpty() ? null : resourceService.requestResources(request);
    }

    /**
     * Deallocate resources held by an intent.
     *
     * @param intent intent to deallocate resources for
     */
    private void deallocateResources(PathIntent intent) {
        if (intent.constraints().isEmpty()) {
            return;
        }

        LinkResourceAllocations allocatedResources = resourceService.getAllocations(intent.id());
        if (allocatedResources != null) {
            resourceService.releaseResources(allocatedResources);
        }
    }
}
