package ru.yandex.canvas.service.html5;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import ru.yandex.canvas.exceptions.ZipIllegalCharset;

public class Html5Zip {
    private Map<String, byte[]> contents;
    private byte[] sourceArchive;

    public Html5Zip(byte[] archive) throws IOException {
        try {
            fromArchiveWrapped(archive, StandardCharsets.UTF_8);
        } catch (ZipIllegalCharset e) {
            try {
                fromArchiveWrapped(archive, Charset.forName("CP866"));
            } catch (ZipIllegalCharset e2) {
                fromArchiveWrapped(archive, Charset.forName("Latin1"));
            }
        }
    }

    private void fromArchiveWrapped(byte[] archive, Charset charset) throws IOException {
        try {
            fromArchive(archive, charset);
        } catch (IllegalArgumentException e) {
            throw new ZipIllegalCharset();
        }
    }

    private void fromArchive(byte[] archive, Charset charset) throws IOException {
        try (ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(archive), charset)) {
            contents = new HashMap<>();

            while (true) {
                ZipEntry zipEntry = zipInputStream.getNextEntry();

                if (zipEntry == null) {
                    break;
                }

                int bufSize = 8192;

                if (zipEntry.getSize() != -1) {
                    bufSize = (int) Math.min(150 * 1024, zipEntry.getSize());
                }

                try (ByteArrayOutputStream out = new ByteArrayOutputStream(bufSize)) {
                    byte[] buf = new byte[bufSize];

                    if (!zipEntry.isDirectory() && !shouldSkip(zipEntry.getName())) {
                        while (true) {
                            int read = zipInputStream.read(buf);
                            if (read == -1) {
                                break;
                            }
                            out.write(buf, 0, read);
                        }
                        contents.put(zipEntry.getName(), out.toByteArray());
                    }
                }
            }

            sourceArchive = archive;
        }
    }

    private Html5Zip(Map<String, byte[]> contents) {
        this.contents = contents;
    }

    public static Builder builder() {
        return new Builder();
    }

    public Set<String> files() {
        return contents.keySet();
    }

    public byte[] getFileContent(String name) {
        return contents.get(name);
    }

    public String getFileAsUtf8String(String name) {
        return new String(contents.get(name), StandardCharsets.UTF_8);
    }

    public byte[] toArchive() throws IOException {
        if (sourceArchive != null)
            return Arrays.copyOf(sourceArchive, sourceArchive.length);

        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            try (ZipOutputStream zipOut = new ZipOutputStream(out)) {
                for (String file : files()) {
                    ZipEntry entry = new ZipEntry(file);
                    zipOut.putNextEntry(entry);
                    zipOut.write(getFileContent(file));
                    zipOut.closeEntry();
                }
            }
            sourceArchive = out.toByteArray();
        }

        return sourceArchive;
    }

    public static class Builder {
        Map<String, byte[]> contents;

        private Builder() {
            contents = new HashMap<>();
        }

        public Builder addFile(String fileName, byte[] content) {
            contents.put(fileName, content);
            return this;
        }

        public Html5Zip build() {
            return new Html5Zip(contents);
        }
    }

    public boolean shouldSkip(String fileName) {
        if (fileName.contains("__MACOSX")
                || fileName.contains(".DS_Store")
                || fileName.contains("Thumbs.db")) {
            return true;
        }

        return false;
    }
}
