/*
This file is part of Ruby/SMB.
Copyright (c) 2002 Henrik Falck <hefa at users.sourceforge.net>

Ruby/SMB is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

Ruby/SMB is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Ruby/SMB; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <libsmbclient.h>
#include <ruby.h>
#include <rubyio.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "rubysmb.h"
#include "smbdir.h"
#include "smbfile.h"

struct smbdir {
  int dh;
  char *url;
};

struct smbdirentry {
  char *url;
  char *name;
  char *comment;
  int type;
};

static VALUE smbdirentry_new(struct smbc_dirent*, char*);
static VALUE smbdirentry_name(VALUE);
static VALUE smbdirentry_comment(VALUE);

static void dir_free(struct smbdir *dir)
{
  if (dir->dh >= 0) {
    smbc_closedir(dir->dh);
    dir->dh = -1;
  }
  xfree(dir->url);
  xfree(dir);
}

static void dir_check_open(struct smbdir *dir)
{
  if (dir->dh < 0) {
    rb_raise(rb_eIOError, "closed directory");
  }
}

static VALUE smbdir_new(VALUE self, VALUE vurl)
{
  char *url = StringValuePtr(vurl);
  VALUE obj;
  struct smbdir *dir;
  int dh;
  struct smbc_dirent *ent;
  VALUE ary;

  dh = smbc_opendir(url);
  if (dh < 0) {
    rb_sys_fail(url);
  }

  obj = Data_Make_Struct(cSmbDir, struct smbdir, 0, dir_free, dir);
  dir->url = ALLOC_N(char, strlen(url) + 1);
  strcpy(dir->url, url);
  dir->dh = dh;

  rb_obj_call_init(obj, 1, &vurl);

  return obj;
}

static VALUE yield_dir(VALUE vdir)
{
  return rb_yield(vdir);
}

static VALUE smbdir_close(VALUE);

VALUE smbdir_open(VALUE self, VALUE vurl)
{
  VALUE vdir = smbdir_new(self, vurl);

  if (rb_block_given_p()) {
    rb_ensure(yield_dir, vdir, smbdir_close, vdir);
  }
  else {
    return vdir;
  }

  return Qnil;
}

static VALUE smbdir_initialize(VALUE self, VALUE vurl)
{
  return Qnil;
}

static VALUE smbdir_url(VALUE self)
{
  struct smbdir *dir;

  Data_Get_Struct(self, struct smbdir, dir);
  dir_check_open(dir);

  return rb_str_new2(dir->url);
}

static VALUE smbdir_close(VALUE self)
{
  struct smbdir *dir;

  Data_Get_Struct(self, struct smbdir, dir);

  if (dir->dh >= 0) {
    smbc_closedir(dir->dh);
    dir->dh = -1;
  }

  return Qnil;
}

static VALUE smbdir_read(VALUE self)
{
  struct smbdir *dir;
  struct smbc_dirent *ent;

  Data_Get_Struct(self, struct smbdir, dir);
  dir_check_open(dir);

  errno = 0;
  ent = smbc_readdir(dir->dh);
  if (ent == NULL) {
    if (errno) {
      rb_sys_fail(dir->url);
    }
    return Qnil;
  }

  return rb_str_new2(ent->name);
}

static VALUE smbdir_read_entry(VALUE self)
{
  struct smbdir *dir;
  struct smbc_dirent *ent;

  Data_Get_Struct(self, struct smbdir, dir);
  dir_check_open(dir);

  errno = 0;
  if ((ent = smbc_readdir(dir->dh)) == NULL) {
    if (errno) {
      rb_sys_fail(dir->url);
    }
    return Qnil;
  }

  return smbdirentry_new(ent, dir->url);
}

static VALUE smbdir_each(VALUE self)
{
  VALUE name;

  while (!NIL_P(name = smbdir_read(self))) {
    rb_yield(name);
  }

  return self;
}

static VALUE smbdir_each_entry(VALUE self)
{
  VALUE entry;

  while (!NIL_P(entry = smbdir_read_entry(self))) {
    rb_yield(entry);
  }

  return self;
}

static VALUE smbdir_tell(VALUE self)
{
  struct smbdir *dir;
  off_t offset;

  Data_Get_Struct(self, struct smbdir, dir);
  dir_check_open(dir);

  /* smbc_telldir() in libsmbclient 3.0.25b or later returns wrong offset.
   * See https://bugzilla.samba.org/show_bug.cgi?id=4750 for details. */
  errno = 0;
  if ((offset = smbc_telldir(dir->dh)) < 0) {
    if (errno) {
      rb_sys_fail(dir->url);
    }
  }

  return LL2NUM(offset);
}

