/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package compiler.compilercontrol.share.scenario;

import compiler.compilercontrol.share.method.MethodDescriptor;
import compiler.compilercontrol.share.pool.PoolHelper;
import jdk.test.lib.util.Pair;

import java.lang.reflect.Executable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * An abstract class that builds states by applying
 * commands one after another
 */
public abstract class AbstractCommandBuilder
        implements StateBuilder<CompileCommand> {
    protected static final List<Pair<Executable, Callable<?>>> METHODS
            = new PoolHelper().getAllMethods();
    protected final List<CompileCommand> compileCommands = new ArrayList<>();

    @Override
    public void add(CompileCommand command) {
        compileCommands.add(command);
        CommandStateBuilder.getInstance().add(command);
    }

    @Override
    public Map<Executable, State> getStates() {
        return CommandStateBuilder.getInstance().getStates();
    }

    @Override
    public List<CompileCommand> getCompileCommands() {
        return Collections.unmodifiableList(compileCommands);
    }

    @Override
    public boolean isValid() {
        // -XX:CompileCommand(File) ignores invalid items
        return true;
    }

    /*
     * This is an internal class used to build states for commands given from
     * options and a file. As all commands are added into a single set in
     * CompilerOracle, we need a class that builds states in the same manner
     */
    private static class CommandStateBuilder {
        private static final CommandStateBuilder INSTANCE
                = new CommandStateBuilder();
        private final List<CompileCommand> optionCommands = new ArrayList<>();
        private final List<CompileCommand> fileCommands = new ArrayList<>();

        private CommandStateBuilder() { }

        public static CommandStateBuilder getInstance() {
            return INSTANCE;
        }

        public void add(CompileCommand command) {
            switch (command.type) {
                case OPTION:
                    optionCommands.add(command);
                    break;
                case FILE:
                    fileCommands.add(command);
                    break;
                default:
                    throw new Error("TESTBUG: wrong type: " + command.type);
            }
        }

        public Map<Executable, State> getStates() {
            List<CompileCommand> commandList = new ArrayList<>();
            commandList.addAll(optionCommands);
            commandList.addAll(fileCommands);
            Map<Executable, State> states = new HashMap<>();
            for (Pair<Executable, Callable<?>> pair : METHODS) {
                Executable exec = pair.first;
                State state = getState(commandList, exec);
                states.put(exec, state);
            }
            return states;
        }

        private State getState(List<CompileCommand> commandList,
                               Executable exec) {
            State state = new State();
            MethodDescriptor execDesc = new MethodDescriptor(exec);
            for (CompileCommand compileCommand : commandList) {
                if (compileCommand.isValid()) {
                    // Create a copy without compiler set
                    CompileCommand cc = new CompileCommand(
                            compileCommand.command,
                            compileCommand.methodDescriptor,
                            /* CompileCommand option and file doesn't support
                               compiler setting */
                            null,
                            compileCommand.type);
                    MethodDescriptor md = cc.methodDescriptor;
                    // if executable matches regex then apply the state
                    if (execDesc.getCanonicalString().matches(md.getRegexp())) {
                        if (cc.command == Command.COMPILEONLY
                                && !state.isCompilable()) {
                        /* if the method was already excluded it will not
                           be compilable again */
                        } else {
                            state.apply(cc);
                        }
                    }
                }
            }

            /*
             * Set compilation states for methods that don't match
             * any compileonly command. Such methods should be excluded
             * from compilation
             */
            for (CompileCommand compileCommand : commandList) {
                if (compileCommand.isValid()
                        && (compileCommand.command == Command.COMPILEONLY)) {
                    MethodDescriptor md = compileCommand.methodDescriptor;
                    if (!execDesc.getCanonicalString().matches(md.getRegexp())
                            // if compilation state wasn't set before
                            && (!state.getCompilableOptional(
                                    // no matter C1, C2 or both
                                    Scenario.Compiler.C2).isPresent())) {
                        /* compileonly excludes only methods that haven't been
                           already set to be compilable or excluded */
                        state.setC1Compilable(false);
                        state.setC2Compilable(false);
                    }
                }
            }
            return state;
        }
    }
}
