package info.knacki.pass.git;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import info.knacki.pass.git.entities.GitCommit;
import info.knacki.pass.git.entities.GitObject;
import info.knacki.pass.git.entities.GitPackable;
import info.knacki.pass.git.entities.GitPackableUtil;
import info.knacki.pass.io.OutputStreamWithCheckSum;

class Pacman {
    private static final Logger log = Logger.getLogger(Pacman.class.getName());
    final Collection<GitPackable> fPacked;
    final Map<GitPackable.eType, Collection<GitPackable>> fPackedByType;

    public Pacman(Collection<GitPackable> objectsToPack) {
        fPacked = objectsToPack;
        fPackedByType = new HashMap<>();
        for (GitPackable i: fPacked) {
            Collection<GitPackable> col = fPackedByType.get(i.GetPackableType());
            if (col == null) {
                col = new ArrayDeque<>();
                fPackedByType.put(i.GetPackableType(), col);
            }
            col.add(i);
        }
    }

    private GitObject.GitTree FindTree(byte[] hash) {
        Collection<GitPackable> col = fPackedByType.get(GitPackable.eType.eType_Tree);
        if (col != null)
            for (GitPackable i: col)
                if (GitSha1.CompareChecksum(hash, GitSha1.getRawSha1OfPackable(i)))
                    return (GitObject.GitTree) i;
        return null;
    }

    private GitObject.GitRawData FindBlob(byte[] hash) {
        Collection<GitPackable> col = fPackedByType.get(GitPackable.eType.eType_Blob);
        if (col != null)
            for (GitPackable i: col)
                if (GitSha1.CompareChecksum(hash, GitSha1.getRawSha1OfPackable(i)))
                    return (GitObject.GitRawData) i;
        return null;
    }

    private void BuildTree(GitObject.GitTree root) throws PacmanBuilder.InvalidPackException {
        GitObject.GitTree nextUninitialized = root;
        do {
            if (!nextUninitialized.IsRoot()) {
                GitObject.GitTree realTree = FindTree(nextUninitialized.GetHash());
                if (realTree == null)
                    throw new PacmanBuilder.InvalidPackException("Cannot find object in pack");
                realTree = new GitObject.GitTree(nextUninitialized.GetParent(), realTree, nextUninitialized.GetFilename());
                nextUninitialized.GetParent().SetRealObject(nextUninitialized.GetFilename(), realTree);
                nextUninitialized = realTree;
            }
            for (GitObject.GitBlob i: nextUninitialized.GetBlobs()) {
                GitObject.GitRawData blob = FindBlob(i.GetHash());
                if (blob == null)
                    throw new PacmanBuilder.InvalidPackException("Cannot find object in pack");
                nextUninitialized.SetRealObject(i.GetFilename(), new GitObject.GitRawData(nextUninitialized, i, blob));
            }
        } while ((nextUninitialized = root.FindNextNotInitializedTree()) != null);
    }

    private GitCommit BuildCommit(GitCommit ci) throws PacmanBuilder.InvalidPackException {
        GitObject.GitTree tree = ci.GetTree();
        if (tree == null) {
            tree = FindTree(ci.GetTreeHash());
            if (tree != null)
                ci.SetTree(tree);
        }
        if (tree != null)
            BuildTree(tree);
        return ci;
    }

    public GitCommit BuildCommit(String hash) throws PacmanBuilder.InvalidPackException {
        Collection<GitPackable> col = fPackedByType.get(GitPackable.eType.eType_Commit);
        if (col != null)
            for (GitPackable i: col)
                if (hash.equals(((GitCommit) i).GetHash()))
                    return BuildCommit((GitCommit) i);
        return null;
    }

    public boolean Write(OutputStream out) throws IOException
    {
        OutputStreamWithCheckSum msg = new OutputStreamWithCheckSum(out);
        msg.write("PACK")
                .write(new byte[] { 0, 0, 0, 2 } )
                .write(ByteBuffer.allocate(4).putInt(fPacked.size()).array());
        for (GitPackable i: fPacked) {
            byte[] pack = i.GetPack();
            if (null == pack)
                return false;
            GitPackable.PackV2Header header = new GitPackable.PackV2Header(i.GetPackableType(), pack.length);
            msg.write(GitPackableUtil.getObjHeader(header)).write(GitPackableUtil.deflate(pack));
            log.info("Writing pack " +i.GetPackableType() +", " +GitSha1.getSha1OfPackable(i) +new String(i.GetPack()));
        }
        msg.writeSha1();
        return true;
    }
}
