/*******************************************************************************
 * Copyright 2018 Dynamic Analysis Group, Università della Svizzera Italiana (USI)
 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
 *
 * 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 ch.usi.inf.nodeprof.handlers;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.ListIterator;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;

import ch.usi.inf.nodeprof.ProfiledTagEnum;

public class MultiEventHandler extends BaseSingleTagEventHandler {

    @Children final BaseEventHandlerNode[] handlers;
    @CompilationFinal boolean noChildHandlerUpdate = true;

    /**
     *
     * @param handlers should be of the same kind T
     */
    protected MultiEventHandler(ProfiledTagEnum tag, BaseEventHandlerNode[] handlers) {
        super(handlers[0].context, tag);
        this.handlers = handlers.clone();

        // sort handlers by their priority
        Arrays.sort(this.handlers, Comparator.comparingInt(ch.usi.inf.nodeprof.handlers.BaseEventHandlerNode::getPriority));
    }

    public static MultiEventHandler create(ProfiledTagEnum tag, BaseEventHandlerNode[] handlers) {
        assert (handlers != null && handlers.length > 0);
        return new MultiEventHandler(tag, handlers);
    }

    @Override
    @ExplodeLoop
    public void executePre(VirtualFrame frame, Object[] inputs) throws Exception {
        for (BaseEventHandlerNode handler : handlers) {
            handler.executePre(frame, inputs);
            noChildHandlerUpdate = noChildHandlerUpdate && (handler.wantsToUpdateHandler() == handler);
        }
    }

    @Override
    public BaseEventHandlerNode wantsToUpdateHandler() {
        if (noChildHandlerUpdate) {
            return this;
        }
        return wantsToUpdateHandlerSlow();
    }

    // TODO: could optimize into single loop and annotate with ExplodeLoop?
    @TruffleBoundary
    private BaseEventHandlerNode wantsToUpdateHandlerSlow() {

        ArrayList<BaseEventHandlerNode> newHandlers = new ArrayList<>(Arrays.asList(handlers));

        // 1st iteration: remove deactivated handlers
        boolean modified = newHandlers.removeIf((BaseEventHandlerNode h) -> h.wantsToUpdateHandler() == null);

        // 2nd iteration: replace updated handlers
        ListIterator<BaseEventHandlerNode> iter = newHandlers.listIterator();
        while (iter.hasNext()) {
            BaseEventHandlerNode cur = iter.next();
            BaseEventHandlerNode replacement = cur.wantsToUpdateHandler();
            if (cur != replacement) {
                iter.set(replacement);
                modified = true;
            }
        }

        assert modified : "noChildHandlerUpdate has lied to us";

        if (newHandlers.size() > 1) {
            // return new MultiEventHandler
            CompilerDirectives.transferToInterpreterAndInvalidate();
            return new MultiEventHandler(this.tag, newHandlers.toArray(new BaseEventHandlerNode[0]));
        } else if (newHandlers.size() == 1) {
            // optimize to SingleEventHandler
            return newHandlers.get(0);
        } else {
            // remove ourselves
            return null;
        }
    }

    @Override
    @ExplodeLoop
    public void executePost(VirtualFrame frame, Object result, Object[] inputs) throws Exception {
        for (BaseEventHandlerNode handler : handlers) {
            handler.executePost(frame, result, inputs);
            noChildHandlerUpdate = noChildHandlerUpdate && (handler.wantsToUpdateHandler() == handler);
        }
    }

    @ExplodeLoop
    @Override
    public void executeExceptional(VirtualFrame frame, Throwable exception) throws Exception {
        for (BaseEventHandlerNode handler : handlers) {
            handler.executeExceptional(frame, exception);
        }
    }

    @Override
    @ExplodeLoop
    public void executeExceptionalCtrlFlow(VirtualFrame frame, Throwable exception, Object[] inputs) throws Exception {
        for (BaseEventHandlerNode handler : handlers) {
            handler.executeExceptionalCtrlFlow(frame, exception, inputs);
        }
    }

    @ExplodeLoop
    @Override
    public Object onUnwind(VirtualFrame frame, Object info) {
        Object res = null;
        for (BaseEventHandlerNode handler : handlers) {
            res = handler.onUnwind(frame, info);
            if (res != null) {
                return res;
            }
        }
        return null;
    }
}