/* $Id: geoip.c,v 1.6 2002/11/19 21:21:38 sean Exp $ */

#include "geoip.h"
#include "GeoIP.h"
#include "GeoIPUpdate.h"

VALUE ruby_net_geoip_country_code_by_addr(VALUE self, VALUE addr) {
  ruby_net_geoip *rng;
  char *cc;

  Check_Type(addr, T_STRING);
  Data_Get_Struct(self, ruby_net_geoip, rng);
  cc = (char *)GeoIP_country_code_by_addr(rng->g, STR2CSTR(addr));
  if (cc == NULL) {
    return(Qnil);
  } else {
    return(rb_str_new2(cc));
  }
}


VALUE ruby_net_geoip_country_code3_by_addr(VALUE self, VALUE addr) {
  ruby_net_geoip *rng;
  char *cc;

  Check_Type(addr, T_STRING);
  Data_Get_Struct(self, ruby_net_geoip, rng);
  cc = (char *)GeoIP_country_code3_by_addr(rng->g, STR2CSTR(addr));
  if (cc == NULL) {
    return(Qnil);
  } else {
    return(rb_str_new2(cc));
  }
}


VALUE ruby_net_geoip_country_code_by_name(VALUE self, VALUE name) {
  ruby_net_geoip *rng;
  char *cc;

  Check_Type(name, T_STRING);
  Data_Get_Struct(self, ruby_net_geoip, rng);
  cc = (char *)GeoIP_country_code_by_name(rng->g, STR2CSTR(name));
  if (cc == NULL) {
    return(Qnil);
  } else {
    return(rb_str_new2(cc));
  }
}


VALUE ruby_net_geoip_country_code3_by_name(VALUE self, VALUE name) {
  ruby_net_geoip *rng;
  char *cc;

  Check_Type(name, T_STRING);
  Data_Get_Struct(self, ruby_net_geoip, rng);
  cc = (char *)GeoIP_country_code3_by_name(rng->g, STR2CSTR(name));
  if (cc == NULL) {
    return(Qnil);
  } else {
    return(rb_str_new2(cc));
  }
}


VALUE ruby_net_geoip_country_id_by_addr(VALUE self, VALUE addr) {
  ruby_net_geoip *rng;
  Check_Type(addr, T_STRING);
  Data_Get_Struct(self, ruby_net_geoip, rng);
  return(INT2NUM(GeoIP_country_id_by_addr(rng->g, STR2CSTR(addr))));
}


VALUE ruby_net_geoip_country_id_by_name(VALUE self, VALUE name) {
  ruby_net_geoip *rng;
  Check_Type(name, T_STRING);
  Data_Get_Struct(self, ruby_net_geoip, rng);
  return(INT2NUM(GeoIP_country_id_by_name(rng->g, STR2CSTR(name))));
}


VALUE ruby_net_geoip_country_name_by_addr(VALUE self, VALUE addr) {
  ruby_net_geoip *rng;
  char *cn;

  Check_Type(addr, T_STRING);
  Data_Get_Struct(self, ruby_net_geoip, rng);
  cn = (char *)GeoIP_country_name_by_addr(rng->g, STR2CSTR(addr));
  if (cn == NULL) {
    return(Qnil);
  } else {
    return(rb_str_new2(cn));
  }
}


VALUE ruby_net_geoip_country_name_by_name(VALUE self, VALUE name) {
  ruby_net_geoip *rng;
  char *cn;

  Check_Type(name, T_STRING);
  Data_Get_Struct(self, ruby_net_geoip, rng);
  cn = (char *)GeoIP_country_name_by_name(rng->g, STR2CSTR(name));
  if (cn == NULL) {
    return(Qnil);
  } else {
    return(rb_str_new2(cn));
  }
}


void ruby_net_geoip_free(ruby_net_geoip *rng) {
  if (rng->g != NULL)
    GeoIP_delete(rng->g);

  free(rng);
}


