module GitClient
  class GitError < StandardError
    attr_reader :git_output

    def initialize(git_output)
      super
      @git_output = git_output
    end

    def conflict?
      git_output.any { |line| line.include? 'CONFLICT' }
    end
  end

  class Repository
    def initialize(repo_path)
      @repo_root = repo_path
      @git_folder = File.join(repo_path, '.git')
      @merge = false
    end

    def initialized?
      File.exist? @git_folder
    end

    def merging?
      return @merge
    end

    def init
      raise GitError.new("The folder #{@repo_root} already contains a git repository") if initialized?
      git 'init'
    end

    def current_branch
      current_branch = git('branch').split("\n").delete_if { |line| line[0] != "*" }
      current_branch.first.gsub("* ", "")
    end

    def add_files_to_index(files=nil)
      files = files ? files : '.'
      files = files.is_a?(Array) ? files : [files]
      files.each do |file|
        git "add \"#{file}\""
      end
    end

    def workdir_clean?
      return status.empty?
    end
    # x index, y working_dir
    #       X          Y     Meaning
    #       -------------------------------------------------
    #                 [MD]   not updated
    #       M        [ MD]   updated in index
    #       A        [ MD]   added to index
    #       D         [ M]   deleted from index
    #       R        [ MD]   renamed in index
    #       C        [ MD]   copied in index
    #       [MARC]           index and work tree matches
    #       [ MARC]     M    work tree changed since index
    #       [ MARC]     D    deleted in work tree
    #       -------------------------------------------------
    #       D           D    unmerged, both deleted
    #       A           U    unmerged, added by us
    #       U           D    unmerged, deleted by them
    #       U           A    unmerged, added by them
    #       D           U    unmerged, deleted by us
    #       A           A    unmerged, both added
    #       U           U    unmerged, both modified
    #       -------------------------------------------------
    #       ?           ?    untracked
    #       !           !    ignored
    #       -------------------------------------------------

    def status
      output = git 'status -s'
      status_hash = Hash.new
      return status_hash if output.length == 0 || merging?

      output.split("\n").each do |entry|
        raw_status = entry[0..1]
        status = case raw_status ##needs to split leaving the freaking spaces on!
                 when / (M|D)/         then :idx_not_up_to_date
                 when /(A|M|R)M/       then :idx_not_up_to_date
                 when /A /             then :idx_added
                 when /M /             then :idx_modified
                 when /D /             then :idx_deleted
                 when /R /             then :idx_renamed
                 when /\?\?/           then :untracked
                 when /(DD|AA|(U|D|A)(U|D|A))/ then :merging
                 else                  :unknown
                 end

        filename = entry.split(' ')[1]
        status_hash[status] ||= []
        status_hash[status] << filename
      end
      status_hash
    end

    def remotes
      remotes = git 'remote -v'
      remotes_hash = Hash.new
      remotes.split("\n").each do |line|
        split = line.split(' ')[0..1]
        remotes_hash[symbolicate(split[0])] = split[1]
      end if remotes
      remotes_hash
    end

    def submodules
      submodules_hash = Hash.new
      return submodules_hash unless File.exist? File.join(@repo_root, '.gitmodules')
      
      submodules = git "config -f .gitmodules --get-regexp '^submodule\..*\.path$'"
      submodules.split("\n").each do |line|
        split = line.split(' ')[0..1]
        submodule_name = split[0][/^submodule.(.*).path$/, 1]
        submodule_path = split[1]
        submodule_remote = (git "config -f .gitmodules --get-all 'submodule.#{submodule_name}.url'").delete "\n" 

        submodules_hash[submodule_path] = submodule_remote
      end 
      submodules_hash
    end

    def submodules_update(conf)
      return if submodules.empty?
      git "submodule init" if(conf[:init])
      git "submodule update --recursive"
    end

    def branch_exist?(branch_name)
      git('branch').split("\n").any? { |line| line.include? branch_name }
    end

    def ensure_branch_exists(branch_name)
      return if branch_exist? branch_name
      raise GitError, "Branch #{branch_name} doesn't exists"
    end

    def ensure_workdir_clean
      return if workdir_clean?
      raise GitError, 'The working directory is not clean'
    end

    # returns the commit hash
    def commit(message)
      output = git "commit -m\"#{message}\""
      output[/\[\w* (\w*)\] .*/,1]
    end

    def switch_branch(branch_name, force_create=true)
      ensure_branch_exists branch_name unless force_create

      git "branch #{branch_name}" if force_create && !branch_exist?(branch_name)
      git "checkout \"#{branch_name}\"" unless current_branch == branch_name
    end

    def merge(from_branch, into_branch=nil, msg=nil)
      ensure_branch_exists from_branch
      ensure_branch_exists into_branch if into_branch

      ensure_workdir_clean
      git "checkout \"#{into_branch}\""

      begin
        merge = true
        msg.nil? ? git("merge --no-ff #{from_branch}"): git("merge --no-ff #{from_branch} -m \"#{msg}\"")
      rescue GitError => e
        git 'merge --abort' if e.conflict?
        merge = false
        raise e
      end
    end

    def save_stash
      git 'stash save'
    end

    def pop_stash
      git 'stash pop'
    end

    def rebase(from_branch, into_branch=nil)
      ensure_branch_exists from_branch
      ensure_branch_exists into_branch if into_branch
      ensure_workdir_clean

      git "checkout \"#{into_branch}\""
      begin
        merge = true
        git "rebase \"#{from_branch}\""
      rescue GitError => e
        git 'rebase --abort' if e.conflict?
        merge = false
        raise e
      end
    end

    private

    # good way of making this more robust:
    # https://github.com/CocoaPods/CocoaPods/blob/master/lib/cocoapods/executable.rb
    # and:
    # https://github.com/CocoaPods/CocoaPods/blob/master/lib/cocoapods/downloader/git.rb
    def git(git_cmd)
      output = nil
      FileUtils.cd(@repo_root) { output = safe_exec("git #{git_cmd}") }
      output
    end

    def safe_exec(cmd)
      cmd_output = `#{cmd} 2>&1 3>&1`
      raise GitError.new(cmd_output) unless $?.success?
      cmd_output
    end

    def symbolicate(string)
      string.gsub(/\s+/, "_").downcase.to_sym
    end
  end
end