# ~*~ encoding: utf-8 ~*~
require 'pathname'

module Gollum
  class File
    Wiki.file_class = self

    # Public: Initialize a file.
    #
    # wiki - The Gollum::Wiki in question.
    #
    # Returns a newly initialized Gollum::File.
    def initialize(wiki)
      @wiki         = wiki
      @blob         = nil
      @path         = nil
      @on_disk      = false
      @on_disk_path = nil
    end

    # Public: The url path required to reach this page within the repo.
    #
    # Returns the String url_path
    def url_path
      path = self.path
      path = path.sub(/\/[^\/]+$/, '/') if path.include?('/')
      path
    end

    # Public: The url_path, but CGI escaped.
    #
    # Returns the String url_path
    def escaped_url_path
      CGI.escape(self.url_path).gsub('%2F', '/')
    end

    # Public: The on-disk filename of the file.
    #
    # Returns the String name.
    def name
      return @path if on_disk?
      @blob && @blob.name
    end

    alias filename name

    # Public: The raw contents of the page.
    #
    # Returns the String data.
    def raw_data
      return IO.read(@on_disk_path) if on_disk?
      return nil unless @blob

      if !@wiki.repo.bare && @blob.is_symlink
        new_path = @blob.symlink_target(::File.join(@wiki.repo.path, '..', self.path))
        return IO.read(new_path) if new_path
      end

      @blob.data
    end

    # Public: Is this an on-disk file reference?
    #
    # Returns true if this is a pointer to an on-disk file
    def on_disk?
      @on_disk
    end

    # Public: The path to this file on disk
    #
    # Returns nil if on_disk? is false.
    def on_disk_path
      @on_disk_path
    end

    # Public: The Gollum::Git::Commit version of the file.
    attr_accessor :version

    # Public: The String path of the file.
    attr_reader :path

    # Public: The String mime type of the file.
    def mime_type
      @blob && @blob.mime_type
    end

    # Populate the File with information from the Blob.
    #
    # blob - The Gollum::Git::Blob that contains the info.
    # path - The String directory path of the file.
    #
    # Returns the populated Gollum::File.
    def populate(blob, path = nil)
      @blob         = blob
      @path         = "#{path}/#{blob.name}"[1..-1]
      @on_disk      = false
      @on_disk_path = nil
      self
    end

    #########################################################################
    #
    # Internal Methods
    #
    #########################################################################

    # Return the file path to this file on disk, if available.
    #
    # Returns nil if the file isn't available on disk. This can occur if the
    # repo is bare, if the commit isn't the HEAD, or if there are problems
    # resolving symbolic links.
    def get_disk_reference(name, commit)
      return false if @wiki.repo.bare
      return false if commit.sha != @wiki.repo.head.commit.sha

      # This will try to resolve symbolic links, as well
      pathname = Pathname.new(::File.expand_path(::File.join(@wiki.repo.path, '..', name)))
      if pathname.symlink?
        source   = ::File.readlink(pathname.to_path)
        realpath = ::File.join(::File.dirname(pathname.to_path), source)
        return false unless realpath && ::File.exist?(realpath)
        @on_disk_path = realpath.to_s
      else
        @on_disk_path = pathname.to_path
      end
      true
    end

    # Find a file in the given Gollum repo.
    #
    # name    - The full String path.
    # version - The String version ID to find.
    # try_on_disk - If true, try to return just a reference to a file
    #               that exists on the disk.
    #
    # Returns a Gollum::File or nil if the file could not be found. Note
    # that if you specify try_on_disk=true, you may or may not get a file
    # for which on_disk? is actually true.
    def find(name, version, try_on_disk = false)
      checked = name.downcase
      map     = @wiki.tree_map_for(version)
      commit  = version.is_a?(Gollum::Git::Commit) ? version : @wiki.commit_for(version)

      if (result = map.detect { |entry| entry.path.downcase == checked })
        @path    = name
        @version = commit

        if try_on_disk && get_disk_reference(name, commit)
          @on_disk = true
        else
          @blob = result.blob(@wiki.repo)
        end

        self
      end
    end
  end
end
