///////////////////////////////////////////////////////////////////////////////
// BSD 3-Clause License
//
// Copyright (c) 2019, Nefelus Inc
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
//   contributors may be used to endorse or promote products derived from
//   this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/times.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include <fstream>
#include <iostream>
#if sun == 1
#include <fcntl.h>
#include <procfs.h>
#endif

#include <assert.h>
#include <stdlib.h>

#include "dbLogger.h"

using namespace std;

int Ath__double2int(double v)
{
  int iv = (int) v;
  if (iv > 0) {
    if (v > (double) iv + 0.5)
      iv++;
  } else if (iv < 0) {
    if ((double) iv > v + 0.5)
      iv--;
  }
  return iv;
}
namespace odb {

unsigned int AthHashFunction(char* key, unsigned int len, unsigned int prime)
{
  unsigned int hash, i;
  for (hash = len, i = 0; i < len; ++i)
    hash = (hash << 4) ^ (hash >> 28) ^ key[i];
  return (hash % prime);
}

int AthGetProcessMem(uint64* size, uint64* res)
{
#if unix == 1
#if sun == 1

#define page_size 1024
  // for Solaris, read /proc/pid/psinfo and get the right fields
  psinfo_t psi;
  char buff[1024];
  int pid = getpid();
  int fd = -1;
  int sz;

  sprintf(buff, "/proc/%d/psinfo", pid);
  fd = open(buff, O_RDONLY);
  if (fd < 0) {
    return -1;
  }
  sz = read(fd, &psi, sizeof(psinfo_t));

  close(fd);
  if (sz != sizeof(psinfo_t)) {
    return -1;
  }

  *size = psi.pr_size * page_size;
  *res = psi.pr_rssize * page_size;

  return 0;

#elif linux == 1
#define page_size 4096
  // for Linux, parse the first two fields from /proc/pid/statm
  char buff[1024];
  int pid = getpid();
  sprintf(buff, "/proc/%d/statm", pid);

  FILE* f = fopen(buff, "r");
  int cnt = fscanf(f, "%llu %llu", size, res);
  fclose(f);

  if (2 != cnt) {
    return -1;
  }

  *size = *size * page_size;
  *res = *res * page_size;

  return 0;

#endif
#endif
  return 0;
}

uint64 max_res = 0;
uint64 max_size = 0;

void AthSignalInstaller(int signo, void (*signal_handler)(int))
{
  struct sigaction act;
  act.sa_handler = signal_handler;
  sigemptyset(&act.sa_mask);
  act.sa_flags = SA_RESTART;
  if (sigaction(signo, &act, NULL) == -1) {
    perror("signal:");
  }
  // printf("Installed signal handler!\n");
}

void AthMaxMem(uint64 size, uint64 res)
{
  if (size > max_size)
    max_size = size;

  if (res > max_res)
    max_res = res;
}
void AthMemCounter(int /* unused: signo */)
{
  uint64 size;
  uint64 res;
  AthGetProcessMem(&size, &res);
  AthMaxMem(size, res);
}

// for scaling mem output
#define MS (1024 * 1024)
#define GS (1024 * 1024 * 1024)
void AthMemCounterp(int /* unused: signo */)
{
  uint64 size;
  uint64 res;
  AthGetProcessMem(&size, &res);
  AthMaxMem(size, res);

  double psize = ((double) size) / MS;
  double pres = ((double) res) / MS;
  if (psize > 1024.0) {
    fprintf(stdout,
            "AthMemCounter (size/res): %.2f GB / %.2f GB\n",
            psize / 1024,
            pres / 1024);
  } else {
    fprintf(
        stdout, "AthMemCounter (size/res): %.2f MB / %.2f MB\n", psize, pres);
  }
}

int AthResourceLog(const char* title, int ss)
{
  static struct tms ctp;
  static time_t wtp = 0;
  static double mmp = 0.0;

  // mallinfo does not work on 64 bit.
  // tttt_use_mallinfo is set 0.
  // change it to 1 and rebuild before trying mallinfo on 32 bit.

  if (ss == -2)
    return 0;

  uint64 res;
  uint64 size;

  double mmn;
#ifdef OLD_MALLOC
  static struct mallinfo mminfo;
  mminfo = mallinfo();
  mmn = (double) ((uint) mminfo.arena) + (double) ((uint) mminfo.hblkhd);
#else
  if (AthGetProcessMem(&size, &res) != 0) {
    return 0;
  }

  AthMaxMem(size, res);

  mmn = (double) size;
#endif

  if (ss == -1) {
    if (mmn != mmp)
      return 1;
    return 0;
  }

  struct tms ctn;
  time_t wtn, wtd;
  time(&wtn);
  times(&ctn);
  int ticks = sysconf(_SC_CLK_TCK);
  if (wtp) {
    if (title && title[0])
      notice(0, "%s:  ", title);

    wtd = wtn - wtp;
    notice(0, "ELAPSE %.8s  ", asctime(gmtime(&wtd)) + 11);

    if (ss) {
      notice(0,
             "CPU %ld cs  ",
             (ctn.tms_utime + ctn.tms_stime) - (ctp.tms_utime + ctp.tms_stime));
      notice(0, "MEM %d B (%d B)", (int) (mmn), (int) (mmn - mmp));
      notice(0, " MAX %d M", (int) max_size);
      notice(0, "  %s\n", ctime(&wtn));
    } else {
      int imeg = 1024 * 1024;
      notice(0,
             "CPU %ld sec  ",
             ((ctn.tms_utime + ctn.tms_stime) - (ctp.tms_utime + ctp.tms_stime))
                 / ticks);
      notice(
          0, "MEM %d M (%d M)", (int) (mmn / imeg), (int) ((mmn - mmp) / imeg));
      notice(0, " MAX %d M", (int) ((double) max_size / imeg));
      notice(0, "  %s\n", ctime(&wtn));
    }
  }
  wtp = wtn;
  ctp = ctn;
  mmp = mmn;

  // reset maximums after they were printed
  max_size = size;
  max_res = res;

  return 1;
}

int Ath__double2int(double v)
{
  int iv = (int) v;
  if (iv > 0) {
    if (v > (double) iv + 0.5)
      iv++;
  } else if (iv < 0) {
    if ((double) iv > v + 0.5)
      iv--;
  }
  return iv;
}

}  // namespace odb