static VALUE smbdir_seek(VALUE self, VALUE pos)
{
  struct smbdir *dir;

  Data_Get_Struct(self, struct smbdir, dir);
  dir_check_open(dir);

  if (smbc_lseekdir(dir->dh, (off_t)NUM2LL(pos)) < 0) {
    rb_sys_fail(dir->url);
  }

  return self;
}

static VALUE smbdir_rewind(VALUE self)
{
  struct smbdir *dir;

  Data_Get_Struct(self, struct smbdir, dir);
  dir_check_open(dir);

  if (smbc_lseekdir(dir->dh, (off_t)NULL) < 0) {
    rb_sys_fail(dir->url);
  }

  return self;
}

static VALUE foreach_smbdir(VALUE vdir)
{
  VALUE entry;

  while (!NIL_P(entry = smbdir_read(vdir))) {
    rb_yield(entry);
  }

  return Qnil;
}

static VALUE smbdir_foreach(VALUE self, VALUE vurl)
{
  VALUE vdir = smbdir_new(self, vurl);

  rb_ensure(foreach_smbdir, vdir, smbdir_close, vdir);

  return Qnil;
}

static VALUE smbdir_delete(VALUE self, VALUE vurl)
{
  char *url = StringValuePtr(vurl);

  if (smbc_rmdir(url) < 0) {
    rb_sys_fail(url);
  }

  return INT2FIX(0);
}

static VALUE smbdir_unlink(VALUE self, VALUE vurl)
{
  smbdir_delete(self, vurl);

  return Qtrue;
}

static VALUE smbdir_mkdir(int argc, VALUE *argv, VALUE self)
{
  VALUE vurl;
  VALUE rmode;
  char *url;
  int mode;

  rb_scan_args(argc, argv, "11", &vurl, &rmode);
  url = StringValuePtr(vurl);

  if (argc == 1) {
    mode = 0644;
  }
  else {
    mode = NUM2INT(rmode);
  }

  if (smbc_mkdir(url, (mode_t)mode) < 0) {
    rb_sys_fail(url);
  }

  return INT2FIX(0);
}

static void free_direntry(struct smbdirentry *ent)
{
  xfree(ent->url);
  xfree(ent->name);
  if (ent->comment != NULL)
    xfree(ent->comment);
  xfree(ent);
}

static VALUE smbdirentry_new(struct smbc_dirent *smbc_ent, char *baseurl)
{
  VALUE obj;
  struct smbdirentry *ent;

  obj = Data_Make_Struct(cSmbDirEntry, struct smbdirentry, 0, free_direntry, ent);

  ent->url = ALLOC_N(char, smbc_ent->namelen + strlen(baseurl) + 2);
  strcpy(ent->url, baseurl);
  if (baseurl[strlen(baseurl) - 1] != '/') {
    strcat(ent->url, "/");
  }
  strcat(ent->url, smbc_ent->name);
  ent->name = ALLOC_N(char, smbc_ent->namelen + 1);
  strcpy(ent->name, smbc_ent->name);
  if (smbc_ent->commentlen > 0) {
    ent->comment = ALLOC_N(char, smbc_ent->commentlen + 1);
    strcpy(ent->comment, smbc_ent->comment);
  }
  else {
    ent->comment = NULL;
  }
  ent->type = smbc_ent->smbc_type;

  return obj;
}

static VALUE smbdirentry_name(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  return rb_str_new2(ent->name);
}

static VALUE smbdirentry_comment(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  if (ent->comment == NULL) {
    return Qnil;
  }

  return rb_str_new2(ent->comment);
}

static VALUE smbdirentry_smb_type(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  return INT2FIX(ent->type);
}

static VALUE smbdirentry_open(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  if (ent->type == SMBC_FILE) {
    VALUE vurl = rb_str_new2(ent->url);
    return smbfile_open(1, &vurl, cSmbFile);
  }
  else if (ent->type == SMBC_DIR ||
	   ent->type == SMBC_FILE_SHARE ||
	   ent->type == SMBC_SERVER ||
	   ent->type == SMBC_WORKGROUP) {
    VALUE vurl = rb_str_new2(ent->url);
    return smbdir_open(cSmbDir, vurl);
  }
  else {
    rb_raise(eSmbError, "can't open that file type");
  }

  return Qnil;
}

static VALUE smbdirentry_url(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  return rb_str_new2(ent->url);
}

static VALUE smbdirentry_workgroup_p(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  return (ent->type == SMBC_WORKGROUP ? Qtrue : Qfalse);
}

