// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "node.h"
#include "platform.h"


#include <unistd.h> /* getpagesize() */
#include <stdlib.h> /* getexecname() */
#include <strings.h> /* strncpy() */

#include <kstat.h>
#include <errno.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/loadavg.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#ifdef SUNOS_HAVE_IFADDRS
# include <ifaddrs.h>
#endif



#if (!defined(_LP64)) && (_FILE_OFFSET_BITS - 0 == 64)
#define PROCFS_FILE_OFFSET_BITS_HACK 1
#undef _FILE_OFFSET_BITS
#else
#define PROCFS_FILE_OFFSET_BITS_HACK 0
#endif

#include <procfs.h>

#if (PROCFS_FILE_OFFSET_BITS_HACK - 0 == 1)
#define _FILE_OFFSET_BITS 64
#endif


namespace node {

using namespace v8;

double Platform::prog_start_time = Platform::GetUptime();

char** Platform::SetupArgs(int argc, char *argv[]) {
  return argv;
}


void Platform::SetProcessTitle(char *title) {
  ;
}


const char* Platform::GetProcessTitle(int *len) {
  *len = 0;
  return NULL;
}


int Platform::GetMemory(size_t *rss, size_t *vsize) {
  pid_t pid = getpid();

  size_t page_size = getpagesize();
  char pidpath[1024];
  sprintf(pidpath, "/proc/%d/psinfo", pid);

  psinfo_t psinfo;
  FILE *f = fopen(pidpath, "r");
  if (!f) return -1;

  if (fread(&psinfo, sizeof(psinfo_t), 1, f) != 1) {
    fclose (f);
    return -1;
  }

  /* XXX correct? */

  *vsize = (size_t) psinfo.pr_size * page_size;
  *rss = (size_t) psinfo.pr_rssize * 1024;

  fclose (f);

  return 0;
}


static Handle<Value> data_named(kstat_named_t *knp) {
  Handle<Value> val;

  switch (knp->data_type) {
  case KSTAT_DATA_CHAR:
    val = Number::New(knp->value.c[0]);
    break;
  case KSTAT_DATA_INT32:
    val = Number::New(knp->value.i32);
    break;
  case KSTAT_DATA_UINT32:
    val = Number::New(knp->value.ui32);
    break;
  case KSTAT_DATA_INT64:
    val = Number::New(knp->value.i64);
    break;
  case KSTAT_DATA_UINT64:
    val = Number::New(knp->value.ui64);
    break;
  case KSTAT_DATA_STRING:
    val = String::New(KSTAT_NAMED_STR_PTR(knp));
    break;
  default:
    throw (String::New("unrecognized data type"));
  }

  return (val);
}


int Platform::GetCPUInfo(Local<Array> *cpus) {
  HandleScope scope;
  Local<Object> cpuinfo;
  Local<Object> cputimes;

  int           lookup_instance;
  kstat_ctl_t   *kc;
  kstat_t       *ksp;
  kstat_named_t *knp;

  if ((kc = kstat_open()) == NULL)
    throw "could not open kstat";

  *cpus = Array::New();

  lookup_instance = 0;
  while (ksp = kstat_lookup(kc, (char *)"cpu_info", lookup_instance, NULL)){
    cpuinfo  = Object::New();

    if (kstat_read(kc, ksp, NULL) == -1) {
      /*
       * It is deeply annoying, but some kstats can return errors
       * under otherwise routine conditions.  (ACPI is one
       * offender; there are surely others.)  To prevent these
       * fouled kstats from completely ruining our day, we assign
       * an "error" member to the return value that consists of
       * the strerror().
       */
      cpuinfo->Set(String::New("error"), String::New(strerror(errno)));
      (*cpus)->Set(lookup_instance, cpuinfo);
    } else {
      knp = (kstat_named_t *) kstat_data_lookup(ksp, (char *)"clock_MHz");
      cpuinfo->Set(String::New("speed"), data_named(knp));
      knp = (kstat_named_t *) kstat_data_lookup(ksp, (char *)"brand");
      cpuinfo->Set(String::New("model"), data_named(knp));
      (*cpus)->Set(lookup_instance, cpuinfo);
    }

    lookup_instance++;
  }

  lookup_instance = 0;
  while (ksp = kstat_lookup(kc, (char *)"cpu", lookup_instance, (char *)"sys")){
    cpuinfo = (*cpus)->Get(lookup_instance)->ToObject();
    cputimes = Object::New();

    if (kstat_read(kc, ksp, NULL) == -1) {
      cputimes->Set(String::New("error"), String::New(strerror(errno)));
      cpuinfo->Set(String::New("times"), cpuinfo);
    } else {
      knp = (kstat_named_t *) kstat_data_lookup(ksp, (char *)"cpu_ticks_kernel");
      cputimes->Set(String::New("system"), data_named(knp));
      knp = (kstat_named_t *) kstat_data_lookup(ksp, (char *)"cpu_ticks_user");
      cputimes->Set(String::New("user"), data_named(knp));
      knp = (kstat_named_t *) kstat_data_lookup(ksp, (char *)"cpu_ticks_idle");
      cputimes->Set(String::New("idle"), data_named(knp));
      knp = (kstat_named_t *) kstat_data_lookup(ksp, (char *)"intr");
      cputimes->Set(String::New("irq"), data_named(knp));

      cpuinfo->Set(String::New("times"), cputimes);
    }

    lookup_instance++;
  }

  kstat_close(kc);

  return 0;
}


double Platform::GetFreeMemory() {
  kstat_ctl_t   *kc;
  kstat_t       *ksp;
  kstat_named_t *knp;

  double pagesize = static_cast<double>(sysconf(_SC_PAGESIZE));
  ulong_t freemem;

  if((kc = kstat_open()) == NULL)
    throw "could not open kstat";

  ksp = kstat_lookup(kc, (char *)"unix", 0, (char *)"system_pages");

  if(kstat_read(kc, ksp, NULL) == -1){
    throw "could not read kstat";
  }
  else {
    knp = (kstat_named_t *) kstat_data_lookup(ksp, (char *)"freemem");
    freemem = knp->value.ul;
  }

  kstat_close(kc);

  return static_cast<double>(freemem)*pagesize;
}


double Platform::GetTotalMemory() {
  double pagesize = static_cast<double>(sysconf(_SC_PAGESIZE));
  double pages = static_cast<double>(sysconf(_SC_PHYS_PAGES));

  return pagesize*pages;
}

double Platform::GetUptimeImpl() {
  kstat_ctl_t   *kc;
  kstat_t       *ksp;
  kstat_named_t *knp;

  long hz = sysconf(_SC_CLK_TCK);
  ulong_t clk_intr;

  if ((kc = kstat_open()) == NULL)
    throw "could not open kstat";

  ksp = kstat_lookup(kc, (char *)"unix", 0, (char *)"system_misc");

  if (kstat_read(kc, ksp, NULL) == -1) {
    throw "unable to read kstat";
  } else {
    knp = (kstat_named_t *) kstat_data_lookup(ksp, (char *)"clk_intr");
    clk_intr = knp->value.ul;
  }

  kstat_close(kc);

  return static_cast<double>( clk_intr / hz );
}

int Platform::GetLoadAvg(Local<Array> *loads) {
  HandleScope scope;
  double loadavg[3];

  (void) getloadavg(loadavg, 3);
  (*loads)->Set(0, Number::New(loadavg[LOADAVG_1MIN]));
  (*loads)->Set(1, Number::New(loadavg[LOADAVG_5MIN]));
  (*loads)->Set(2, Number::New(loadavg[LOADAVG_15MIN]));

  return 0;
}


Handle<Value> Platform::GetInterfaceAddresses() {
  HandleScope scope;

#ifndef SUNOS_HAVE_IFADDRS
  return ThrowException(Exception::Error(String::New(
    "This version of sunos doesn't support getifaddrs")));
#else
  struct ::ifaddrs *addrs, *ent;
  struct ::sockaddr_in *in4;
  struct ::sockaddr_in6 *in6;
  char ip[INET6_ADDRSTRLEN];
  Local<Object> ret, o;
  Local<String> name, ipaddr, family;
  Local<Array> ifarr;

  if (getifaddrs(&addrs) != 0) {
    return ThrowException(ErrnoException(errno, "getifaddrs"));
  }

  ret = Object::New();

  for (ent = addrs; ent != NULL; ent = ent->ifa_next) {
    bzero(&ip, sizeof (ip));
    if (!(ent->ifa_flags & IFF_UP && ent->ifa_flags & IFF_RUNNING)) {
      continue;
    }

    if (ent->ifa_addr == NULL) {
      continue;
    }

    name = String::New(ent->ifa_name);
    if (ret->Has(name)) {
      ifarr = Local<Array>::Cast(ret->Get(name));
    } else {
      ifarr = Array::New();
      ret->Set(name, ifarr);
    }

    if (ent->ifa_addr->sa_family == AF_INET6) {
      in6 = (struct sockaddr_in6 *)ent->ifa_addr;
      inet_ntop(AF_INET6, &(in6->sin6_addr), ip, INET6_ADDRSTRLEN);
      family = String::New("IPv6");
    } else if (ent->ifa_addr->sa_family == AF_INET) {
      in4 = (struct sockaddr_in *)ent->ifa_addr;
      inet_ntop(AF_INET, &(in4->sin_addr), ip, INET6_ADDRSTRLEN);
      family = String::New("IPv4");
    } else {
      (void) strlcpy(ip, "<unknown sa family>", INET6_ADDRSTRLEN);
      family = String::New("<unknown>");
    }

    o = Object::New();
    o->Set(String::New("address"), String::New(ip));
    o->Set(String::New("family"), family);
    o->Set(String::New("internal"), ent->ifa_flags & IFF_PRIVATE || ent->ifa_flags &
	IFF_LOOPBACK ? True() : False());

    ifarr->Set(ifarr->Length(), o);

  }

  freeifaddrs(addrs);

  return scope.Close(ret);

#endif  // SUNOS_HAVE_IFADDRS
}


}  // namespace node

