/* vim: ts=2 sw=2 expandtab
 * BEGIN FLOCK GPL
 *
 * Copyright Flock Inc. 2005-2009
 * http://flock.com
 *
 * This file may be used under the terms of the
 * GNU General Public License Version 2 or later (the "GPL"),
 * http://www.gnu.org/licenses/gpl.html
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * END FLOCK GPL
 */

#if defined (XP_MACOSX)
#include <Processes.h>
#include <Events.h>
#endif

#include "flockSystemInfo.h"

NS_IMPL_ISUPPORTS1(flockSystemInfo, flockISystemInfo)

flockSystemInfo::flockSystemInfo() {
  nsresult rv = NS_OK;
  
  mFirstRun = PR_FALSE;
  nsCOMPtr<nsIPrefService> prefServ(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  if (prefServ) {
    nsCOMPtr<nsIPrefBranch> prefBranch = nsnull;
    prefServ->GetBranch(nsnull, getter_AddRefs(prefBranch));
    if (prefBranch) {
      PRInt32 prefType;
      prefBranch->GetPrefType(PREF_FIRSTRUN_COMPLETED, &prefType);
      if (prefType == nsIPrefBranch::PREF_BOOL) {
        prefBranch->GetBoolPref(PREF_FIRSTRUN_COMPLETED, &mFirstRun);
      }
    }
  }
  
#if defined(XP_WIN32)
  if (!sGetMemInfo) {
    sPSModule = LoadLibrary("psapi.dll");
    if (sPSModule) {
      sGetMemInfo = (GETPROCESSMEMORYINFO_FUNC)GetProcAddress(sPSModule, "GetProcessMemoryInfo");
      if (sGetMemInfo)
        sProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,FALSE, GetCurrentProcessId());
      // Don't leave ourselves partially initialized.
      if (!sProcess) {
        if (sPSModule) {
          FreeLibrary(sPSModule);
          sPSModule = NULL;
        }
        sGetMemInfo = NULL;
      }
    }
  }
  mLastTime = 0;
#endif
}

flockSystemInfo::~flockSystemInfo() {
#if defined(XP_WIN32)
  if (sProcess) {
    CloseHandle(sProcess);
    sProcess = NULL;
  }
  if (sPSModule) {
    FreeLibrary(sPSModule);
    sPSModule = NULL;
  }
  sGetMemInfo = NULL;
#endif
}

#if defined(__linux)
static int uptime(void) {
  double up=0;
  FILE *uptime_fd;
  
  uptime_fd = fopen("/proc/uptime", "rb");
  if (uptime_fd) {
    fscanf(uptime_fd, "%lf", &up);
    fclose(uptime_fd);
  }
  return (int)up;  // assume never be zero seconds in practice
}

static unsigned long long getHertz(void){
#ifdef HZ
    return (unsigned long long)HZ;    /* <asm/param.h> */
#else
    /* If 32-bit or big-endian (not Alpha or ia64), assume HZ is 100. */
    return (sizeof(long)==sizeof(int) || htons(999)==999) ? 100UL : 1024UL;
#endif
}
#endif


