/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.jboss.as.server.deployment.scanner;

import static org.jboss.as.server.deployment.scanner.ZipCompletionScanner.CENLEN;
import static org.jboss.as.server.deployment.scanner.ZipCompletionScanner.CENSIG;
import static org.jboss.as.server.deployment.scanner.ZipCompletionScanner.CENSIZ;
import static org.jboss.as.server.deployment.scanner.ZipCompletionScanner.CEN_LOC_OFFSET;
import static org.jboss.as.server.deployment.scanner.ZipCompletionScanner.ENDLEN;
import static org.jboss.as.server.deployment.scanner.ZipCompletionScanner.ENDSIG;
import static org.jboss.as.server.deployment.scanner.ZipCompletionScanner.END_CENSTART;
import static org.jboss.as.server.deployment.scanner.ZipCompletionScanner.END_COMMENTLEN;
import static org.jboss.as.server.deployment.scanner.ZipCompletionScanner.EXTSIG;
import static org.jboss.as.server.deployment.scanner.ZipCompletionScanner.LOCLEN;
import static org.jboss.as.server.deployment.scanner.ZipCompletionScanner.LOCSIG;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channel;
import java.nio.channels.FileChannel;
import java.util.HashSet;
import java.util.Set;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.WebArchive;

/**
 * Support utility for tests of auto-deploy.
 *
 * @author Brian Stansberry (c) 2011 Red Hat Inc.
 */
public class AutoDeployTestSupport {

    private static final int EXTLEN = 16;

    private final File tmpDir;
    private final File basicWar;

    private final Set<Channel> channels = new HashSet<Channel>();

    public AutoDeployTestSupport(String testId) {

        File tmp = new File(System.getProperty("java.io.tmpdir"));
        tmpDir = new File(tmp, testId);
        cleanFile(tmpDir);
        tmpDir.mkdirs();
        tmpDir.deleteOnExit();

        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
        URL webxml = tccl.getResource("basic.war/web.xml");
        WebArchive war = ShrinkWrap.create(WebArchive.class, "basic.war");
        URL resource = tccl.getResource("basic.war/index.html");
        if (resource == null)
            throw new IllegalStateException("basic.war/index.html not found");
        war.addAsResource(resource, "index.html");
        war.setWebXML(webxml);

        basicWar = new File(tmpDir, "basic.war");
        basicWar.deleteOnExit();
        war.as(ZipExporter.class).exportTo(basicWar, true);
    }

    public void cleanupChannels() {
        for (Channel ch : channels) {
            try {
                ch.close();
            }
            catch (Exception ignored) {
            }
        }
    }

    public void cleanupFiles() {
        cleanFile(tmpDir);
    }

    public File getTempDir() {
        return tmpDir;
    }

    public File getBasicWar() {
        return basicWar;
    }

    public File getFile(String name) throws IOException {
        File f = new File(tmpDir, name);
        f.deleteOnExit();
        f.createNewFile();
        return f;
    }

    public FileChannel getChannel(File file, boolean read) throws IOException {
        FileChannel ch;
        if (read) {
            ch = new FileInputStream(file).getChannel();
        }
        else {
            ch = new FileOutputStream(file).getChannel();
        }
        channels.add(ch);
        return ch;
    }

    public static ByteBuffer getByteBuffer(int capacity) {
        ByteBuffer b = ByteBuffer.allocate(capacity);
        b.order(ByteOrder.LITTLE_ENDIAN);
        return b;
    }

    public static void putUnsignedShort(ByteBuffer bb, int value, int pos) {
        bb.putShort(pos, (short) (value & 0xffff));
    }

    public static void putUnsignedInt(ByteBuffer bb, long value, int pos) {
        bb.putInt(pos, (int) (value & 0xffffffffL));
    }

    /** Builds a mock zip file according to specifications */
    public void createZip(File file, int leadingBytes, boolean trailingByte, boolean extraLoc, boolean useExt, boolean useZip64) throws IOException {

        FileChannel ch = getChannel(file, false);
        try {
            ch.position(leadingBytes);
            int locPos = leadingBytes + (extraLoc ? (LOCLEN + (useExt ? EXTLEN : 0)) : 0);
            int cenPos = locPos + LOCLEN + (useExt ? EXTLEN : 0);
            int endPos = cenPos + CENLEN;
            if (extraLoc) {
                addLocFile(ch, leadingBytes, useExt);
            }
            addLocFile(ch, locPos, useExt);
            addCenDir(ch, cenPos, locPos);
            addEndRecord(ch, endPos, cenPos, trailingByte, useZip64);
        }
        finally {
            ch.close();
        }
    }

    private void addLocFile(FileChannel ch, int pos, boolean useExt) throws IOException {

        int len = useExt ? LOCLEN + EXTLEN : LOCLEN;
        ByteBuffer bb = getByteBuffer(len);

        putUnsignedInt(bb, LOCSIG, 0);
        if (useExt) {
            putUnsignedInt(bb, EXTSIG, LOCLEN);
        }
//        bb.flip();  // don't flip as we never moved the position
        ch.write(bb, pos);

    }

    private static void cleanFile(File file) {
        if (file.exists()) {
            if (file.isDirectory()) {
                for (File child : file.listFiles()) {
                    cleanFile(child);
                }
            }
            if (!file.delete()) {
                file.deleteOnExit();
            }
        }
    }

    private void addCenDir(FileChannel ch, int cenPos, int locPos) throws IOException {

        ByteBuffer bb = getByteBuffer(CENLEN);

        putUnsignedInt(bb, CENSIG, 0);
        putUnsignedInt(bb, locPos, CEN_LOC_OFFSET);
        putUnsignedInt(bb, 0, CENSIZ);

//        bb.flip();  // don't flip as we never moved the position
        ch.write(bb, cenPos);

    }

    private void addEndRecord(FileChannel ch, int endPos, int cenPos, boolean trailingByte, boolean useZip64) throws IOException {

        ByteBuffer bb = getByteBuffer(ENDLEN + (trailingByte ? 1 : 0));


        putUnsignedInt(bb, ENDSIG, 0);
        putUnsignedInt(bb, useZip64 ? 0xffffffffL : cenPos, END_CENSTART);

        if (trailingByte) {
            putUnsignedShort(bb, 1, END_COMMENTLEN);
        }

//        bb.flip(); // don't flip as we never moved the position
        ch.write(bb, endPos);

    }
}
