module Rubinius
  class Stat
    include Comparable

    attr_reader :path

    def self.stat(path)
      stat = allocate
      if Rubinius.privately { stat.setup path } == 0
        stat
      else
        nil
      end
    end

    def self.fstat(fd)
      stat = allocate
      result = Rubinius.privately { stat.fsetup fd }
      Errno.handle "file descriptor #{fd}" unless result == 0
      stat
    end

    def self.lstat(path)
      stat = allocate
      result = Rubinius.privately { stat.lsetup path }
      Errno.handle path unless result == 0
      stat
    end

    def initialize(path)
      Errno.handle path unless setup(path) == 0
    end

    def setup(path)
      Rubinius.primitive :stat_stat
      path = Rubinius::Type.coerce_to_path(path)
      setup(path)
    end
    private :setup

    def fsetup(fd)
      Rubinius.primitive :stat_fstat
      fd = Rubinius::Type.coerce_to fd, Integer, :to_int
      fsetup(fd)
    end
    private :fsetup

    def lsetup(path)
      Rubinius.primitive :stat_lstat
      path = Rubinius::Type.coerce_to_path(path)
      lsetup(path)
    end
    private :lsetup

    def dev
      Rubinius.primitive :stat_dev
      raise PrimitiveFailure, "Rubinius::Stat#dev primitive failed"
    end

    def ino
      Rubinius.primitive :stat_ino
      raise PrimitiveFailure, "Rubinius::Stat#ino primitive failed"
    end

    def mode
      Rubinius.primitive :stat_mode
      raise PrimitiveFailure, "Rubinius::Stat#mode primitive failed"
    end

    def nlink
      Rubinius.primitive :stat_nlink
      raise PrimitiveFailure, "Rubinius::Stat#nlink primitive failed"
    end

    def uid
      Rubinius.primitive :stat_uid
      raise PrimitiveFailure, "Rubinius::Stat#uid primitive failed"
    end

    def gid
      Rubinius.primitive :stat_gid
      raise PrimitiveFailure, "Rubinius::Stat#gid primitive failed"
    end

    def rdev
      Rubinius.primitive :stat_rdev
      raise PrimitiveFailure, "Rubinius::Stat#rdev primitive failed"
    end

    def size
      Rubinius.primitive :stat_size
      raise PrimitiveFailure, "Rubinius::Stat#size primitive failed"
    end

    def blksize
      Rubinius.primitive :stat_blksize
      raise PrimitiveFailure, "Rubinius::Stat#blksize primitive failed"
    end

    def blocks
      Rubinius.primitive :stat_blocks
      raise PrimitiveFailure, "Rubinius::Stat#blocks primitive failed"
    end

    def atime
      Rubinius.primitive :stat_atime
      raise PrimitiveFailure, "Rubinius::Stat#atime primitive failed"
    end

    def mtime
      Rubinius.primitive :stat_mtime
      raise PrimitiveFailure, "Rubinius::Stat#mtime primitive failed"
    end

    def ctime
      Rubinius.primitive :stat_ctime
      raise PrimitiveFailure, "Rubinius::Stat#ctime primitive failed"
    end

    def birthtime
      Rubinius.primitive :stat_birthtime
      raise NotImplementedError, "birthtime() function is unimplemented on this machine"
    end

    def inspect
      "#<#{self.class.name} dev=0x#{self.dev.to_s(16)}, ino=#{self.ino}, " \
      "mode=#{sprintf("%07d", self.mode.to_s(8).to_i)}, nlink=#{self.nlink}, " \
      "uid=#{self.uid}, gid=#{self.gid}, rdev=0x#{self.rdev.to_s(16)}, " \
      "size=#{self.size}, blksize=#{self.blksize}, blocks=#{self.blocks}, " \
      "atime=#{self.atime}, mtime=#{self.mtime}, ctime=#{self.ctime}>"
    end

    def blockdev?
      mode & S_IFMT == S_IFBLK
    end

    def chardev?
      mode & S_IFMT == S_IFCHR
    end

    def dev_major
      major = Rubinius::FFI::Platform::POSIX.major dev
      major < 0 ? nil : major
    end

    def dev_minor
      minor = Rubinius::FFI::Platform::POSIX.major dev
      minor < 0 ? nil : minor
    end

    def directory?
      mode & S_IFMT == S_IFDIR
    end

    def executable?
      return true if superuser?
      return mode & S_IXUSR != 0 if owned?
      return mode & S_IXGRP != 0 if grpowned?
      return mode & S_IXOTH != 0
    end

    def executable_real?
      return true if rsuperuser?
      return mode & S_IXUSR != 0 if rowned?
      return mode & S_IXGRP != 0 if rgrpowned?
      return mode & S_IXOTH != 0
    end

    def file?
      mode & S_IFMT == S_IFREG
    end

    def ftype
      if file?
        "file"
      elsif directory?
        "directory"
      elsif chardev?
        "characterSpecial"
      elsif blockdev?
        "blockSpecial"
      elsif pipe?
        "fifo"
      elsif socket?
        "socket"
      elsif symlink?
        "link"
      else
        "unknown"
      end
    end

    def grpowned?
      Process.groups.include?(gid)
    end

    def owned?
      uid == Rubinius::FFI::Platform::POSIX.geteuid
    end

    def pipe?
      mode & S_IFMT == S_IFIFO
    end

    def rdev_major
      major = Rubinius::FFI::Platform::POSIX.major rdev
      major < 0 ? nil : major
    end

    def rdev_minor
      minor = Rubinius::FFI::Platform::POSIX.minor rdev
      minor < 0 ? nil : minor
    end

    def readable?
      return true if superuser?
      return mode & S_IRUSR != 0 if owned?
      return mode & S_IRGRP != 0 if grpowned?
      return mode & S_IROTH != 0
    end

    def readable_real?
      return true if rsuperuser?
      return mode & S_IRUSR != 0 if rowned?
      return mode & S_IRGRP != 0 if rgrpowned?
      return mode & S_IROTH != 0
    end

    def setgid?
      mode & S_ISGID != 0
    end

    def setuid?
      mode & S_ISUID != 0
    end

    def sticky?
      mode & S_ISVTX != 0
    end

    def size?
      size == 0 ? nil : size
    end

    def socket?
      mode & S_IFMT == S_IFSOCK
    end

    def symlink?
      mode & S_IFMT == S_IFLNK
    end

    def writable?
      return true if superuser?
      return mode & S_IWUSR != 0 if owned?
      return mode & S_IWGRP != 0 if grpowned?
      return mode & S_IWOTH != 0
    end

    def writable_real?
      return true if rsuperuser?
      return mode & S_IWUSR != 0 if rowned?
      return mode & S_IWGRP != 0 if rgrpowned?
      return mode & S_IWOTH != 0
    end

    def zero?
      size == 0
    end

    def <=>(other)
      return nil unless other.is_a?(File::Stat)
      self.mtime <=> other.mtime
    end

    def rgrpowned?
      gid == Rubinius::FFI::Platform::POSIX.getgid
    end
    private :rgrpowned?

    def rowned?
      uid == Rubinius::FFI::Platform::POSIX.getuid
    end
    private :rowned?

    def rsuperuser?
      Rubinius::FFI::Platform::POSIX.getuid == 0
    end
    private :rsuperuser?

    def superuser?
      Rubinius::FFI::Platform::POSIX.geteuid == 0
    end
    private :superuser?
  end
end
