module Gitlab
  module Git
    class Blob
      include Linguist::BlobHelper
      include EncodingHelper

      attr_accessor :name, :path, :size, :data, :mode, :id, :commit_id

      class << self
        def find(repository, sha, path)
          commit = repository.lookup(sha)
          root_tree = commit.tree

          blob_entry = find_entry_by_path(repository, root_tree.oid, path)

          return nil unless blob_entry

          if blob_entry[:type] == :commit
            submodule_blob(blob_entry, path, sha)
          else
            blob = repository.lookup(blob_entry[:oid])

            if blob
              Blob.new(
                id: blob.oid,
                name: blob_entry[:name],
                size: blob.size,
                data: blob.content,
                mode: blob_entry[:filemode].to_s(8),
                path: path,
                commit_id: sha,
              )
            end
          end
        end

        def raw(repository, sha)
          blob = repository.lookup(sha)

          Blob.new(
            id: blob.oid,
            size: blob.size,
            data: blob.content,
          )
        end

        # Recursive search of blob id by path
        #
        # Ex.
        #   blog/            # oid: 1a
        #     app/           # oid: 2a
        #       models/      # oid: 3a
        #       file.rb      # oid: 4a
        #
        #
        # Blob.find_entry_by_path(repo, '1a', 'app/file.rb') # => '4a'
        #
        def find_entry_by_path(repository, root_id, path)
          root_tree = repository.lookup(root_id)
          path_arr = path.split('/')

          entry = root_tree.find do |entry|
            entry[:name] == path_arr[0]
          end

          return nil unless entry

          if path_arr.size > 1
            return nil unless entry[:type] == :tree
            path_arr.shift
            find_entry_by_path(repository, entry[:oid], path_arr.join('/'))
          else
            [:blob, :commit].include?(entry[:type]) ? entry : nil
          end
        end

        def submodule_blob(blob_entry, path, sha)
          Blob.new(
            id: blob_entry[:oid],
            name: blob_entry[:name],
            data: '',
            path: path,
            commit_id: sha,
          )
        end

        # Commit file in repository and return commit sha
        #
        # options should contain next structure:
        #   file: {
        #     content: 'Lorem ipsum...',
        #     path: 'documents/story.txt'
        #   },
        #   author: {
        #     email: 'user@example.com',
        #     name: 'Test User',
        #     time: Time.now
        #   },
        #   committer: {
        #     email: 'user@example.com',
        #     name: 'Test User',
        #     time: Time.now
        #   },
        #   commit: {
        #     message: 'Wow such commit',
        #     branch: 'master'
        #   }
        #
        def commit(repository, options)
          file = options[:file]
          author = options[:author]
          committer = options[:committer]
          commit = options[:commit]
          repo = repository.rugged

          oid = repo.write(file[:content], :blob)
          index = repo.index
          index.read_tree(repo.head.target.tree) unless repo.empty?
          index.add(path: file[:path], oid: oid, mode: 0100644)

          opts = {}
          opts[:tree] = index.write_tree(repo)
          opts[:author] = author
          opts[:committer] = committer
          opts[:message] = commit[:message]
          opts[:parents] = repo.empty? ? [] : [ repo.head.target ].compact
          opts[:update_ref] = 'refs/heads/' + commit[:branch]

          Rugged::Commit.create(repo, opts)
        end
      end

      def initialize(options)
        %w(id name path size data mode commit_id).each do |key|
          self.send("#{key}=", options[key.to_sym])
        end
      end

      def empty?
        !data || data == ''
      end

      def data
        encode! @data
      end

      def name
        encode! @name
      end
    end
  end
end