module Grit

  class Status
    include Enumerable

    attr_reader :files

    @base = nil
    @files = nil

    def initialize(base)
      @base = base
      construct_status
    end

    def changed
      @files.select { |k, f| f.type == 'M' }
    end

    def added
      @files.select { |k, f| f.type == 'A' }
    end

    def deleted
      @files.select { |k, f| f.type == 'D' }
    end

    def untracked
      @files.select { |k, f| f.untracked }
    end

    def pretty
      out = ''
      self.each do |file|
        out << file.path
        out << "\n\tsha(r) " + file.sha_repo.to_s + ' ' + file.mode_repo.to_s
        out << "\n\tsha(i) " + file.sha_index.to_s + ' ' + file.mode_index.to_s
        out << "\n\ttype   " + file.type.to_s
        out << "\n\tstage  " + file.stage.to_s
        out << "\n\tuntrac " + file.untracked.to_s
        out << "\n"
      end
      out << "\n"
      out
    end

    # enumerable method

    def [](file)
      @files[file]
    end

    def each
      @files.each do |k, file|
        yield file
      end
    end

    class StatusFile
      attr_accessor :path, :type, :stage, :untracked
      attr_accessor :mode_index, :mode_repo
      attr_accessor :sha_index, :sha_repo

      @base = nil

      def initialize(base, hash)
        @base = base
        @path = hash[:path]
        @type = hash[:type]
        @stage = hash[:stage]
        @mode_index = hash[:mode_index]
        @mode_repo = hash[:mode_repo]
        @sha_index = hash[:sha_index]
        @sha_repo = hash[:sha_repo]
        @untracked = hash[:untracked]
      end

      def blob(type = :index)
        if type == :repo
          @base.object(@sha_repo)
        else
          @base.object(@sha_index) rescue @base.object(@sha_repo)
        end
      end

    end

    private

      def construct_status
        @files = ls_files

        # find untracked in working dir
        Dir.glob(File.join(@base.working_dir, '**/*')) do |full_file|
          file = full_file.gsub "#{@base.working_dir}/", ""
          if !@files[file]
            @files[file] = {:path => file, :untracked => true} if !File.directory?(file)
          end
        end

        # find modified in tree
        diff_files.each do |path, data|
          @files[path] ? @files[path].merge!(data) : @files[path] = data
        end

        # find added but not committed - new files
        diff_index('HEAD').each do |path, data|
          @files[path] ? @files[path].merge!(data) : @files[path] = data
        end

        @files.each do |k, file_hash|
          @files[k] = StatusFile.new(@base, file_hash)
        end
      end

      # compares the index and the working directory
      def diff_files
        hsh = {}
        @base.git.diff_files({}).split("\n").each do |line|
          (info, file) = line.split("\t")
          (mode_src, mode_dest, sha_src, sha_dest, type) = info.split
          hsh[file] = {:path => file, :mode_file => mode_src.to_s[1, 7], :mode_index => mode_dest,
                        :sha_file => sha_src, :sha_index => sha_dest, :type => type}
        end
        hsh
      end

      # compares the index and the repository
      def diff_index(treeish)
        hsh = {}
        @base.git.diff_index({}, treeish).split("\n").each do |line|
          (info, file) = line.split("\t")
          (mode_src, mode_dest, sha_src, sha_dest, type) = info.split
          hsh[file] = {:path => file, :mode_repo => mode_src.to_s[1, 7], :mode_index => mode_dest,
                        :sha_repo => sha_src, :sha_index => sha_dest, :type => type}
        end
        hsh
      end

      def ls_files
        hsh = {}
        lines = @base.git.ls_files({:stage => true})
        lines.split("\n").each do |line|
          (info, file) = line.split("\t")
          (mode, sha, stage) = info.split
          hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
        end
        hsh
      end
  end

end
