package info.knacki.pass.git;

import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.logging.Level;
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.GitRef;
import info.knacki.pass.io.OnResponseListener;
import info.knacki.pass.io.OnStreamResponseListener;
import info.knacki.pass.settings.SettingsManager;

import static info.knacki.pass.io.NetworkUtils.ProtoPost;
import static info.knacki.pass.io.NetworkUtils.protoGet;
import static info.knacki.pass.io.NetworkUtils.protoInflateGet;

class HttpGitProtocol extends BaseGitProtocol {
    private GitRef[] fRefsCache = null;
    private static final Logger log = Logger.getLogger(HttpGitProtocol.class.getName());

    HttpGitProtocol(SettingsManager.Git config) {
        super(config);
    }

    public void GetRefs(final OnResponseListener<GitRef[]> callback) {
        if (fRefsCache != null) {
            callback.OnResponse(fRefsCache);
            return;
        }
        try {
            URL url = new URL(fConfig.GetUrl() + "/info/REFS");
            protoGet(url, fConfig, new OnResponseListener<String>() {
                @Override
                public void OnResponse(String result) {
                    if (result.length() == 0) {
                        fRefsCache = new GitRef[0];
                    } else {
                        String[] refStrings = result.split("\n");
                        fRefsCache = new GitRef[refStrings.length];
                        for (int i = 0; i < refStrings.length; ++i) {
                            String refLine = refStrings[i];
                            for (int j = 0; j < refLine.length(); ++j) {
                                if (refLine.charAt(j) == '\t') {
                                    fRefsCache[i] = new GitRef(refLine.substring(0, j).trim(), refLine.substring(j + 1).trim());
                                    break;
                                }
                            }
                            if (fRefsCache[i] == null) {
                                fRefsCache = null;
                                callback.OnError("Cannot read references from repository", null);
                                return;
                            }
                        }
                    }
                    callback.OnResponse(fRefsCache);
                }

                @Override
                public void OnError(String msg, Throwable e) {
                    callback.OnError(msg, e);
                }
            });
        } catch (MalformedURLException e) {
            callback.OnError(e.getMessage(), e);
        }
    }

    public void FetchTree(final GitCommit ci, final OnStreamResponseListener<GitObject.GitTree> response) {
        class RecursiveFetchTreeWalker implements OnStreamResponseListener<byte[]> {
            private final GitObject.GitTree fRoot;
            private GitObject.GitTree fCurrentTree;
            private final OnStreamResponseListener<GitObject.GitTree> fResponseListener;

            private RecursiveFetchTreeWalker(GitCommit ci, final OnStreamResponseListener<GitObject.GitTree> responseListener) {
                fRoot = fCurrentTree = ci.InitializeTree().Initialize();
                fResponseListener = responseListener;
            }

            public void run() {
                PullHash(ci.GetTreeHash());
            }

            @Override
            public void OnResponse(byte[] result) {
                fCurrentTree.Fill(result);
                fCurrentTree = fRoot.FindNextNotInitializedTree();
                if (fCurrentTree != null)
                    PullHash(fCurrentTree.Initialize().GetHash());
                else
                    fResponseListener.OnResponse(fRoot);
            }

            @Override
            public void OnError(String msg, Throwable e) {
                fResponseListener.OnError(msg, e);
            }

            @Override
            public void OnMsg(String message) {
                fResponseListener.OnMsg(message);
            }

            private void PullHash(String hash) {
                fResponseListener.OnMsg("Reading tree " +hash);
                HttpGitProtocol.this.PullHash(hash, this);
            }

            private void PullHash(byte[] hash) {
                PullHash(GitSha1.BytesToString(hash));
            }
        }

        new RecursiveFetchTreeWalker(ci, response).run();
    }

    public void FetchBlob(final GitObject.GitBlob blob, final OnStreamResponseListener<byte[]> response) {
        PullHash(GitSha1.BytesToString(blob.GetHash()), response);
    }

