/* Copyright (c) 2011 TOPP - www.openplans.org. All rights reserved.
 * This code is licensed under the LGPL 2.1 license, available at the root
 * application directory.
 */
package org.geogit.api.porcelain;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

import javax.annotation.Nullable;

import org.geogit.api.AbstractGeoGitOp;
import org.geogit.api.ObjectId;
import org.geogit.api.Ref;
import org.geogit.api.RevObject.TYPE;
import org.geogit.api.plumbing.RefParse;
import org.geogit.api.plumbing.ResolveObjectType;
import org.geogit.api.plumbing.RevParse;
import org.geogit.api.plumbing.UpdateRef;

import com.google.common.base.Optional;
import com.google.inject.Inject;

/**
 * Creates a new head ref (branch) pointing to the specified tree-ish or the current HEAD if no
 * tree-ish was specified.
 * <p>
 * 
 * @TODO: support branch descriptions
 * @TODO: support setting up the branch to track a remote branch
 */
public class BranchCreateOp extends AbstractGeoGitOp<Ref> {

    private String branchName;

    private String commit_ish;

    private boolean checkout;

    @Inject
    public BranchCreateOp() {
    }

    /**
     * @param branchName the name of the branch to create, must not already exist
     */
    public BranchCreateOp setName(final String branchName) {
        this.branchName = branchName;
        return this;
    }

    /**
     * @param commit_ish either a branch ref or commit id where this branch starts at. If not set
     *        defaults to the current {@link Ref#HEAD HEAD}
     */
    public BranchCreateOp setSource(@Nullable String commit_ish) {
        this.commit_ish = commit_ish;
        return this;
    }

    /**
     * @param checkout if {@code true}, in addition to creating the new branch, a {@link CheckoutOp
     *        checkout} operation will be performed against the newly created branch. If the check
     *        out failed for any reason the {@link CheckoutException} will be propagated back to the
     *        caller, although the branch is guaranteed to be created and could be retrieved through
     *        a {@link RefParse ref-parse} op.
     */
    public BranchCreateOp setAutoCheckout(boolean checkout) {
        this.checkout = checkout;
        return this;
    }

    public Ref call() {
        checkState(branchName != null, "branch name was not provided");
        final String branchRefPath = Ref.HEADS_PREFIX + branchName;
        checkArgument(!command(RefParse.class).setName(branchRefPath).call().isPresent(),
                "A branch named '" + branchName + "' already exists.");

        final String branchOrigin = Optional.fromNullable(commit_ish).or(Ref.HEAD);

        final ObjectId branchOriginCommitId = resolveOriginCommitId(branchOrigin);

        Optional<Ref> branchRef = command(UpdateRef.class).setName(branchRefPath)
                .setNewValue(branchOriginCommitId).call();
        checkState(branchRef.isPresent());

        if (checkout) {
            command(CheckoutOp.class).setSource(branchRefPath).call();
        }
        return branchRef.get();
    }

    private ObjectId resolveOriginCommitId(String branchOrigin) {
        Optional<Ref> ref = command(RefParse.class).setName(branchOrigin).call();
        if (ref.isPresent()) {
            ObjectId commitId = ref.get().getObjectId();
            checkArgument(!commitId.isNull(), branchOrigin
                    + " has no commits yet, branch cannot be created.");
            return commitId;
        }
        Optional<ObjectId> objectId = command(RevParse.class).setRefSpec(branchOrigin).call();
        checkArgument(objectId.isPresent(), branchOrigin
                + " does not resolve to a repository object");

        ObjectId commitId = objectId.get();
        TYPE objectType = command(ResolveObjectType.class).setObjectId(commitId).call();
        checkArgument(TYPE.COMMIT.equals(objectType), branchOrigin
                + " does not resolve to a commit: " + objectType);

        return commitId;
    }
}

@@ -1154,16 +1154,45 @@
 ckout;%0A%0A
+    private boolean orphan;%0A%0A
     @Inj
@@ -1756,32 +1756,292 @@
 rn this;%0A    %7D%0A%0A
+    /**%0A     * @param orphan %7B@code true%7D if the new branch shares no history with the current one, defaults%0A     *        to %7B@code false%7D%0A     */%0A    public BranchCreateOp setOrphan(boolean orphan) %7B%0A        this.orphan = orphan;%0A        return this;%0A    %7D%0A%0A
     /**%0A     * @
@@ -2935,24 +2935,228 @@
 exists.%22);%0A%0A
+        Optional%3CRef%3E branchRef;%0A        if (orphan) %7B%0A            branchRef = command(UpdateRef.class).setName(branchRefPath).setNewValue(ObjectId.NULL)%0A                    .call();%0A        %7D else %7B%0A    
         fina
@@ -3224,24 +3224,28 @@
 Ref.HEAD);%0A%0A
+    
         fina
@@ -3320,37 +3320,27 @@
 );%0A%0A        
-Optional%3CRef%3E
+   
  branchRef =
@@ -3404,16 +3404,20 @@
         
+   
+ 
 .setNewV
@@ -3451,32 +3451,36 @@
 call();%0A        
+    
 checkState(branc
@@ -3497,16 +3497,26 @@
 sent());
+%0A        %7D
 %0A%0A      

add a boolean orphan argument to BranchCreateOp to create a new orphan branch