NS_IMETHODIMP
flockSystemInfo::GetCPU(PRInt32 *_retval) {
  *_retval = 0;
#if defined(XP_WIN32)
  PRInt64 time;
  FILETIME createTime, exitTime;
  
  GetSystemTimeAsFileTime((LPFILETIME)&time);
  
  if (mLastTime == 0) {
    GetProcessTimes( GetCurrentProcess(), &createTime, &exitTime, (LPFILETIME)&mLastKernelTimeProcess, (LPFILETIME)&mLastUserTimeProcess );
    mLastTime = time;
  } else {
    PRInt64 kernelTimeProcess;
    PRInt64 userTimeProcess;
    PRInt64 div = 0;
                     
    GetProcessTimes( GetCurrentProcess(), &createTime, &exitTime, (LPFILETIME)&kernelTimeProcess, (LPFILETIME)&userTimeProcess);
    div = time - mLastTime;
    if (div > 0) {
      *_retval = PRInt32((((userTimeProcess - mLastUserTimeProcess) + (kernelTimeProcess - mLastKernelTimeProcess)) *100) / div);
    } else {
      *_retval = 0;
    }
    
    mLastTime = time;
    mLastUserTimeProcess = userTimeProcess;
    mLastKernelTimeProcess = kernelTimeProcess;
  }
#endif
  
#if defined(__linux)
  unsigned long   seconds_since_boot;
  FILE *sProcStatFP;
  char *buffer = new char[1024];
 
  if (mHertz == 0) {
    mHertz = getHertz();
  }
  seconds_since_boot = uptime();

  pid_t cpid = getpid();
  snprintf(buffer, 1024, "/proc/%d/stat", cpid);
  sProcStatFP = fopen(buffer, "rb");
  
  if (sProcStatFP) {
    unsigned long utime = 0;
    unsigned long stime = 0;
    unsigned long startTime = 0;
    
    if (fgets(buffer, 1024, sProcStatFP)) {
      char *space;
      int spCount;
      
      // Get utime
      for (spCount = 0; spCount < 13; spCount++) {
        buffer = strchr(buffer, ' ');
        buffer++;
      }
      space = strchr(buffer, ' ');
      utime = strtoul(buffer, &space, 10);
      
      // Get stime
      buffer = strchr(buffer, ' ');
      buffer++;
      space = strchr(buffer, ' ');
      stime = strtoul(buffer, &space, 10);
      
      // Get startTime
      for (spCount = 0; spCount < 7; spCount++) {
        buffer = strchr(buffer, ' ');
        buffer++;
      }
      space = strchr(buffer, ' ');
      startTime = strtoul(buffer, &space, 10);

      //printf("GetCPU ****** utime: %lu, stime: %lu, startTime: %lu ******\n", utime, stime, startTime);
      //printf("GetCPU ****** seconds_since_boot: %lu, Hertz: %lu ******\n", seconds_since_boot, mHertz);
      unsigned pcpu = 0;               // scaled %cpu, 999 means 99.9%
      unsigned long long seconds;      // seconds of process life
      seconds = seconds_since_boot - startTime / mHertz ;
      if(seconds) pcpu = ((utime + stime) * 1000ULL / mHertz) / seconds;
      if (pcpu > 999U) pcpu = 999U;

      *_retval = PRInt32(pcpu / 10);
    }
    fclose(sProcStatFP);

    //delete(buffer); // why does deleting this cause a segfault?
    return NS_OK;
  }
#endif
  
#if defined(XP_MACOSX)
#endif
  return NS_OK; // If we reach here then _retval will equal 0.
}

NS_IMETHODIMP
flockSystemInfo::GetMem(PRInt64 *_retval) {
  *_retval = 0;
  
#if defined(__linux)
  FILE *sProcStatFP;
  char *buffer = new char[1024];
  pid_t pid = getpid();
  snprintf(buffer, 1024, "/proc/%d/status", pid);
  sProcStatFP = fopen(buffer, "rb");

  if (sProcStatFP) {
    unsigned long vmsize = 0;
    while (vmsize == 0 && fgets(buffer, 1024, sProcStatFP)) {
      if (strncmp(buffer, "VmSize", 6) == 0) {
        char *colon = strchr(buffer, ':');
        buffer = colon + 2;
        vmsize = strtoul(buffer,&buffer,10);
      }
    }
    fclose(sProcStatFP);
    if (vmsize > 0) {
      *_retval = (vmsize / 1024);
    }
    //delete(buffer); // why does deleting this cause a segfault?
    return NS_OK;
  }
  delete(buffer);
#endif

#if defined(XP_WIN)
  // Use GetProcessMemoryInfo, which only works on WinNT and later.
  /* This does not work?
  if (sGetMemInfo) {
    PROCESS_MEMORY_COUNTERS pmc;
    if (sGetMemInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
      //result->total = PRInt64(pmc.PagefileUsage);
      //result->resident = PRInt64(pmc.WorkingSetSize);
      *_retval = (PRInt64(pmc.PagefileUsage) / 1024);
      return NS_OK;
    }
  }
  */
#endif

#if defined(XP_MACOSX)
  // Use task_info

  task_basic_info_data_t ti;
  mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
  kern_return_t error = task_info(mach_task_self(), TASK_BASIC_INFO,
                                  (task_info_t) &ti, &count);
  if (error == KERN_SUCCESS) {
    //result->total = PRInt64(ti.virtual_size);
    //result->resident = PRInt64(ti.resident_size);
    *_retval = (PRInt64(ti.virtual_size) / 1024);
    return NS_OK;
  }
#endif
  
  return NS_OK; // If we reach here then _retval will equal 0.
}

