require 'git/browse/remote/git'
require 'pathname'
require 'uri'

module Git::Browse::Remote
  class Path < Array
    def to_s
      join('/')
    end
  end

  class Filepath < Pathname
    def to_s
      Pathname.new(Git.show_prefix).join(super).cleanpath.to_s
    end
  end

  class Core
    attr_accessor :lines, :file

    MAPPING_RECIPES = {
      :github => {
        :top  => 'https://{host}/{path}',
        :ref  => 'https://{host}/{path}/tree/{short_ref}',
        :rev  => 'https://{host}/{path}/commit/{commit}',
        :pr   => 'https://{host}/{path}/pull/{short_ref}',
        :file => 'https://{host}/{path}/{file.directory? and :tree or :blob}/{short_rev}/{file}{lines && "#L%s" % lines.join("-")}',
      },

      :gitweb => {
        :top => 'http://{host}/?p={path[-2..-1]}.git',
        :ref => 'http://{host}/?p={path[-2..-1]}.git;h={ref}',
        :rev => 'http://{host}/?p={path[-2..-1]}.git;a=commit;h={ref}',
        # XXX
        # I don't know file url of gitweb...
      }
    }

    def template_type
      if @file
        :file
      else
        mode
      end
    end

    def init!(host, name)
      mapping = MAPPING_RECIPES[name] or abort "Recipe '#{name}' not found"
      mapping.each do |mode,template|
        system %Q(git config --global browse-remote.#{host}.#{mode} '#{template}')
      end
    end

    def url
      if target && !@file && File.exists?(target)
        self.target, @file = nil, target
      end

      if @file && File.exists?(@file)
        @file = Filepath.new(@file)
      end

      if target
        if Git.is_valid_rev? target
          @ref = Git.full_name_of_rev(target)
        elsif Git.is_valid_remote? target
          @remote, @ref = target, 'master'
        else
          abort "Not a valid ref or remote: #{target}"
        end
      else
        @ref = Git.symbolic_name_of_head
      end

      if @ref == 'HEAD'
        @ref = nil
      end

      remote_url = `git config remote.#{remote}.url`[/.+/] or
          abort "Could not get remote url: #{remote}"

      unless %r(^\w+://) === remote_url
        # SCP-like URLs of form "[user@]host.xz:path/to/repo.git"
        # will be converted to "ssh://[user@]host.xz/path/to/repo.git"
        remote_url = 'ssh://' + remote_url.sub(/:/, '/')
      end

      # Normal URLs
      u = URI(remote_url)
      host = u.host
      port = u.port
      host_port = if port == u.default_port then host else "#{host}:#{port}" end
      path = u.path.sub(%r(^/), '').split('/')
      path.last.sub!(/\.git$/, '')
      path = Path.new(path)

      template = `git config --get browse-remote.#{host}.#{template_type}`[/.+/]
      if not template and host == 'github.com'
        template = MAPPING_RECIPES[:github][template_type.to_sym] or
          abort "No '#{template_type}' mapping found for #{host} (maybe `git browse-remote --init` required)"
      end

      url = template.gsub(/\{(.+?)\}/) { |m| eval($1) }
    end

    def remote=(remote)
      @remote = remote
    end

    def remote
      return @remote if @remote

      if ref
        if ref.match(%r<^(?:refs/)?remotes/([^/]+)/>)
          @remote = $1
        end
      end

      @remote ||= 'origin'
    end

    def mode=(mode)
      @mode = mode
    end

    def mode
      return @mode if @mode

      if ref
        if ref.match(%r<^((?:refs/)?(?:heads|remotes/[^/]+)/)?master$>)
          @mode = :top
        elsif ref.match(%r<^(?:refs/)?(?:heads|tags|remotes)/>)
          @mode = :ref
        end
      end

      @mode || :rev
    end

    def _commit(short = false)
      if short
        Git.parse_rev_short(target || 'HEAD')
      else
        Git.parse_rev(target || 'HEAD')
      end
    end

    def commit
      _commit
    end

    def short_commit
      _commit(true)
    end

    def _rev(short = false)
      if mode == :rev
        _commit(short)
      else
        _ref(short) || _commit(short)
      end
    end

    def rev
      _rev
    end

    def short_rev
      _rev(true)
    end

    def _ref(short = false)
      if short
        @ref.sub(%r(^refs/), '').
             sub(%r(^heads/), '').
             sub(%r(^tags/), '').
             sub(%r(^remotes/([^/]+)/), '')
      else
        @ref
      end
    end

    def ref
      _ref
    end

    def short_ref
      _ref(true)
    end

    def line
      lines and lines.first
    end

    def target=(target)
      @target = target
    end

    def target
      return @target if @target
      @target ||= Git.resolved_head
    end
  end
end
