# ====================================================================
#    Licensed to the Apache Software Foundation (ASF) under one
#    or more contributor license agreements.  See the NOTICE file
#    distributed with this work for additional information
#    regarding copyright ownership.  The ASF licenses this file
#    to you under the Apache License, Version 2.0 (the
#    "License"); you may not use this file except in compliance
#    with the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing,
#    software distributed under the License is distributed on an
#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#    KIND, either express or implied.  See the License for the
#    specific language governing permissions and limitations
#    under the License.
# ====================================================================

require "English"
require "tempfile"
require "svn/error"
require "svn/util"
require "svn/delta"
require "svn/ext/fs"

module Svn
  module Fs
    Util.set_constants(Ext::Fs, self)
    Util.set_methods(Ext::Fs, self)

    @@fs_pool = Svn::Core::Pool.new
    initialize(@@fs_pool)

    class << self
      def modules
        print_modules("")
      end
    end

    @@alias_targets = %w(create delete open hotcopy recover)
    class << self
      @@alias_targets.each do |target|
        alias_method "_#{target}", target
      end
    end
    @@alias_targets.each do |target|
      alias_method "_#{target}", target
    end
    @@alias_targets = nil

    module_function
    def create(path, config={}, &block)
      _create(path, config, &block)
    end

    def delete(path)
      Fs.delete_fs(path)
    end

    def open(path, config={}, &block)
      _open(path, config, &block)
    end

    def hotcopy(src, dest, clean=false)
      _hotcopy(src, dest, clean)
    end

    def recover(path, &cancel_func)
      _recover(path, cancel_func)
    end

    FileSystem = SWIG::TYPE_p_svn_fs_t
    class FileSystem
      class << self
        # For backward compatibility
        def create(*args, &block)
          Fs.create(*args, &block)
        end

        def delete(*args, &block)
          Fs.delete(*args, &block)
        end

        def open(*args, &block)
          Fs.open(*args, &block)
        end
        alias new open

        def hotcopy(*args, &block)
          Fs.hotcopy(*args, &block)
        end

        def recover(*args, &block)
          Fs.recover(*args, &block)
        end
      end

      def set_warning_func(&func)
        Fs.set_warning_func_wrapper(self, func)
      end

      def path
        Fs.path(self)
      end

      def open_txn(name)
        Fs.open_txn(self, name)
      end

      def purge_txn(id)
        Fs.purge_txn(self, id)
      end

      def transaction(rev=nil, flags=0)
        txn = Fs.begin_txn2(self, rev || youngest_rev, flags)

        if block_given?
          yield(txn)
          txn.commit if transactions.include?(txn.name)
        else
          txn
        end
      end

      def youngest_revision
        Fs.youngest_rev(self)
      end
      alias_method :youngest_rev, :youngest_revision

      def deleted_revision(path, start_rev, end_rev)
        Repos.deleted_rev(self, path, start_rev, end_rev)
      end
      alias_method :deleted_rev, :deleted_revision

      def prop(name, rev=nil)
        value = Fs.revision_prop(self, rev || youngest_rev, name)
        if value and name == Svn::Core::PROP_REVISION_DATE
          Time.parse_svn_format(value)
        else
          value
        end
      end

      def set_prop(name, value, rev=nil)
        Fs.change_rev_prop(self, rev || youngest_rev, name, value)
      end

      def proplist(rev=nil)
        list = Fs.revision_proplist(self, rev || youngest_rev)
        date_str = list[Svn::Core::PROP_REVISION_DATE]
        if date_str
          list[Svn::Core::PROP_REVISION_DATE] = Time.parse_svn_format(date_str)
        end
        list
      end

      def transactions
        Fs.list_transactions(self)
      end

      def root(rev=nil)
        Fs.revision_root(self, rev || youngest_rev)
      end

      def access
        Fs.get_access(self)
      end

      def access=(new_access)
        Fs.set_access(self, new_access)
      end

      def deltify_revision(rev=nil)
        Fs.deltify_revision(self, rev || youngest_rev)
      end

      def uuid
        Fs.get_uuid(self)
      end

      def uuid=(new_uuid)
        Fs.set_uuid(self, new_uuid)
      end

      def lock(path, token=nil, comment=nil, dav_comment=true,
               expiration_date=nil, current_rev=nil, steal_lock=false)
        if expiration_date
          expiration_date = expiration_date.to_apr_time
        else
          expiration_date = 0
        end
        current_rev ||= youngest_rev
        Fs.lock(self, path, token, comment, dav_comment,
                expiration_date, current_rev, steal_lock)
      end

      def unlock(path, token, break_lock=false)
        Fs.unlock(self, path, token, break_lock)
      end

      def generate_lock_token
        Fs.generate_lock_token(self)
      end

      def get_lock(path)
        Fs.get_lock(self, path)
      end

      def get_locks(path)
        locks = []
        receiver = Proc.new do |lock|
          locks << lock
          yield(lock) if block_given?
        end
        Fs.get_locks(self, path, receiver)
        locks
      end

      def history(path, start_rev, end_rev,
                  cross_copies=true, authz_read_func=nil)
        hist = []
        history_func = Proc.new do |path, revision|
          yield(path, revision) if block_given?
          hist << [path, revision]
        end
        Repos.history2(self, path, history_func,
                       authz_read_func, start_rev, end_rev,
                       cross_copies)
        hist
      end

      def trace_node_locations(fs_path, location_revisions,
                               peg_rev=nil, &authz_read_func)
        peg_rev ||= youngest_rev
        Repos.trace_node_locations(self, fs_path, peg_rev,
                                   location_revisions,
                                   authz_read_func)
      end
    end

    Access = SWIG::TYPE_p_svn_fs_access_t
    class Access
      class << self
        def new(username)
          Fs.create_access(username)
        end
      end

      def username
        Fs.access_get_username(self)
      end

      def add_lock_token(token)
        Fs.access_add_lock_token(self, token)
      end
    end

    Transaction = SWIG::TYPE_p_svn_fs_txn_t
    class Transaction

      def name
        Fs.txn_name(self)
      end

      def prop(name)
        value = Fs.txn_prop(self, name)
        if name == Svn::Core::PROP_REVISION_DATE and value
          value = Time.parse_svn_format(value)
        end
        value
      end

      def set_prop(name, value, validate=true)
        if validate
          Repos.fs_change_txn_prop(self, name, value)
        else
          Fs.change_txn_prop(self, name, value)
        end
      end

      def base_revision
        Fs.txn_base_revision(self)
      end

      def root
        Fs.txn_root(self)
      end

      def proplist
        list = Fs.txn_proplist(self)
        date_str = list[Svn::Core::PROP_REVISION_DATE]
        if list[Svn::Core::PROP_REVISION_DATE]
          list[Svn::Core::PROP_REVISION_DATE] = Time.parse_svn_format(date_str)
        end
        list
      end

      def abort
        Fs.abort_txn(self)
      end

      def commit
        result = Fs.commit_txn(self)
        if result.is_a?(Array)
          result
        else
          [nil, result]
        end
      end
    end


    Root = SWIG::TYPE_p_svn_fs_root_t
    class Root
      attr_reader :editor

      def dir?(path)
        Fs.is_dir(self, path)
      end

      def file?(path)
        Fs.is_file(self, path)
      end

      def base_revision
        Fs.txn_root_base_revision(self)
      end

      def revision
        Fs.revision_root_revision(self)
      end

      def name
        Fs.txn_root_name(self)
      end

      def fs
        Fs.root_fs_wrapper(self)
      end

      def node_id(path)
        Fs.node_id(self, path)
      end

      def node_created_rev(path)
        Fs.node_created_rev(self, path)
      end

      def node_created_path(path)
        Fs.node_created_path(self, path)
      end

      def node_prop(path, key)
        Fs.node_prop(self, path, key)
      end

      def set_node_prop(path, key, value, validate=true)
        if validate
          Repos.fs_change_node_prop(self, path, key, value)
        else
          Fs.change_node_prop(self, path, key, value)
        end
      end

      def node_proplist(path)
        Fs.node_proplist(self, path)
      end
      alias node_prop_list node_proplist

      def check_path(path)
        Fs.check_path(self, path)
      end

      def file_length(path)
        Fs.file_length(self, path)
      end

      def file_md5_checksum(path)
        Fs.file_md5_checksum(self, path)
      end

      def file_contents(path)
        stream = Fs.file_contents(self, path)
        if block_given?
          begin
            yield stream
          ensure
            stream.close
          end
        else
          stream
        end
      end

      def close
        Fs.close_root(self)
      end

      def dir_entries(path)
        entries = {}
        Fs.dir_entries(self, path).each do |name, entry|
          entries[name] = entry
        end
        entries
      end

      def dir_delta(src_path, src_entry, tgt_root, tgt_path,
                    editor, text_deltas=false, recurse=true,
                    entry_props=false, ignore_ancestry=false,
                    &authz_read_func)
        Repos.dir_delta(self, src_path, src_entry, tgt_root,
                        tgt_path, editor, authz_read_func,
                        text_deltas, recurse, entry_props,
                        ignore_ancestry)
      end

      def replay(editor, base_dir=nil, low_water_mark=nil, send_deltas=true,
                 &callback)
        base_dir ||= ""
        low_water_mark ||= Core::INVALID_REVNUM
        Repos.replay2(self, base_dir, low_water_mark, send_deltas,
                      editor, callback)
      end

      def copied_from(path)
        Fs.copied_from(self, path)
      end

      def txn_root?
        Fs.is_txn_root(self)
      end

      def revision_root?
        Fs.is_revision_root(self)
      end

      def paths_changed
        Fs.paths_changed(self)
      end

      def node_history(path)
        Fs.node_history(self, path)
      end

      def props_changed?(path1, root2, path2)
        Fs.props_changed(self, path1, root2, path2)
      end

      def merge(target_path, source_root, source_path,
                ancestor_root, ancestor_path)
        Fs.merge(source_root, source_path, self, target_path,
                 ancestor_root, ancestor_path)
      end

      def make_dir(path)
        Fs.make_dir(self, path)
      end

      def delete(path)
        Fs._delete(self, path)
      end

      def copy(to_path, from_root, from_path)
        Fs.copy(from_root, from_path, self, to_path)
      end

      def revision_link(from_root, path)
        Fs.revision_link(from_root, self, path)
      end

      def make_file(path)
        Fs.make_file(self, path)
      end

      def apply_textdelta(path, base_checksum=nil, result_checksum=nil)
        handler, handler_baton = Fs.apply_textdelta(self, path,
                                                    base_checksum,
                                                    result_checksum)
        handler.baton = handler_baton
        handler
      end

      def apply_text(path, result_checksum=nil)
        Fs.apply_text(self, path, result_checksum)
      end

      def contents_changed?(path1, root2, path2)
        Fs.contents_changed(self, path1, root2, path2)
      end

      def file_delta_stream(arg1, arg2, target_path)
        if arg1.is_a?(self.class)
          source_root = arg1
          source_path = arg2
          target_root = self
        else
          source_root = self
          source_path = arg1
          target_root = arg2
        end
        Fs.get_file_delta_stream(source_root, source_path,
                                 target_root, target_path)
      end

      def stat(path)
        Repos.stat(self, path)
      end

      def committed_info(path)
        Repos.get_committed_info(self, path)
      end

      def closest_copy(path)
        Fs.closest_copy(self, path)
      end

      def mergeinfo(paths, inherit=nil, include_descendants=false)
        paths = [paths] unless paths.is_a?(Array)
        Fs.get_mergeinfo(self, paths, inherit, include_descendants)
      end

      def change_mergeinfo(path, info)
        Fs.change_mergeinfo(self, path, info)
      end
    end

    History = SWIG::TYPE_p_svn_fs_history_t

    class History
      def location
        Fs.history_location(self)
      end

      def prev(cross_copies=true)
        Fs.history_prev(self, cross_copies)
      end
    end


    DirectoryEntry = Dirent

    Id = SWIG::TYPE_p_svn_fs_id_t
    class Id
      def to_s
        unparse
      end

      def unparse
        Fs.unparse_id(self)
      end

      def compare(other)
        Fs.compare_ids(self, other)
      end

      def related?(other)
        Fs.check_related(self, other)
      end
    end

    class PathChange
      def modify?
        change_kind == Svn::Fs::PATH_CHANGE_MODIFY
      end

      def add?
        change_kind == Svn::Fs::PATH_CHANGE_ADD
      end

      def text_mod?
        text_mod
      end
    end

    class FileDiff

      def initialize(root1, path1, root2, path2)
        @tempfile1 = nil
        @tempfile2 = nil

        @binary = nil

        @root1 = root1
        @path1 = path1
        @root2 = root2
        @path2 = path2
      end

      def binary?
        if @binary.nil?
          @binary = _binary?(@root1, @path1)
          @binary ||= _binary?(@root2, @path2)
        end
        @binary
      end

      def files
        if @tempfile1
          [@tempfile1, @tempfile2]
        else
          @tempfile1 = Tempfile.new("svn_fs")
          @tempfile2 = Tempfile.new("svn_fs")

          begin
            dump_contents(@tempfile1, @root1, @path1)
            dump_contents(@tempfile2, @root2, @path2)
          ensure
            @tempfile1.close
            @tempfile2.close
          end

          [@tempfile1, @tempfile2]
        end
      end

      def diff
        files
        @diff ||= Core::Diff.file_diff(@tempfile1.path, @tempfile2.path)
      end

      def unified(label1, label2, header_encoding=nil)
        if diff and diff.diff?
          diff.unified(label1, label2, header_encoding)
        else
          ""
        end
      end

      private
      def dump_contents(open_tempfile, root, path)
        if root and path
          root.file_contents(path) do |stream|
            open_tempfile.print(stream.read)
          end
        end
      end

      def _binary?(root, path)
        if root and path
          prop = root.node_prop(path, Core::PROP_MIME_TYPE)
          prop and Core.binary_mime_type?(prop)
        end
      end
    end
  end
end