NS_IMETHODIMP
flockSystemInfo::GetBuildInfo(nsAString &_retval) {
  // platform, firstrun, build_id, version, flags
#ifdef XP_WIN32
  #define PLATFORM "Windows"
#elif defined(__linux)
  #define PLATFORM "Unix"
#elif defined(XP_MACOSX) || defined(XP_MAC)
  #define PLATFORM "MAC"
#else
  #define PLATFORM "Unknown"
#endif
  
  nsresult rv = NS_OK;
  
  // Set the platform
  nsCOMPtr<nsIXULRuntime> appRunTime(do_GetService("@mozilla.org/xre/app-info;1", &rv));
  if (appRunTime) {
    nsCAutoString retOSName;
    appRunTime->GetOS(retOSName);
    CopyUTF8toUTF16(retOSName, _retval);
  } else {
    _retval = NS_LITERAL_STRING(PLATFORM);
  }
    
  // Set the firstrun status (NOT SOON ENOUGH?)
  _retval += NS_LITERAL_STRING(",");
  if (mFirstRun) {
    _retval += NS_LITERAL_STRING("1");
  } else {
    _retval += NS_LITERAL_STRING("0");
  }    
  
  // Set the build id of this application
  _retval += NS_LITERAL_STRING(",");
  _retval += NS_LITERAL_STRING("0");
  
  nsCOMPtr<nsIXULAppInfo> appInfo(do_GetService("@mozilla.org/xre/app-info;1", &rv));
  if (appInfo) {
    // Set the version of this application
    _retval += NS_LITERAL_STRING(",");
    nsCAutoString retVersion;
    appInfo->GetVersion(retVersion);
    _retval += NS_ConvertUTF8toUTF16(retVersion);
  
    // Set the flags (debug,optimized,static,shared)
    _retval += NS_LITERAL_STRING(",");
    nsCAutoString retName;
    appInfo->GetName(retName);
    _retval += NS_ConvertUTF8toUTF16(retName);
  }
  return NS_OK;
}

NS_IMETHODIMP
flockSystemInfo::GetStartTime(nsAString &_retval) {
  _retval = NS_ConvertUTF8toUTF16(PR_GetEnv("starttime"));
  return NS_OK;
}

NS_IMETHODIMP
flockSystemInfo::IsFrontProcess(PRBool *_retval) {
  *_retval = PR_TRUE;
#if defined (XP_MACOSX)
  *_retval = PR_FALSE;
  Boolean isForeground = PR_FALSE;
  ProcessSerialNumber psnSelf, psnFront;
  if (::GetCurrentProcess(&psnSelf) == noErr &&
      ::GetFrontProcess(&psnFront) == noErr &&
      ::SameProcess(&psnSelf, &psnFront, &isForeground) == noErr &&
      isForeground)
  {
    *_retval = PR_TRUE;
  }
#endif
  return NS_OK;
}

NS_IMETHODIMP
flockSystemInfo::SetFrontProcess() {
#if defined (XP_MACOSX)
  ProcessSerialNumber psn;
  if (::GetCurrentProcess(&psn) == noErr) {
    ::SetFrontProcess(&psn);
  }
#endif
  return NS_OK;
}
