require 'rugged'

module Ginatra
  class Repo
    attr_reader :name, :param, :description

    # Create a new repository, and sort out clever stuff including assigning
    # the param, the name and the description.
    #
    # @param [String] path a path to the repository you want created
    # @return [Ginatra::Repo] a repository instance
    def initialize(path)
      @repo = Rugged::Repository.new(path)
      @param = File.split(path).last
      @name = @param
      @description = ''
      if File.exists?("#{@repo.path}description")
        @description = File.read("#{@repo.path}description").strip
        @description = '' if @description.match(/\AUnnamed repository;/)
      end
    end

    # Return a commit corresponding to sha in the repo.
    #
    # @param [String] sha the commit id or tag name
    # @return [Rugged::Commit] the commit object
    def commit(sha)
      @repo.lookup(sha)
    end

    # Return a commit corresponding to tag in the repo.
    def commit_by_tag(name)
      target = @repo.ref("refs/tags/#{name}").target

      if target.is_a? Rugged::Tag::Annotation
        target = target.target
      end

      target
    end

    # Return a list of commits in a certain branch, including pagination options and all the refs.
    #
    # @param [String] start the branch to look for commits in
    # @param [Integer] max_count the maximum count of commits
    # @param [Integer] skip the number of commits in the branch to skip before taking the count.
    #
    # @return [Array<Rugged::Commit>] the array of commits.
    def commits(branch='master', max_count=10, skip=0)
      raise Ginatra::InvalidRef unless branch_exists?(branch)

      walker = Rugged::Walker.new(@repo)
      walker.sorting(Rugged::SORT_TOPO)
      walker.push(@repo.ref("refs/heads/#{branch}").target)

      commits = walker.collect {|commit| commit }
      commits[skip, max_count]
    end

    # Returns list of branches sorted by name alphabetically
    def branches
      @repo.branches.each(:local).sort_by {|b| b.name }
    end

    # Returns list of branches containing the commit
    def branches_with(commit)
      b = []
      branches.each do |branch|
        walker = Rugged::Walker.new(@repo)
        walker.sorting(Rugged::SORT_TOPO)
        walker.push(@repo.ref("refs/heads/#{branch.name}").target)
        walker.collect { |c| b << branch if c.oid == commit }
      end
      b
    end

    # Checks existence of branch by name
    def branch_exists?(branch_name)
      @repo.branches.exists?(branch_name)
    end

    # Find blob by oid
    def find_blob(oid)
      Rugged::Blob.new @repo, oid
    end

    # Find tree by tree oid or branch name
    def find_tree(oid)
      if branch_exists?(oid)
        last_commit_sha = @repo.ref("refs/heads/#{oid}").target.oid
        lookup(last_commit_sha).tree
      else
        lookup(oid)
      end
    end

    # Returns Rugged::Repository instance
    def to_rugged
      @repo
    end

    # Catch all
    #
    # @todo update respond_to? method
    def method_missing(sym, *args, &block)
      if @repo.respond_to?(sym)
        @repo.send(sym, *args, &block)
      else
        super
      end
    end

    # to correspond to the #method_missing definition
    def respond_to?(sym)
      @repo.respond_to?(sym) || super
    end
  end
end
