class SCM::Git::Branch < SCM::Git::CommandProxyBase
  module BranchHelperMethods
    def shorten(name)
      name && name.gsub(/refs\/(heads|remotes)\//, "")
    end
    
    def format_name(name, format = :short)
      (format == :short || format == nil) ? shorten(name) : name
    end
  end
  
  include BranchHelperMethods
  
  def [](name)
    SCM::Git::Branch::BranchProxy.new(@base, self, name)
  end
  
  def create_and_switch(name)
    base.command("checkout", "-b", name)
  end
  
  def create(name, options = {})
    args = ["branch"]
    args << "--track" if options[:autotrack]
    args << name
    case
    when ! options[:source].blank?
      args << options[:source]
    when ! options[:tag].blank?
      args << options[:tag]
    when ! options[:revision].blank?
      args << options[:revision]
    end
    base.command(*args)
  end
  
  def switch(name)
    result = base.command("checkout", name)
    result
  end
  
  def all_for_local_or_remote(which, options = {})
    list(which, options).map do |branch_params| 
      BranchProxy.new(@base, self, branch_params)
    end
  end
  
  def all(which = :both, options = {})
    branches = []
    which = [:local, :remote] if which == :both
    [which].flatten.each do |side|
      branches.concat all_for_local_or_remote(side, options)
    end
    
    branches.compact
  end
  
  def list(which = :local, options= {})
    params = []
    case which
    when :local then params << "refs/heads"
    when :remote then params << "refs/remotes"
    end
    result = base.command("for-each-ref", *params).split("\n").map do |e|
      next unless /^([a-f0-9]{40})\s*commit\s*(.+)$/.match(e)
      {:ref => $1, :name => $2}
    end.compact
    
    if options[:remote]
      r_prefix = @base.remote[options[:remote]].remote_branch_prefix
      result.delete_if { |r| r[:name][0..(r_prefix.length-1)] != r_prefix }
    end
    result
  end
  
  def list_names(which = :local, options = {})
    list(which, options).map do |b|
      format_name(b[:name], options[:format])
    end
  end
  
  alias names list_names
  
  def current
    _current_name = current_name(:long)
    list(:local).each do |branch_params|
      return BranchProxy.new(@base, self, branch_params) if branch_params[:name] == _current_name
    end
    
    nil
  end
  
  def current_name(format = :short)
    return unless /^ref: (.+)$/.match(File.read(@base.path_for(".git/HEAD")))
    format_name($1, format)
  rescue
    nil
  end
  
  def delete(name, options = {})
    branch_type = options[:branch_type] || (name.include?("/") ? :remote : :local)
    case branch_type.to_sym
    when :remote then delete_remote(name, options)
    when :local then delete_local(name, options)
    else
      raise "Unknown branch type: #{branch_type}"
    end
  end
  
  def delete_local(name, options = {})
    
    output = base.command("branch", options[:force] ? "-D" : "-d", name).strip
    outcome = case output
      when /^Deleted branch #{name}/
        :success
      when /error.+(not a strict subset|not an ancestor of your current HEAD)/
        :unmerged
      else
        :unknown
      end
    {:outcome => outcome, :output => output, :branch => name }
  end
  
  def delete_remote(name, options = {})
    remote, branch = name.split("/")
    output = base.command("push", remote, ":#{branch}")
    outcome = case output
    when /refs\/heads\/#{branch}: .+\-\> deleted/, /\[deleted\]/
      :success
    when /error: dst refspec .+ does not match any existing ref on the remote and does not start with refs\/./
      :branch_not_found
    else
      :unknown
    end
    { :outcome => outcome, :output => output, :remote => remote, :branch => branch }
  end
  
  def compare_status(left, right)
    new_commits = Hash.new(0)
    result = @base.command("rev-list", "--left-right", "#{left}...#{right}").split("\n").each do |line|
      case line[0..0]
      when "<"
        new_commits[:left] += 1
      when ">"
        new_commits[:right] += 1
      end
    end
    
    case
    when new_commits[:left] == 0 && new_commits[:right] == 0
      :same
    when new_commits[:left] == 0 && new_commits[:right] >= 0
      :behind
    when new_commits[:left] >= 0 && new_commits[:right] == 0
      :ahead
    else
      :diverged
    end
  end
  
  class BranchProxy
    include BranchHelperMethods
    attr_reader :ref
    
    def initialize(base, parent, options = {})
      @base = base
      @parent = parent
      @name = options[:name]
      @ref = options[:ref]
    end
  
    def name(format = :short)
      format_name(@name, format)
    end
    
    def local?
      @local ||= (@name[0..10] == "refs/heads/")
    end
  
    def current?
      @parent.current_name(:long) == name(:long)
    end
  
    def remote?
      ! @local
    end
  
    def default?
      raise "implement me"
    end
    
    def remote(reload = false)
      remote_name(reload) && @base.remote[remote_name]
    end
    
    def remote_name(reload = false)
      @remote_name = nil if reload
      @remote_name ||= @base.config["branch.#{name}.remote"]
    end
  
    def remote_name=(value)
      @remote_name, @tracking_branch_name = nil
      @base.config["branch.#{name}.remote"] = value
    end
  
    def merge(reload = false)
      @merge = nil if (reload)
      @merge ||= @base.config["branch.#{name}.merge"]
    end
  
    def merge=(value)
      @merge, @tracking_branch_name = nil
      @base.config["branch.#{name}.merge"] = value
    end
    
    def tracking_branch_name(format = :short)
      @tracking_branch_name ||= (remote && merge && remote.remote_branch_name_for(merge, :long))
      format_name(@tracking_branch_name, format)
    end
    
    # tell if a branch 
    def tracking_status
      return unless tracking_branch_name(:long)
      @parent.compare_status(name(:long), tracking_branch_name(:long))
    end
  end
end

