require 'digest/md5'

module Ginatra
  # Helpers used in the views, and not only.
  module Helpers
    # Escapes string to HTML entities
    def h(text)
      Rack::Utils.escape_html(text)
    end

    # Checks X-PJAX header
    def is_pjax?
      request.env['HTTP_X_PJAX']
    end

    # Repository stats for a given repo
    def repo_stats(repo)
      ref = params[:ref] || params[:tag] || params[:tree] || 'master'
      @stats ||= Ginatra::RepoStats.new(repo, ref)
    end

    # Sets title for pages
    def title(*args)
      @title ||= []
      @title_options ||= { headline: nil, sitename: h(Ginatra.config.sitename) }
      options = args.last.is_a?(Hash) ? args.pop : {}

      @title += args
      @title_options.merge!(options)

      t = @title.clone
      t << @title_options[:headline]
      t << @title_options[:sitename]
      t.compact.join ' - '
    end

    # Constructs the URL used in the layout's base tag
    def prefix_url(rest_of_url='')
      prefix = Ginatra.config.prefix.to_s

      if prefix.length > 0 && prefix[-1].chr == '/'
        prefix.chop!
      end

      "#{prefix}/#{rest_of_url}"
    end

    # Returns hint to set repository description
    def empty_description_hint_for(repo)
      return '' unless repo.description.empty?
      hint_text = "Edit `#{repo.path}description` file to set the repository description."
      "<img src='/img/exclamation-circle.svg' title='#{hint_text}' alt='hint' class='icon'>"
    end

    # Returns file icon depending on filemode
    def file_icon(filemode)
      case filemode
        # symbolic link (120000)
        when 40960 then "<img src='/img/mail-forward.svg' alt='symbolic link' class='icon'>"
        # executable file (100755)
        when 33261 then "<img src='/img/asterisk.svg' alt='executable file' class='icon'>"
        else "<img src='/img/file.svg' alt='file' class='icon'>"
      end
    end

    # Masks original email
    def secure_mail(email)
      local, domain = email.split('@')
      "#{local[0..3]}...@#{domain}"
    end

    # Takes an email and returns an image tag with gravatar
    #
    # @param [String] email the email address
    # @param [Hash] options alt, class and size options for image tag
    # @return [String] html image tag
    def gravatar_image_tag(email, options={})
      alt = options.fetch(:alt, email.gsub(/@\S*/, ''))
      size = options.fetch(:size, 40)
      url = "https://secure.gravatar.com/avatar/#{Digest::MD5.hexdigest(email)}?s=#{size}"

      if options[:lazy]
        placeholder = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
        tag = "<img data-original='#{url}' src='#{placeholder}' alt='#{h alt}' height='#{size}' width='#{size}' class='js-lazy #{options[:class]}'>"
      else
        tag = "<img src='#{url}' alt='#{h alt}' height='#{size}' width='#{size}'#{" class='#{options[:class]}'" if options[:class]}>"
      end

      tag
    end

    # Reformats the date into a user friendly date with html entities
    #
    # @param [#strftime] date object to format nicely
    # @return [String] html string formatted using
    #   +"%b %d, %Y &ndash; %H:%M"+
    def nicetime(date)
      date.strftime("%b %d, %Y &ndash; %H:%M")
    end

    # Returns an html time tag for the given time
    #
    # @param [Time] time object
    # @return [String] time tag formatted using
    #   +"%B %d, %Y %H:%M"+
    def time_tag(time)
      datetime = time.strftime('%Y-%m-%dT%H:%M:%S%z')
      title = time.strftime('%Y-%m-%d %H:%M:%S')
      "<time datetime='#{datetime}' title='#{title}'>#{time.strftime('%B %d, %Y %H:%M')}</time>"
    end

    # Returns a string including the link to download a patch for a certain
    # commit to the repo
    #
    # @param [Rugged::Commit] commit the commit you want a patch for
    # @param [String] repo_param the url-sanitised name for the repo
    #   (for the link path)
    #
    # @return [String] the HTML link to the patch
    def patch_link(commit, repo_param)
      patch_url = prefix_url("#{repo_param}/commit/#{commit.oid}.patch")
      "<a href='#{patch_url}'>Download Patch</a>"
    end

    # Spits out a HTML link to the atom feed for a given ref of a given repo
    #
    # @param [Sting] repo_param the url-sanitised-name of a given repo
    # @param [String] ref the ref to link to.
    #
    # @return [String] the HTML containing the link to the feed.
    def atom_feed_url(repo_param, ref=nil)
      ref.nil? ? prefix_url("#{repo_param}.atom") : prefix_url("#{repo_param}/#{ref}.atom")
    end

    # Returns a HTML (+<ul>+) list of the files modified in a given commit.
    #
    # It includes classes for added/modified/deleted and also anchor links
    # to the diffs for further down the page.
    #
    # @param [Rugged::Commit] commit the commit you want the list of files for
    #
    # @return [String] a +<ul>+ with lots of +<li>+ children.
    def file_listing(diff)
      list = []
      diff.deltas.each_with_index do |delta, index|
        if delta.deleted?
          list << "<li class='deleted'><img src='/img/minus-square.svg' alt='deleted' class='icon'> <a href='#file-#{index + 1}'>#{delta.new_file[:path]}</a></li>"
        elsif delta.added?
          list << "<li class='added'><img src='/img/plus-square.svg' alt='added' class='icon'> <a href='#file-#{index + 1}'>#{delta.new_file[:path]}</a></li>"
        elsif delta.modified?
          list << "<li class='changed'><img src='/img/edit.svg' alt='modified' class='icon'> <a href='#file-#{index + 1}'>#{delta.new_file[:path]}</a></li>"
        end
      end
      "<ul class='list-unstyled'>#{list.join}</ul>"
    end

    # Highlights commit diff
    #
    # @param [Rugged::Hunk] diff hunk for highlighting
    #
    # @return [String] highlighted HTML.code
    def highlight_diff(hunk)
      lines = []
      lines << hunk.header

      hunk.each_line do |line|
        if line.context?
          lines << "  #{line.content}"
        elsif line.deletion?
          lines << "- #{line.content}"
        elsif line.addition?
          lines << "+ #{line.content}"
        end
      end

      formatter = Rouge::Formatters::HTML.new
      lexer     = Rouge::Lexers::Diff.new

      source   = lines.join
      encoding = source.encoding
      source   = source.force_encoding(Encoding::UTF_8)

      hd = formatter.format lexer.lex(source)
      hd.force_encoding encoding
    end

    # Highlights blob source
    #
    # @param [Rugged::Blob] blob to highlight source
    #
    # @return [String] highlighted HTML.code
    def highlight_source(source, filename='')
      source    = source.force_encoding(Encoding::UTF_8)
      formatter = Rouge::Formatters::HTML.new(line_numbers: true, wrap: false)
      lexer     = Rouge::Lexer.guess_by_filename(filename)

      if lexer == Rouge::Lexers::PlainText
        lexer = Rouge::Lexer.guess_by_source(source) || Rouge::Lexers::PlainText
      end

      formatter.format lexer.lex(source)
    end

    # Formats the text to remove multiple spaces and newlines, and then inserts
    # HTML linebreaks.
    #
    # Borrowed from Rails: ActionView::Helpers::TextHelper#simple_format
    # and simplified to just use <p> tags without any options, then modified
    # more later.
    #
    # @param [String] text the text you want formatted
    #
    # @return [String] the formatted text
    def simple_format(text)
      text.gsub!(/ +/, " ")
      text.gsub!(/\r\n?/, "\n")
      text.gsub!(/\n/, "<br />\n")
      text
    end

    # Truncates a given text to a certain number of letters, including a special ending if needed.
    #
    # @param [String] text the text to truncate
    # @option options [Integer] :length   (30) the length you want the output string
    # @option options [String]  :omission ("...") the string to show an omission.
    #
    # @return [String] the truncated text.
    def truncate(text, options={})
      options[:length] ||= 30
      options[:omission] ||= "..."

      if text
        l = options[:length] - options[:omission].length
        chars = text
        stop = options[:separator] ? (chars.rindex(options[:separator], l) || l) : l
        (chars.length > options[:length] ? chars[0...stop] + options[:omission] : text).to_s
      end
    end

    # Returns the rfc representation of a date, for use in the atom feeds.
    #
    # @param [DateTime] datetime the date to format
    # @return [String] the formatted datetime
    def rfc_date(datetime)
      datetime.strftime("%Y-%m-%dT%H:%M:%SZ") # 2003-12-13T18:30:02Z
    end

    # Returns the Hostname of the given install, for use in the atom feeds.
    #
    # @return [String] the hostname of the server. Respects HTTP-X-Forwarded-For
    def hostname
      (request.env['HTTP_X_FORWARDED_SERVER'] =~ /[a-z]*/) ? request.env['HTTP_X_FORWARDED_SERVER'] : request.env['HTTP_HOST']
    end
  end
end