    protected void PullHash(String hash, final OnStreamResponseListener<byte[]> response) {
        URL url;
        try {
            url = new URL(fConfig.GetUrl() + "/objects/" + hash.substring(0, 2) + "/" + hash.substring(2));
        }
        catch (MalformedURLException e) {
            response.OnError(e.getClass().getName() +": " +e.getMessage(), e);
            return;
        }
        protoInflateGet(url, fConfig, response);
    }

    public void FetchCommit(final GitRef ref, final OnStreamResponseListener<GitCommit> response) {
        PullHash(ref.GetHash(), new OnStreamResponseListener<byte[]>() {
            @Override
            public void OnResponse(byte[] result) {
                GitCommit ci = new GitCommit(ref.GetHash(), new String(result, Charset.defaultCharset()));
                response.OnMsg("Finished read commit");
                response.OnMsg(ci.GetMessage());
                response.OnMsg("Reading tree #" +GitSha1.BytesToString(ci.GetTreeHash()));
                FetchTree(ci, new OnStreamResponseListener<GitObject.GitTree>() {
                    @Override
                    public void OnMsg(String message) {
                        response.OnMsg(message);
                    }

                    @Override
                    public void OnResponse(GitObject.GitTree tree) {
                        response.OnMsg("Finished reading tree");
                        response.OnResponse(ci);
                    }

                    @Override
                    public void OnError(String msg, Throwable e) {
                        response.OnError(msg, e);
                    }
                });
            }

            @Override
            public void OnError(String msg, Throwable e) {
                response.OnError(msg, e);
            }

            @Override
            public void OnMsg(String message) {
                response.OnMsg(message);
            }
        });
    }

    @Override
    public void PushPack(final GitCommit commit, Pacman pack, String branch, final OnStreamResponseListener<Void> response) {
        GetRefs(new OnResponseListener<GitRef[]>() {
            @Override
            public void OnResponse(final GitRef[] result) {
                final GitRef myRef = GetRefFromBranchName(result, branch);
                if (myRef == null)
                    response.OnMsg("Creating branch " +branch);
                else
                    response.OnMsg("Pushing over " +myRef.GetBranch() +" revision " +myRef.GetHash());
                try {
                    HashMap<String, String> headers = new HashMap<>();
                    headers.put("Content-Type", "application/x-git-receive-pack-request");
                    ProtoPost(new URL(fConfig.GetUrl() +"/git-receive-pack"), headers, fConfig, new OnResponseListener<byte[]>() {
                        @Override
                        public void OnResponse(byte[] result) {
                            response.OnResponse(null);
                        }

                        @Override
                        public void OnError(String msg, Throwable e) {
                            log.log(Level.SEVERE, "git-receive-pack error", e);
                            response.OnError(msg, e);
                        }
                    }, new OnResponseListener<OutputStream>() {
                        @Override
                        public void OnResponse(OutputStream result) {
                            try {
                                String prev = myRef == null ? "0000000000000000000000000000000000000000 " : (myRef.GetHash() +" ");
                                result.write(GitLine(prev +GitSha1.BytesToString(GitSha1.getRawSha1OfPackable(commit)) +" " +branch));
                                result.write(GitLine(null));
                                if (!pack.Write(result)) {
                                    OnError("Pack error", null);
                                    return;
                                }
                                result.close();
                            }
                            catch (IOException e) {
                                log.log(Level.SEVERE, "Cannot git-upload-pack: " +e.getMessage(), e);
                                OnError(e.getMessage(), e);
                            }
                        }

                        @Override
                        public void OnError(String msg, Throwable e) {
                            response.OnError(msg, e);
                        }
                    });
                }
                catch (IOException e) {
                    response.OnError(e.getMessage(), e);
                }
            }

            @Override
            public void OnError(final String msg, final Throwable e) {
                response.OnError(msg, e);
            }
        });
    }

    @Override
    protected void OnBranchCreated() {
        fRefsCache = null;
    }
}
