/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.netbeans.nbbuild;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.URL;
import java.security.Permission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.Manifest;
import junit.framework.AssertionFailedError;
import org.netbeans.junit.NbTestCase;

/**
 * Predefines commonly used utilities.
 */
abstract class TestBase extends NbTestCase {

    protected TestBase(String name) {
        super(name);
    }

    protected @Override void setUp() throws Exception {
        clearWorkDir();
    }

    // XXX look for other copy-pasted utility methods, like createNewJarFile

    protected final String readFile(java.io.File f) throws java.io.IOException {
        int s = (int) f.length();
        byte[] data = new byte[s];
        assertEquals("Read all data", s, new java.io.FileInputStream(f).read(data));

        return new String(data);
    }

    protected final Manifest createManifest() {
        Manifest m = new Manifest();
        m.getMainAttributes().putValue(java.util.jar.Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
        return m;
    }

    protected final File extractString(String res) throws Exception {
        File f = File.createTempFile("res", ".xml", getWorkDir());

        try (FileOutputStream os = new FileOutputStream(f)) {
            InputStream is = new ByteArrayInputStream(res.getBytes("UTF-8"));
            for (;;) {
                int ch = is.read();
                if (ch == -1) {
                    break;
                }
                os.write(ch);
            }
        }

        return f;
    }

    protected final File extractResource(String res) throws Exception {
        File f = File.createTempFile("res", ".xml", getWorkDir());
        extractResource(f, res);
        return f;
    }

    protected final void extractResource(File f, String res) throws Exception {
        URL u = getClass().getResource(res);
        assertNotNull("Resource should be found " + res, u);


        try (FileOutputStream os = new FileOutputStream(f)) {
            InputStream is = u.openStream();
            for (;;) {
                int ch = is.read();
                if (ch == -1) {
                    break;
                }
                os.write(ch);
            }
        }
    }

    protected final void execute(String res, String... args) throws Exception {
        execute(extractResource(res), args);
    }

    private static ByteArrayOutputStream out, err;

    protected final String getStdOut() {
        return out.toString();
    }

    protected final String getStdErr() {
        return err.toString();
    }

    protected final void execute(File f, String[] args) throws Exception {
        // we need security manager to prevent System.exit
        if (!(System.getSecurityManager() instanceof MySecMan)) {
            out = new java.io.ByteArrayOutputStream();
            err = new java.io.ByteArrayOutputStream();
            System.setOut(new java.io.PrintStream(out));
            System.setErr(new java.io.PrintStream(err));

            System.setSecurityManager(new MySecMan());
        }

        MySecMan sec = (MySecMan) System.getSecurityManager();

        // Jesse claims that this is not the right way how the execution
        // of an ant script should be invoked:
        //
        // better IMHO to just run the task directly
        // (setProject() and similar, configure its bean properties, and call
        // execute()), or just make a new Project and initialize it.
        // ant.Main.main is not intended for embedded use. Then you could get rid
        // of the SecurityManager stuff, would be cleaner I think.
        //
        // If I had to write this once again, I would try to follow the
        // "just make a new Project and initialize it", but as this works
        // for me now, I leave it for the time when somebody really
        // needs that...

        List<String> arr = new ArrayList<>();
        arr.add("-f");
        arr.add(f.toString());
        arr.addAll(Arrays.asList(args));


        out.reset();
        err.reset();

        try {
            sec.setActive(true);
            org.apache.tools.ant.Main.main(arr.toArray(new String[0]));
        } catch (MySecExc ex) {
            assertNotNull("The only one to throw security exception is MySecMan and should set exitCode", sec.exitCode);
            ExecutionError.assertExitCode(
                    "Execution has to finish without problems",
                    sec.exitCode.intValue());
        } finally {
            sec.setActive(false);
        }
    }

    protected static final class ExecutionError extends AssertionFailedError {
        public final int exitCode;
        public ExecutionError(String msg, int e) {
            super(msg);
            this.exitCode = e;
        }
        private static void assertExitCode(String msg, int e) {
            if (e != 0) {
                throw new ExecutionError(
                        msg + " was: " + e + "\nOutput: " + out.toString()
                        + "\nError: " + err.toString(),
                        e);
            }
        }
    }

    private static class MySecExc extends SecurityException {
        @Override
        public void printStackTrace() {
        }
        @Override
        public void printStackTrace(PrintStream ps) {
        }
        @Override
        public void printStackTrace(PrintWriter ps) {
        }
    }

    private static class MySecMan extends SecurityManager {
        public Integer exitCode;
        private boolean active;
        @Override
        public void checkExit(int status) {
            if (active) {
                exitCode = new Integer(status);
                throw new MySecExc();
            }
        }
        @Override
        public void checkPermission(Permission perm, Object context) {
        }
        @Override
        public void checkPermission(Permission perm) {
            /*
            if (perm instanceof RuntimePermission) {
            if (perm.getName ().equals ("setIO")) {
            throw new MySecExc ();
            }
            }
             */
        }
        @Override
        public void checkMulticast(InetAddress maddr) {
        }
        @Override
        public void checkAccess(ThreadGroup g) {
        }
        @Override
        public void checkWrite(String file) {
        }
        @Override
        public void checkLink(String lib) {
        }
        @Override
        public void checkExec(String cmd) {
        }
        @Override
        public void checkDelete(String file) {
        }
        @Override
        public void checkPackageAccess(String pkg) {
        }
        @Override
        public void checkPackageDefinition(String pkg) {
        }
        @Override
        public void checkPropertyAccess(String key) {
        }
        @Override
        public void checkRead(String file) {
        }
        @Override
        public void checkSecurityAccess(String target) {
        }
        @Override
        public void checkWrite(FileDescriptor fd) {
        }
        @Override
        public void checkListen(int port) {
        }
        @Override
        public void checkRead(FileDescriptor fd) {
        }
        @SuppressWarnings("deprecation")
        @Override
        public void checkMulticast(InetAddress maddr, byte ttl) {
        }
        @Override
        public void checkAccess(Thread t) {
        }
        @Override
        public void checkConnect(String host, int port, Object context) {
        }
        @Override
        public void checkRead(String file, Object context) {
        }
        @Override
        public void checkConnect(String host, int port) {
        }
        @Override
        public void checkAccept(String host, int port) {
        }
        @Override
        public void checkMemberAccess(Class<?> clazz, int which) {
        }
        @Override
        public void checkSystemClipboardAccess() {
        }
        @Override
        public void checkSetFactory() {
        }
        @Override
        public void checkCreateClassLoader() {
        }
        @Override
        public void checkAwtEventQueueAccess() {
        }
        @Override
        public void checkPrintJobAccess() {
        }
        @Override
        public void checkPropertiesAccess() {
        }
        void setActive(boolean b) {
            active = b;
        }
    } // end of MySecMan

}
