/* human.c -- print human readable file size
   Copyright (C) 1996, 1997, 1998 Free Software Foundation, Inc.

   This program 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, or (at your option)
   any later version.

   This program 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 this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

/* Originally contributed by lm@sgi.com;
   --si, output block size selection, and large file support
   added by eggert@twinsun.com.  */

#if HAVE_CONFIG_H
# include <config.h>
#endif

#if HAVE_INTTYPES_H
# include <inttypes.h>
#endif

#include <sys/types.h>
#include <stdio.h>

#if HAVE_LIMITS_H
# include <limits.h>
#endif

#ifndef CHAR_BIT
# define CHAR_BIT 8
#endif
#if HAVE_STDLIB_H
# include <stdlib.h>
#endif

#ifndef HAVE_DECL_GETENV
char *getenv ();
#endif

#if ENABLE_NLS
# include <libintl.h>
# define _(Text) gettext (Text)
#else
# define _(Text) Text
#endif

#include <argmatch.h>
#include <error.h>
#include <xstrtoul.h>

#include "human.h"

static const char suffixes[] =
{
  0,	/* not used */
  'k',	/* kilo */
  'M',	/* Mega */
  'G',	/* Giga */
  'T',	/* Tera */
  'P',	/* Peta */
  'E',	/* Exa */
  'Z',	/* Zetta */
  'Y'	/* Yotta */
};

/* Convert N to a human readable format in BUF.

   N is expressed in units of FROM_BLOCK_SIZE.  FROM_BLOCK_SIZE must
   be positive.

   If OUTPUT_BLOCK_SIZE is positive, use units of OUTPUT_BLOCK_SIZE in
   the output number.  OUTPUT_BLOCK_SIZE must be a multiple of
   FROM_BLOCK_SIZE or vice versa.

   If OUTPUT_BLOCK_SIZE is negative, use a format like "127k" if
   possible, using powers of -OUTPUT_BLOCK_SIZE; otherwise, use
   ordinary decimal format.  Normally -OUTPUT_BLOCK_SIZE is either
   1000 or 1024; it must be at least 2.  Most people visually process
   strings of 3-4 digits effectively, but longer strings of digits are
   more prone to misinterpretation.  Hence, converting to an
   abbreviated form usually improves readability.  Use a suffix
   indicating which power is being used.  For example, assuming
   -OUTPUT_BLOCK_SIZE is 1024, 8500 would be converted to 8.3k,
   133456345 to 127M, 56990456345 to 53G, and so on.  Numbers smaller
   than -OUTPUT_BLOCK_SIZE aren't modified.  */

char *
human_readable (uintmax_t n, char *buf,
		int from_block_size, int output_block_size)
{
  uintmax_t amt;
  int base;
  int to_block_size;
  int tenths;
  int power;
  char *p;

  /* 0 means adjusted N == AMT.TENTHS;
     1 means AMT.TENTHS < adjusted N < AMT.TENTHS + 0.05;
     2 means adjusted N == AMT.TENTHS + 0.05;
     3 means AMT.TENTHS + 0.05 < adjusted N < AMT.TENTHS + 0.1.  */
  int rounding;

  if (output_block_size < 0)
    {
      base = -output_block_size;
      to_block_size = 1;
    }
  else
    {
      base = 0;
      to_block_size = output_block_size;
    }

  p = buf + LONGEST_HUMAN_READABLE;
  *p = '\0';

#ifdef lint
  /* Suppress `used before initialized' warning.  */
  power = 0;
#endif

  /* Adjust AMT out of FROM_BLOCK_SIZE units and into TO_BLOCK_SIZE units.  */

  if (to_block_size <= from_block_size)
    {
      int multiplier = from_block_size / to_block_size;
      amt = n * multiplier;
      tenths = rounding = 0;

      if (amt / multiplier != n)
	{
	  /* Overflow occurred during multiplication.  We should use
	     multiple precision arithmetic here, but we'll be lazy and
	     resort to floating point.  This can yield answers that
	     are slightly off.  In practice it is quite rare to
	     overflow uintmax_t, so this is good enough for now.  */

	  double damt = n * (double) multiplier;

	  if (! base)
	    sprintf (buf, "%.0f", damt);
	  else
	    {
	      double e = 1;
	      power = 0;

	      do
		{
		  e *= base;
		  power++;
		}
	      while (e * base <= damt && power < sizeof suffixes - 1);

	      damt /= e;

	      sprintf (buf, "%.1f%c", damt, suffixes[power]);
	      if (4 < strlen (buf))
		sprintf (buf, "%.0f%c", damt, suffixes[power]);
	    }

	  return buf;
	}
    }
  else
    {
      int divisor = to_block_size / from_block_size;
      int r10 = (n % divisor) * 10;
      int r2 = (r10 % divisor) * 2;
      amt = n / divisor;
      tenths = r10 / divisor;
      rounding = r2 < divisor ? 0 < r2 : 2 + (divisor < r2);
    }


  /* Use power of BASE notation if adjusted AMT is large enough.  */

  if (base && base <= amt)
    {
      power = 0;

      do
	{
	  int r10 = (amt % base) * 10 + tenths;
	  int r2 = (r10 % base) * 2 + (rounding >> 1);
	  amt /= base;
	  tenths = r10 / base;
	  rounding = (r2 < base
		      ? 0 < r2 + rounding
		      : 2 + (base < r2 + rounding));
	  power++;
	}
      while (base <= amt && power < sizeof suffixes - 1);

      *--p = suffixes[power];

      if (amt < 10)
	{
	  tenths += 2 < rounding + (tenths & 1);

	  if (tenths == 10)
	    {
	      amt++;
	      tenths = 0;
	    }

	  if (amt < 10)
	    {
	      *--p = '0' + tenths;
	      *--p = '.';
	      tenths = 0;
	    }
	}
    }

  if (5 < tenths + (2 < rounding + (amt & 1)))
    {
      amt++;

      if (amt == base && power < sizeof suffixes - 1)
	{
	  *p = suffixes[power + 1];
	  *--p = '0';
	  *--p = '.';
	  amt = 1;
	}
    }

  do
    *--p = '0' + (int) (amt % 10);
  while ((amt /= 10) != 0);

  return p;
}


/* The default block size used for output.  This number may change in
   the future as disks get larger.  */
#ifndef DEFAULT_BLOCK_SIZE
# define DEFAULT_BLOCK_SIZE 1024
#endif

static char const *const block_size_args[] = { "human-readable", "si", 0 };
static int const block_size_types[] = { -1024, -1000 };

static strtol_error
humblock (char const *spec, int *block_size)
{
  int i;

  if (! spec && ! (spec = getenv ("BLOCK_SIZE")))
    *block_size = getenv ("POSIXLY_CORRECT") ? 512 : DEFAULT_BLOCK_SIZE;
  else if (0 <= (i = argmatch (spec, block_size_args)))
    *block_size = block_size_types[i];
  else
    {
      char *ptr;
      unsigned long val;
      strtol_error e = xstrtoul (spec, &ptr, 0, &val, "eEgGkKmMpPtTyYzZ0");
      if (e != LONGINT_OK)
	return e;
      if (*ptr)
	return LONGINT_INVALID_SUFFIX_CHAR;
      if ((int) val < 0 || val != (int) val)
	return LONGINT_OVERFLOW;
      *block_size = (int) val;
    }

  return LONGINT_OK;
}

void
human_block_size (char const *spec, int report_errors, int *block_size)
{
  strtol_error e = humblock (spec, block_size);
  if (e != LONGINT_OK && report_errors)
    STRTOL_FATAL_ERROR (spec, _("block size"), e);
}