VALUE ruby_net_geoip_new(int argc, VALUE *argv, VALUE class) {
  ruby_net_geoip *rng;
  int db_type;
  VALUE type;

  switch (argc) {
  case 0:
    db_type = GEOIP_STANDARD;
    break;
  case 1:
    rb_scan_args(argc, argv, "01", &type);
    Check_Type(type, T_FIXNUM);
    switch (NUM2INT(type)) {
    case GEOIP_STANDARD:
      db_type = NUM2INT(type);
      break;
    case GEOIP_MEMORY_CACHE:
      db_type = NUM2INT(type);
      break;
    default:
      rb_raise(rb_eArgError, "invalid database type: bust be TYPE_DISK or TYPE_RAM");
    }
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (needs 0 or 1)");
  }
  rng = ALLOC(ruby_net_geoip);
  rng->g = GeoIP_new(db_type);

  return(Data_Wrap_Struct(class, 0, ruby_net_geoip_free, rng));
}


VALUE ruby_net_geoip_open(int argc, VALUE *argv, VALUE class) {
  ruby_net_geoip *rng;
  int db_type;
  VALUE filename, type;

  switch (argc) {
  case 1:
    rb_scan_args(argc, argv, "01", &filename);
    Check_Type(filename, T_STRING);
    db_type = GEOIP_STANDARD;
    break;
  case 2:
    rb_scan_args(argc, argv, "02", &filename, &type);
    Check_Type(filename, T_STRING);
    Check_Type(type, T_FIXNUM);

    switch (NUM2INT(type)) {
    case GEOIP_STANDARD:
      db_type = NUM2INT(type);
      break;
    case GEOIP_MEMORY_CACHE:
      db_type = NUM2INT(type);
      break;
    default:
      rb_raise(rb_eArgError, "invalid database type");
    }
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (needs 0 or 1)");
  }
  rng = ALLOC(ruby_net_geoip);
  rng->g = GeoIP_open(STR2CSTR(filename), db_type);

  return(Data_Wrap_Struct(class, 0, ruby_net_geoip_free, rng));
}


VALUE ruby_net_geoip_database_info(VALUE self) {
  ruby_net_geoip *rng;
  Data_Get_Struct(self, ruby_net_geoip, rng);
  return(rb_str_new2(GeoIP_database_info(rng->g)));
}


VALUE ruby_net_geoip_region_by_addr(VALUE self, VALUE addr) {
  ruby_net_geoip *rng;
  GeoIPRegion *r;
  VALUE reg;

  Check_Type(addr, T_STRING);
  Data_Get_Struct(self, ruby_net_geoip, rng);
  r = GeoIP_region_by_addr(rng->g, STR2CSTR(addr));
  if (r == NULL)
    return(Qnil);

  reg = rb_str_new2(r->region);
  GeoIPRegion_delete(r);
  return(reg);
}


VALUE ruby_net_geoip_region_by_name(VALUE self, VALUE name) {
  ruby_net_geoip *rng;
  GeoIPRegion *r;
  VALUE reg;

  Check_Type(name, T_STRING);
  Data_Get_Struct(self, ruby_net_geoip, rng);
  r = GeoIP_region_by_name(rng->g, STR2CSTR(name));
  if (r == NULL)
    return(Qnil);

  reg = rb_str_new2(r->region);
  GeoIPRegion_delete(r);
  return(reg);
}


