/*
 * JBoss, Home of Professional Open Source
 * Copyright 2016, Red Hat, Inc. and/or its affiliates,
 * and individual contributors as indicated by the @author tags.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *
 * (C) 2016,
 * @author JBoss, by Red Hat.
 */
package org.jboss.byteman.contrib.dtest;

import java.io.File;
import java.io.FileNotFoundException;
import java.rmi.RemoteException;
import java.util.Scanner;
import java.util.regex.Pattern;

import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class InstrumentorTest {
    private static Instrumentor instrumentor;
    private static final String lineSeparator = System.getProperty("line.separator");

    private static final Class<?> clazz = InstrumentorTest.class;
    private static final String clazzName = clazz.getName();
    private static final String method = "method";

    @Rule
    public TemporaryFolder tmpDir = new TemporaryFolder();

    @BeforeClass
    public static void beforeClass() throws RemoteException {
        instrumentor = new Instrumentor();
    }

    @Before
    public void startUp() throws Exception {
        instrumentor.removeLocalState();
        RuleConstructor.undefineDefaultInstrumentor();
    }

    @Test
    public void ruleCrashAtMethodExit() throws Exception {
        File ruleFile = tmpDir.newFile("crashAtMethodExit.btm");
        instrumentor.setRedirectedSubmissionsFile(ruleFile);

        instrumentor.crashAtMethodExit(clazzName, method);

        String ruleString = readFileToString(ruleFile);

        Pattern pattern = new RegexRuleBuilder()
            .clazz(clazzName)
            .method(method)
            .atExit()
            .ifTrue()
            .doo("killJVM")
            .build();

        Assert.assertTrue("Pattern\n" + pattern.pattern() + "\ndoes not match rule\n" + ruleString,
                pattern.matcher(ruleString).matches());
    }

    @Test
    public void ruleCrashAtInterfaceMethodEntry() throws Exception {
        File ruleFile = tmpDir.newFile("crashAtInterfaceMethodEntry.btm");
        instrumentor.setRedirectedSubmissionsFile(ruleFile);

        instrumentor.crashAtMethodEntry(InstrumentorTestingInterface.class, method);

        String ruleString = readFileToString(ruleFile);

        Pattern pattern = new RegexRuleBuilder()
                .interfaze(InstrumentorTestingInterface.class.getSimpleName())
                .method(method)
                .atEntry()
                .ifTrue()
                .doo("killJVM")
                .build();

        Assert.assertTrue("Pattern\n" + pattern.pattern() + "\ndoes not match rule\n" + ruleString,
                pattern.matcher(ruleString).matches());
    }

    @Test
    public void ruleCrashAtMethodEntry() throws Exception {
        File ruleFile = tmpDir.newFile("crashAtMethodEntry.btm");
        instrumentor.setRedirectedSubmissionsFile(ruleFile);

        instrumentor.crashAtMethodEntry(clazzName, method);

        String ruleString = readFileToString(ruleFile);

        Pattern pattern = new RegexRuleBuilder()
                .clazz(clazzName)
                .method(method)
                .atEntry()
                .ifTrue()
                .doo("killJVM")
                .build();

        Assert.assertTrue("Pattern\n" + pattern.pattern() + "\ndoes not match rule\n" + ruleString,
                pattern.matcher(ruleString).matches());
    }

    @Test
    public void ruleCrashAtMethod() throws Exception {
        File ruleFile = tmpDir.newFile("crashAtMethod.btm");
        instrumentor.setRedirectedSubmissionsFile(ruleFile);

        String atLine = "LINE 123";
        instrumentor.crashAtMethod(clazzName, method, atLine);

        String ruleString = readFileToString(ruleFile);

        Pattern pattern = new RegexRuleBuilder()
                .clazz(clazzName)
                .method(method)
                .at(atLine)
                .ifTrue()
                .doo("killJVM")
                .build();

        Assert.assertTrue("Pattern\n" + pattern.pattern() + "\ndoes not match rule\n" + ruleString,
                pattern.matcher(ruleString).matches());
    }

    @Test
    public void ruleInjectOnCall() throws Exception {
        File ruleFile = tmpDir.newFile("injectOnCall.btm");
        instrumentor.setRedirectedSubmissionsFile(ruleFile);

        String action = "debug(\"here\");";
        instrumentor.injectOnCall(clazz, method, action);

        String ruleString = readFileToString(ruleFile);

        Pattern pattern = new RegexRuleBuilder()
                .clazz(clazzName)
                .method(method)
                .atEntry()
                .ifTrue()
                .doo(action)
                .build();

        Assert.assertTrue("Pattern\n" + pattern.pattern() + "\ndoes not match rule\n" + ruleString,
                pattern.matcher(ruleString).matches());
    }

    @Test
    public void ruleInjectOnExit() throws Exception {
        File ruleFile = tmpDir.newFile("injectOnExit.btm");
        instrumentor.setRedirectedSubmissionsFile(ruleFile);

        String action = "debug(\"here\");";
        instrumentor.injectOnExit(clazz, method, action);

        String ruleString = readFileToString(ruleFile);

        Pattern pattern = new RegexRuleBuilder()
                .clazz(clazzName)
                .method(method)
                .atExit()
                .ifTrue()
                .doo(action)
                .build();

        Assert.assertTrue("Pattern\n" + pattern.pattern() + "\ndoes not match rule\n" + ruleString,
                pattern.matcher(ruleString).matches());
    }

    @Test
    public void ruleInjectOnMethod() throws Exception {
        File ruleFile = tmpDir.newFile("injectOnMethod.btm");
        instrumentor.setRedirectedSubmissionsFile(ruleFile);

        String at = "AFTER READ i";
        String action = "debug(\"here\");";
        String iff = "recovered";
        instrumentor.injectOnMethod(clazz, method, iff, action, at);

        String ruleString = readFileToString(ruleFile);

        Pattern pattern = new RegexRuleBuilder()
                .clazz(clazzName)
                .method(method)
                .at(at)
                .iff(iff)
                .doo(action)
                .build();

        Assert.assertTrue("Pattern\n" + pattern.pattern() + "\ndoes not match rule\n" + ruleString,
                pattern.matcher(ruleString).matches());
    }

    @Test
    public void ruleInjectFault() throws Exception {
        File ruleFile = tmpDir.newFile("injectFault.btm");
        instrumentor.setRedirectedSubmissionsFile(ruleFile);

        Class<NullPointerException> exception = NullPointerException.class;
        Object[] args = {"hello"};
        instrumentor.injectFault(clazz, method, exception, args);

        String ruleString = readFileToString(ruleFile);

        Pattern pattern = new RegexRuleBuilder()
                .clazz(clazzName)
                .method(method)
                .atEntry()
                .ifTrue()
                .doo("throw new " + exception.getName() + "(\"" + args[0])
                .build();

        Assert.assertTrue("Pattern\n" + pattern.pattern() + "\ndoes not match rule\n" + ruleString,
                pattern.matcher(ruleString).matches());
    }

    @Test
    public void ruleInstall() throws Exception {
        File ruleFile = tmpDir.newFile("installRule.btm");
        instrumentor.setRedirectedSubmissionsFile(ruleFile);

        instrumentor.installRule(RuleConstructor.createRule("install rule")
            .onClass(clazz).inMethod(method).atEntry().helper(BytemanTestHelper.class).ifTrue()
            .doAction("throw new " + NullPointerException.class.getName() + "(\"hello\")"));

        verifyRuleInstall(ruleFile);
    }

    @Test
    public void ruleInstallDefaultInstrumentor() throws Exception {
        File ruleFile = tmpDir.newFile("installRule.btm");
        instrumentor.setRedirectedSubmissionsFile(ruleFile);

        RuleConstructor.setDefaultInstrumentor(instrumentor);
        RuleConstructor.createRule("install rule")
            .onClass(clazz)
            .inMethod(method)
            .helper(BytemanTestHelper.class)
            .atEntry()
            .ifTrue()
            .doAction("throw new " + NullPointerException.class.getName() + "(\"hello\")")
            .install();

        verifyRuleInstall(ruleFile);
    }

    private void verifyRuleInstall(File ruleFile) throws FileNotFoundException {
        String ruleString = readFileToString(ruleFile);

        Pattern pattern = new RegexRuleBuilder()
                .clazz(clazzName)
                .method(method)
                .ifTrue()
                .atEntry()
                .doo("throw new " + NullPointerException.class.getName() + "(\"hello")
                .build();

        Assert.assertTrue("Pattern\n" + pattern.pattern() + "\ndoes not match rule\n" + ruleString,
                pattern.matcher(ruleString).matches());
    }

    private String readFileToString(File file) throws FileNotFoundException {
        Scanner in = null;
        try {
            in = new Scanner(file);

            StringBuffer buffer = new StringBuffer();
            while (in.hasNext()) {
                buffer.append(in.nextLine() + lineSeparator);
            }
            return buffer.toString();
        } finally {
            in.close();
        }
    }

    private static class RegexRuleBuilder {
        private String clazz, interfaze, method, at, iff, doo;

        public RegexRuleBuilder clazz(String clazz) {
            this.clazz = clazz;
            return this;
        }
        public RegexRuleBuilder interfaze(String interfaze) {
            this.interfaze = interfaze;
            return this;
        }
        public RegexRuleBuilder method(String method) {
            this.method = method;
            return this;
        }
        public RegexRuleBuilder at(String at) {
            this.at = at;
            return this;
        }
        public RegexRuleBuilder atEntry() {
            this.at = "ENTRY";
            return this;
        }
        public RegexRuleBuilder atExit() {
            this.at = "EXIT";
            return this;
        }
        public RegexRuleBuilder iff(String iff) {
            this.iff = iff;
            return this;
        }
        public RegexRuleBuilder ifTrue() {
            this.iff = "true";
            return this;
        }
        public RegexRuleBuilder doo(String doo) {
            this.doo = doo;
            return this;
        }

        private String cleanForRegexp(String str) {
            return str
                    .replaceAll("\\.", "\\\\.")
                    .replaceAll("\\(", "\\\\(")
                    .replaceAll("\\)", "\\\\)");
        }

        public Pattern build() {
            String regex = String.format(
                "^RULE.*" +
                "%s%s.*" +
                "METHOD%s.*" +
                "%s.*" + // AT
                "HELPER " + BytemanTestHelper.class.getName() + ".*" +
                "%s.*" + // IF
                "%s.*" + // DO
                "ENDRULE.*",
                clazz == null ? "" : "CLASS.*" + cleanForRegexp(clazz),
                interfaze == null ? "" : "INTERFACE.*" + cleanForRegexp(interfaze),
                method == null ? "" : ".*" + cleanForRegexp(method),
                at == null ? "" : "AT.*" + at,
                iff == null ? "" : "IF.*" + iff,
                        doo == null ? "" : "DO.*" + cleanForRegexp(doo));
            return Pattern.compile(regex, Pattern.DOTALL);
        }
    }
}
