require 'thread'
require 'rfusefs'
require 'haveapi/client'

module HaveAPI::Fs
  # Interface with RFuseFS API. Methods called by RFuseFS are redirected to
  # appropriate components.
  class Fs
    CHECK_FILE = FuseFS::Fuse::Root::CHECK_FILE[1..-1].to_sym

    attr_reader :api

    def initialize(api, opts)
      @api = api

      @mutex = Mutex.new

      @path_cache = Cache.new(self)
      @context = Context.new
      @context.opts = opts
      @context.url = opts[:device]
      @context.mountpoint = ::File.realpath(opts[:mountpoint])
      @context.cache = @path_cache
      @context[:fs] = self

      @root = Factory.create(@context, nil, Components::Root)
#      @root.context = @context.clone
#      @root.context[:root] = @root
      @root.setup

      Thread.abort_on_exception = true
      @cleaner = Cleaner.new(self, @root)
      @cleaner.start
      @path_cache.start
    end

    def contents(path)
      puts "contents"
      p path

      guard { find_component(path).contents }
    end

    def directory?(path)
      puts "directory?"
      p path
    
      guard { find_component(path).directory? }
    end

    def file?(path)
      puts "file?"
      p path
    
      guard {find_component(path).file? }
    end

    def can_read?(path)
      puts "can_read?"
      p path

      guard { find_component(path).readable? }
    end

    def can_write?(path)
      puts "can_write?"
      p path

      guard { find_component(path).writable? }
    end

    def executable?(path)
      puts "executable?"
      p path

      guard { find_component(path).executable? }
    end

    def times(path)
      puts "times"
      p path

      guard { find_component(path).times }
    end

    def size(path)
      puts "size"
      p path
    
      guard { find_component(path).size }
    end

    def read_file(path)
      puts "read_file"
      p path
    
      guard { find_component(path).read }
    end

    def write_to(path, str)
      puts "write_to"
      p path

      guard { find_component(path).write(str) }
    end

    def raw_open(path, *args)
      puts "raw_open"
      p path

      guard { find_component(path).raw_open(path, *args) }
    end

    def raw_read(path, *args)
      puts "raw_read"
      p path

      guard { find_component(path).raw_read(path, *args) }
    end

    def raw_write(path, *args)
      puts "raw_write"
      p path

      guard { find_component(path).raw_write(path, *args) }
    end

    def raw_sync(path, *args)
      puts "raw_sync"
      p path

      guard { find_component(path).raw_sync(path, *args) }
    end

    def raw_truncate(path, *args)
      puts "raw_truncate"
      p path

      guard { find_component(path).raw_truncate(path, *args) }
    end
    
    def raw_close(path, *args)
      puts "raw_close"
      p path

      guard { find_component(path).raw_close(path, *args) }
    end

    def unmounted
      puts "unmounted"

      @cleaner.stop
      @path_cache.stop
    end

    def synchronize
      @mutex.synchronize { yield }
    end

    protected
    def find_component(path)
      @path_cache.get(path) do
        t = Time.now

        names = path.split('/').map { |v| v.to_sym }[1..-1]
        tmp = @root

        next(tmp) unless names

        names.each do |n|
          tmp = tmp.find(n)
          
          if tmp.nil?
            raise Errno::ENOENT, "'#{path}' not found"

          else
            tmp.atime = t
          end
        end

        next(tmp)
      end
    end

    def guard
      synchronize { yield }

    rescue => e
      raise e if e.is_a?(::SystemCallError)

      warn "Exception #{e.class}"
      warn e.backtrace.join("\n")
      warn e.message

      raise Errno::EIO, e.message
    end
  end
end