/*******************************************************************************
 * Copyright (c) 2017 Ericsson
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/

package org.eclipse.tracecompass.analysis.counters.core;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.analysis.counters.core.aspects.CounterAspect;
import org.eclipse.tracecompass.analysis.counters.core.aspects.ITmfCounterAspect;
import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystemBuilder;
import org.eclipse.tracecompass.statesystem.core.StateSystemBuilderUtils;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateValueTypeException;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;
import org.eclipse.tracecompass.tmf.core.event.aspect.MultiAspect;
import org.eclipse.tracecompass.tmf.core.statesystem.AbstractTmfStateProvider;
import org.eclipse.tracecompass.tmf.core.statesystem.ITmfStateProvider;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;

/**
 * State provider for the counters analyses. <br>
 * <br>
 * Layout of the generated attribute tree:
 *
 * <pre>
 * {root}
 *   +- Grouped counter aspects
 *   |   +- ...
 *   +- Ungrouped counter aspects
 *       +- ...
 * </pre>
 *
 * @author Mikael Ferland
 */
public class CounterStateProvider extends AbstractTmfStateProvider {

    private static final Logger LOGGER = TraceCompassLog.getLogger(CounterStateProvider.class);

    private final Set<ITmfEventAspect<?>> fCounterAspects;

    /*
     * Map linking a class to an implemented object. Used for retrieving the name of
     * the grouping aspect during resolve.
     */
    private final Map<Class<? extends ITmfEventAspect<?>>, ITmfEventAspect<?>> fGroupingAspectImpls;

    /**
     * Factory method to build a new counter state provider.
     *
     * @param trace
     *            Trace directory
     * @return a counter state provider
     */
    public static CounterStateProvider create(ITmfTrace trace) {
        Map<Class<? extends ITmfEventAspect<?>>, ITmfEventAspect<?>> aspectImpls = new HashMap<>();
        Iterable<ITmfEventAspect<?>> counterAspects = TmfTraceUtils.getEventAspects(trace, ITmfCounterAspect.class);
        for (ITmfEventAspect<?> counter : counterAspects) {

            if (counter instanceof CounterAspect) {
                CounterAspect counterAspect = (CounterAspect) counter;
                for (Class<? extends ITmfEventAspect<?>> parentAspectClass : counterAspect.getGroups()) {

                    // Avoid creating the same aggregated aspect multiple times
                    if (parentAspectClass != null && !aspectImpls.containsKey(parentAspectClass)) {
                        /*
                         * Aggregated aspect if more than one are available for a given
                         * ITmfEventAspect<?> class.
                         */
                        ITmfEventAspect<?> goldenAspect = MultiAspect.create(TmfTraceUtils.getEventAspects(trace, parentAspectClass), parentAspectClass.getClass());
                        if (goldenAspect != null) {
                            aspectImpls.put(parentAspectClass, goldenAspect);
                        }
                    }
                }
            }
        }

        return new CounterStateProvider(trace, counterAspects, aspectImpls);
    }

    private CounterStateProvider(ITmfTrace trace, Iterable<ITmfEventAspect<?>> counterAspects, Map<Class<? extends ITmfEventAspect<?>>, ITmfEventAspect<?>> aspectImpls) {
        super(trace, CounterAnalysis.ID);
        fCounterAspects = ImmutableSet.copyOf(counterAspects);
        fGroupingAspectImpls = ImmutableMap.copyOf(aspectImpls);
    }

    @Override
    public int getVersion() {
        return 1;
    }

    @Override
    public @NonNull ITmfStateProvider getNewInstance() {
        return new CounterStateProvider(getTrace(), fCounterAspects, fGroupingAspectImpls);
    }

    @Override
    protected void eventHandle(@NonNull ITmfEvent event) {
        ITmfStateSystemBuilder ss = getStateSystemBuilder();
        if (ss == null) {
            return;
        }

        for (ITmfEventAspect<?> aspect : fCounterAspects) {
            if (aspect instanceof CounterAspect) {
                CounterAspect counterAspect = (CounterAspect) aspect;
                if (counterAspect.getGroups().length > 0) {
                    int rootQuark = ss.getQuarkAbsoluteAndAdd(CounterAnalysis.GROUPED_COUNTER_ASPECTS_ATTRIB);
                    handleGroupedCounterAspect(event, ss, counterAspect, rootQuark);
                } else {
                    int rootQuark = ss.getQuarkAbsoluteAndAdd(CounterAnalysis.UNGROUPED_COUNTER_ASPECTS_ATTRIB);
                    handleCounterAspect(event, ss, counterAspect, rootQuark);
                }
            }
        }
    }

    /**
     * Add the field value of a grouped counter aspect to the state system (override
     * in specific implementations)
     *
     * @param event
     *            Event to process
     * @param ss
     *            State system object to fill
     * @param aspect
     *            Grouped counter aspect
     * @param rootQuark
     *            Key to a relative root of the state system
     */
    protected void handleGroupedCounterAspect(ITmfEvent event, ITmfStateSystemBuilder ss, CounterAspect aspect, int rootQuark) {
        /*
         * Retrieve the child quark of the counter aspect by going through its attribute
         * tree in the state system. The concatenation of the aspect's groups form a
         * single entry in the state system.
         */
        int quark = rootQuark;
        for (Class<? extends ITmfEventAspect<?>> groupClass : aspect.getGroups()) {
            ITmfEventAspect<?> parentAspect = fGroupingAspectImpls.get(groupClass);
            if (parentAspect != null) {

                // Avoid handling when the parent aspect resolves to null
                Object parentAspectContent = parentAspect.resolve(event);
                if (parentAspectContent != null) {
                    quark = ss.getQuarkRelativeAndAdd(quark, parentAspect.getName());
                    quark = ss.getQuarkRelativeAndAdd(quark, String.valueOf(parentAspectContent));
                } else {
                    return;
                }
            }
        }

        handleCounterAspect(event, ss, aspect, quark);
    }

    private static void handleCounterAspect(ITmfEvent event, ITmfStateSystemBuilder ss, CounterAspect aspect, int rootQuark) {
        int quark = ss.getQuarkRelativeAndAdd(rootQuark, aspect.getName());
        Long eventContent = aspect.resolve(event);
        if (eventContent != null) {
            if (!aspect.isCumulative()) {
                try {
                    StateSystemBuilderUtils.incrementAttributeLong(ss, event.getTimestamp().toNanos(), quark, eventContent);
                } catch (StateValueTypeException e) {
                    TraceCompassLogUtils.traceInstant(LOGGER, Level.WARNING, "HandleCounterAspect:Exception", e); //$NON-NLS-1$
                }
            } else {
                ss.modifyAttribute(event.getTimestamp().toNanos(), eventContent, quark);
            }
        }
    }
}