static VALUE smbdirentry_server_p(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  return (ent->type == SMBC_SERVER ? Qtrue : Qfalse);
}

static VALUE smbdirentry_file_share_p(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  return (ent->type == SMBC_FILE_SHARE ? Qtrue : Qfalse);
}

static VALUE smbdirentry_printer_share_p(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  return (ent->type == SMBC_PRINTER_SHARE ? Qtrue : Qfalse);
}

static VALUE smbdirentry_comms_share_p(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  return (ent->type == SMBC_COMMS_SHARE ? Qtrue : Qfalse);
}

static VALUE smbdirentry_ipc_share_p(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  return (ent->type == SMBC_IPC_SHARE ? Qtrue : Qfalse);
}

static VALUE smbdirentry_dir_p(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  return (ent->type == SMBC_DIR ? Qtrue : Qfalse);
}

static VALUE smbdirentry_file_p(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  return (ent->type == SMBC_FILE ? Qtrue : Qfalse);
}

static VALUE smbdirentry_link_p(VALUE self)
{
  struct smbdirentry *ent;

  Data_Get_Struct(self, struct smbdirentry, ent);

  return (ent->type == SMBC_LINK ? Qtrue : Qfalse);
}

void init_smbdir(void)
{
  cSmbDir = rb_define_class_under(mSMB, "Dir", rb_cObject);
  rb_include_module(cSmbDir, rb_mEnumerable);
  rb_include_module(cSmbDir, mSmbUtil);

  rb_define_singleton_method(cSmbDir, "new", smbdir_new, 1);
  rb_define_singleton_method(cSmbDir, "open", smbdir_open, 1);
  rb_define_method(cSmbDir, "initialize", smbdir_initialize, 1);
  rb_define_method(cSmbDir, "url", smbdir_url, 0);
  rb_define_alias(cSmbDir, "uri", "url");
  rb_define_method(cSmbDir, "close", smbdir_close, 0);
  rb_define_method(cSmbDir, "read", smbdir_read, 0);
  rb_define_method(cSmbDir, "read_entry", smbdir_read_entry, 0);
  rb_define_method(cSmbDir, "tell", smbdir_tell, 0);
  rb_define_alias(cSmbDir, "pos", "tell");
  rb_define_method(cSmbDir, "seek", smbdir_seek, 1);
  rb_define_method(cSmbDir, "rewind", smbdir_rewind, 0);
  rb_define_method(cSmbDir, "each", smbdir_each, 0);
  rb_define_method(cSmbDir, "each_entry", smbdir_each_entry, 0);
  rb_define_singleton_method(cSmbDir, "foreach", smbdir_foreach, 1);
  rb_define_singleton_method(cSmbDir, "delete", smbdir_delete, 1);
  rb_define_singleton_method(cSmbDir, "mkdir", smbdir_mkdir, -1);
  rb_define_singleton_method(cSmbDir, "unlink", smbdir_unlink, 1);
  rb_define_singleton_method(cSmbDir, "rmdir", smbdir_unlink, 1);

  cSmbDirEntry = rb_define_class_under(cSmbDir, "Entry", rb_cObject);
  rb_include_module(cSmbDirEntry, mSmbUtil);
  rb_define_method(cSmbDirEntry, "open", smbdirentry_open, 0);
  rb_define_method(cSmbDirEntry, "name", smbdirentry_name, 0);
  rb_define_method(cSmbDirEntry, "comment", smbdirentry_comment, 0);
  rb_define_method(cSmbDirEntry, "smb_type", smbdirentry_smb_type, 0);
  rb_define_method(cSmbDirEntry, "url", smbdirentry_url, 0);
  rb_define_alias(cSmbDirEntry, "uri", "url");
  rb_define_method(cSmbDirEntry, "workgroup?", smbdirentry_workgroup_p, 0);
  rb_define_method(cSmbDirEntry, "server?", smbdirentry_server_p, 0);
  rb_define_method(cSmbDirEntry, "file_share?", smbdirentry_file_share_p, 0);
  rb_define_method(cSmbDirEntry, "printer_share?", smbdirentry_printer_share_p, 0);
  rb_define_method(cSmbDirEntry, "comms_share?", smbdirentry_comms_share_p, 0);
  rb_define_method(cSmbDirEntry, "ipc_share?", smbdirentry_ipc_share_p, 0);
  rb_define_method(cSmbDirEntry, "dir?", smbdirentry_dir_p, 0);
  rb_define_method(cSmbDirEntry, "file?", smbdirentry_file_p, 0);
  rb_define_method(cSmbDirEntry, "link?", smbdirentry_link_p, 0);
}