VALUE ruby_net_geoip_update_database(int argc, VALUE *argv, VALUE class) {
  int ret, debug;
  VALUE bool, key;

  if (argc == 1) {
    rb_scan_args(argc, argv, "10", &key);
    Check_Type(key, T_STRING);
    debug = 0;
  } else if (argc == 2) {
    rb_scan_args(argc, argv, "20", &key, &bool);
    switch (TYPE(bool)) {
    case T_TRUE:
      debug = 1;
      break;
    case T_FALSE:
      debug = 0;
      break;
    default:
      rb_raise(rb_eArgError, "Invalid argument: debug flag must be boolean");
    }
  } else {
    rb_raise(rb_eArgError, "wrong number of arguments (need 1 or 2)");
  }

  ret = GeoIP_update_database(STR2CSTR(key), debug, NULL);

  switch (ret) {
  case 0:           /* Success, database updated */
    return(Qtrue);
  case 1:           /* Database up-to-date, no action taken */
    return(Qfalse);
  case -1:
    rb_raise(eNetGeoIPError, "Invalid License Key in %s", STR2CSTR(key));
  case -11:
    rb_raise(eNetGeoIPError, "Unable to resolve hostname");
  case -12:
    rb_raise(eNetGeoIPError, "Non-IPv4 addres");
  case -13:
    rb_raise(eNetGeoIPError, "Error opening socket");
  case -14:
    rb_raise(eNetGeoIPError, "Unable to connect");
  case -15:
    rb_raise(eNetGeoIPError, "Unable to write GeoIP.dat.gz file");
  case -16:
    rb_raise(eNetGeoIPError, "Unable to write GeoIP.dat file");
  case -17:
    rb_raise(eNetGeoIPError, "Unable to read gzip data");
  case -18:
    rb_raise(eNetGeoIPError, "Out of memory error");
  case -19:
    rb_raise(eNetGeoIPError, "Error reading from socket, see errno");
  default:
    rb_raise(eNetGeoIPError, "Unknown error: contact the maintainer");
  }
}


void Init_geoip(void) {
  mNet = rb_define_module("Net");
  cNetGeoIP = rb_define_class_under(mNet, "GeoIP", rb_cObject);
  eNetGeoIPError = rb_define_class_under(cNetGeoIP, "Error", rb_eException);

  rb_define_const(cNetGeoIP, "TYPE_DISK", INT2NUM(GEOIP_STANDARD));
  rb_define_const(cNetGeoIP, "TYPE_RAM", INT2NUM(GEOIP_MEMORY_CACHE));
  rb_define_const(cNetGeoIP, "VERSION", rb_str_new2(RUBY_GEOIP_VERSION));
  rb_define_const(cNetGeoIP, "VERNUM", INT2NUM(RUBY_GEOIP_VERNUM));

  rb_define_singleton_method(cNetGeoIP, "new", ruby_net_geoip_new, -1);
  rb_define_singleton_method(cNetGeoIP, "open", ruby_net_geoip_open, -1);
  rb_define_singleton_method(cNetGeoIP, "update_database",
			     ruby_net_geoip_update_database, -1);

  rb_define_method(cNetGeoIP, "country_code_by_addr",
		   ruby_net_geoip_country_code_by_addr, 1);
  rb_define_method(cNetGeoIP, "country_code3_by_addr",
		   ruby_net_geoip_country_code3_by_addr, 1);
  rb_define_method(cNetGeoIP, "country_code_by_name",
		   ruby_net_geoip_country_code_by_name, 1);
  rb_define_method(cNetGeoIP, "country_code3_by_name",
		   ruby_net_geoip_country_code3_by_name, 1);
  rb_define_method(cNetGeoIP, "country_id_by_addr",
		   ruby_net_geoip_country_id_by_addr, 1);
  rb_define_method(cNetGeoIP, "country_id_by_name",
		   ruby_net_geoip_country_id_by_name, 1);
  rb_define_method(cNetGeoIP, "country_name_by_addr",
		   ruby_net_geoip_country_name_by_addr, 1);
  rb_define_method(cNetGeoIP, "country_name_by_name",
		   ruby_net_geoip_country_name_by_name, 1);
  rb_define_method(cNetGeoIP, "database_info",
		   ruby_net_geoip_database_info, 0);
  rb_define_method(cNetGeoIP, "region_by_addr",
		   ruby_net_geoip_region_by_addr, 1);
  rb_define_method(cNetGeoIP, "region_by_name",
		   ruby_net_geoip_region_by_name, 1);
}
