/* *****************************************************************************
 * Copyright 2018 Dynamic Analysis Group, Università della Svizzera Italiana (USI)
 * Copyright (c) 2018, 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.test.examples;

import java.util.Map.Entry;

import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.EventContext;
import com.oracle.truffle.api.instrumentation.Instrumenter;
import com.oracle.truffle.api.instrumentation.TruffleInstrument.Env;
import com.oracle.truffle.js.runtime.builtins.JSArray;

import ch.usi.inf.nodeprof.ProfiledTagEnum;
import ch.usi.inf.nodeprof.analysis.AnalysisFactory;
import ch.usi.inf.nodeprof.handlers.BaseEventHandlerNode;
import ch.usi.inf.nodeprof.handlers.ElementWriteEventHandler;
import ch.usi.inf.nodeprof.test.TestableNodeProfAnalysis;
import ch.usi.inf.nodeprof.test.examples.nodes.GetArrayIndexNode;
import ch.usi.inf.nodeprof.test.examples.nodes.GetArraySizeNode;
import ch.usi.inf.nodeprof.test.examples.nodes.GetArraySizeNodeGen;
import ch.usi.inf.nodeprof.test.examples.report.Report;
import ch.usi.inf.nodeprof.test.examples.report.ReportDB;
import ch.usi.inf.nodeprof.test.examples.report.ReportEntryNode;
import ch.usi.inf.nodeprof.test.examples.report.ReportEntryNodeGen;
import ch.usi.inf.nodeprof.test.examples.report.SimpleCounterReport;
import ch.usi.inf.nodeprof.utils.Logger;

public class NonContiguousArray extends TestableNodeProfAnalysis {
    public NonContiguousArray(Instrumenter instrumenter, Env env) {
        super("NonContiguousArray", instrumenter, env);
    }

    private final ReportDB db = new ReportDB();

    @Override
    public void onClear() {
        this.db.clear();
    }

    @Override
    public void printResult() {
        int idx = 0;
        Logger.info("NonContiguousArray analysis finishes.");
        for (Entry<Integer, Report> entry : db.getRecords().entrySet()) {
            Logger.info("NonContiguousArray [" + idx++ + "]" + entry.getValue().report());
        }
    }

    @Override
    public void initCallbacks() {
        this.onCallback(ProfiledTagEnum.ELEMENT_WRITE, new AnalysisFactory<BaseEventHandlerNode>() {
            @Override
            public BaseEventHandlerNode create(EventContext context) {
                return new ElementWriteEventHandler(context) {

                    @Child ReportEntryNode getReport = ReportEntryNodeGen.create(db, new SimpleCounterReport.SimleReportFactory());

                    @Child GetArrayIndexNode toArrayIndex = new GetArrayIndexNode();

                    @Child GetArraySizeNode getArraySize = GetArraySizeNodeGen.create();

                    @Override
                    public void executePre(VirtualFrame frame, Object[] inputs) {
                        if (!JSArray.isJSArray(getReceiver(inputs))) {
                            addDebugEvent("EW_NONARRAY", getSourceIID(), ProfiledTagEnum.ELEMENT_WRITE);
                            return;
                        }

                        Object convertedIndex = toArrayIndex.execute(getProperty(inputs));

                        if (convertedIndex instanceof Long && ((Long) convertedIndex) >= 0) {
                            long idx = (long) convertedIndex;
                            long curSize = getArraySize.executeSize(getReceiver(inputs));

                            addDebugEvent("EW_ARRAY_INT", getSourceIID(), ProfiledTagEnum.ELEMENT_WRITE, idx, curSize);

                            if (idx > curSize) {
                                SimpleCounterReport report = (SimpleCounterReport) getReport.execute(this.getSourceIID());
                                report.incre();
                                addDebugEvent("REPORT", getSourceIID(), ProfiledTagEnum.ELEMENT_WRITE);
                            }
                        } else {
                            addDebugEvent("EW_ARRAY_ELSE", getSourceIID(), ProfiledTagEnum.ELEMENT_WRITE, convertedIndex);
                        }
                    }
                };
            }
        });
    }
}