/*
 * Copyright 2016 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.events;

import static java.util.stream.Collectors.toList;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Stream;

import org.apache.karaf.shell.commands.Command;
import org.apache.karaf.shell.commands.Option;
import org.joda.time.LocalDateTime;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.cluster.ClusterEvent;
import org.onosproject.event.Event;
import org.onosproject.mastership.MastershipEvent;
import org.onosproject.net.Link;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.link.LinkEvent;
import org.onosproject.net.topology.Topology;
import org.onosproject.net.topology.TopologyEvent;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;

/**
 * Command to print history of instance local ONOS Events.
 */
@Command(scope = "onos", name = "events",
         description = "Command to print history of instance local ONOS Events")
public class EventsCommand
    extends AbstractShellCommand {

    @Option(name = "--all", aliases = "-a",
            description = "Include all Events (default behavior)",
            required = false)
    private boolean all = false;

    @Option(name = "--mastership", aliases = "-m",
            description = "Include MastershipEvent",
            required = false)
    private boolean mastership = false;

    @Option(name = "--device", aliases = "-d",
            description = "Include DeviceEvent",
            required = false)
    private boolean device = false;

    @Option(name = "--link", aliases = "-l",
            description = "Include LinkEvent",
            required = false)
    private boolean link = false;

    @Option(name = "--topology", aliases = "-t",
            description = "Include TopologyEvent",
            required = false)
    private boolean topology = false;

    @Option(name = "--host", aliases = "-t",
            description = "Include HostEvent",
            required = false)
    private boolean host = false;

    @Option(name = "--cluster", aliases = "-c",
            description = "Include ClusterEvent",
            required = false)
    private boolean cluster = false;

    @Option(name = "--max-events", aliases = "-n",
            description = "Maximum number of events to print",
            required = false,
            valueToShowInHelp = "-1 [no limit]")
    private long maxSize = -1;

    @Override
    protected void execute() {
        EventHistoryService eventHistoryService = get(EventHistoryService.class);

        Stream<Event<?, ?>> events = eventHistoryService.history().stream();

        boolean dumpAll = all || !(mastership || device || link || topology || host);

        if (!dumpAll) {
            Predicate<Event<?, ?>> filter = (defaultIs) -> false;

            if (mastership) {
                filter = filter.or(evt -> evt instanceof MastershipEvent);
            }
            if (device) {
                filter = filter.or(evt -> evt instanceof DeviceEvent);
            }
            if (link) {
                filter = filter.or(evt -> evt instanceof LinkEvent);
            }
            if (topology) {
                filter = filter.or(evt -> evt instanceof TopologyEvent);
            }
            if (host) {
                filter = filter.or(evt -> evt instanceof HostEvent);
            }
            if (cluster) {
                filter = filter.or(evt -> evt instanceof ClusterEvent);
            }

            events = events.filter(filter);
        }

        if (maxSize > 0) {
            events = events.limit(maxSize);
        }

        if (outputJson()) {
            ArrayNode jsonEvents = events.map(this::json).collect(toArrayNode());
            printJson(jsonEvents);
        } else {
            events.forEach(this::printEvent);
        }

    }

    private Collector<JsonNode, ArrayNode, ArrayNode> toArrayNode() {
        return Collector.of(() -> mapper().createArrayNode(),
                            ArrayNode::add,
                            ArrayNode::addAll);
    }

    private ObjectNode json(Event<?, ?> event) {
        ObjectNode result = mapper().createObjectNode();

        result.put("time", event.time())
              .put("type", event.type().toString())
              .put("event", event.toString());

        return result;
    }

    /**
     * Print JsonNode using default pretty printer.
     *
     * @param json JSON node to print
     */
    private void printJson(JsonNode json) {
        try {
            print("%s", mapper().writerWithDefaultPrettyPrinter().writeValueAsString(json));
        } catch (JsonProcessingException e) {
            StringWriter sw = new StringWriter();
            e.printStackTrace(new PrintWriter(sw));
            print("[ERROR] %s\n%s", e.getMessage(), sw.toString());
        }
    }

    private void printEvent(Event<?, ?> event) {
        if (event instanceof DeviceEvent) {
            DeviceEvent deviceEvent = (DeviceEvent) event;
            if (event.type().toString().startsWith("PORT")) {
                // Port event
                print("%s %s\t%s/%s [%s]",
                      new LocalDateTime(event.time()),
                      event.type(),
                      deviceEvent.subject().id(), deviceEvent.port().number(),
                      deviceEvent.port()
                  );
            } else {
                // Device event
                print("%s %s\t%s [%s]",
                      new LocalDateTime(event.time()),
                      event.type(),
                      deviceEvent.subject().id(),
                      deviceEvent.subject()
                  );
            }

        } else if (event instanceof MastershipEvent) {
            print("%s %s\t%s [%s]",
                  new LocalDateTime(event.time()),
                  event.type(),
                  event.subject(),
                  ((MastershipEvent) event).roleInfo());

        } else if (event instanceof LinkEvent) {
            LinkEvent linkEvent = (LinkEvent) event;
            Link link = linkEvent.subject();
            print("%s %s\t%s/%s-%s/%s [%s]",
                  new LocalDateTime(event.time()),
                  event.type(),
                  link.src().deviceId(), link.src().port(), link.dst().deviceId(), link.dst().port(),
                  link);

        } else if (event instanceof HostEvent) {
            HostEvent hostEvent = (HostEvent) event;
            print("%s %s\t%s [%s->%s]",
                  new LocalDateTime(event.time()),
                  event.type(),
                  hostEvent.subject().id(),
                  hostEvent.prevSubject(), hostEvent.subject());

        } else if (event instanceof TopologyEvent) {
            TopologyEvent topoEvent = (TopologyEvent) event;
            List<Event> reasons = MoreObjects.firstNonNull(topoEvent.reasons(), ImmutableList.of());
            Topology topo = topoEvent.subject();
            String summary = String.format("(d=%d,l=%d,c=%d)",
                                           topo.deviceCount(),
                                           topo.linkCount(),
                                           topo.clusterCount());
            print("%s %s%s [%s]",
                  new LocalDateTime(event.time()),
                  event.type(),
                  summary,
                  reasons.stream().map(e -> e.type()).collect(toList()));

        } else if (event instanceof ClusterEvent) {
            print("%s %s\t%s [%s]",
                  new LocalDateTime(event.time()),
                  event.type(),
                  ((ClusterEvent) event).subject().id(),
                  event.subject());

        } else {
            // Unknown Event?
            print("%s %s\t%s [%s]",
                  new LocalDateTime(event.time()),
                  event.type(),
                  event.subject(),
                  event);
        }
    }

}

@@ -7853,16 +7853,75 @@
 asons(),
+%0A                                                          
  Immutab
@@ -7927,16 +7927,23 @@
 bleList.
+%3CEvent%3E
 of());%0A 

Fix compilation problem on some versions of the JVM