/*  unit.c - functions for metric conversions 
 *  Copyright (C) 1999, 2000 Ian Haywood (ihaywood@gnu.org)
 *
 *  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 of the License, 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
 */

#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gnome.h>
#include <glade/glade.h>
#include "text_util.h"
#include "food.h"
#include "recipe_win.h"
#include "support.h"
#include "unit.h"

/* NOTE: the measurements are drawn form the USDA datafile measure.txt*
 * Conversion is in two phases:
 * Phase I: all Imperial unit expressions are removed and replaced with 
 * the equivalent metric unit, so "1 oz" becomes "1 g", etc.
 * Phase II: if Imperial units are used to express the unit, they are 
 * converted to a same AMOUNT in metric, so 1 bar (1" thick), becomes
 * 1 bar (2.6cm thick), by the seperate function scale () */

struct unit
{
  char *imperial;
  char *metric; 
  float ratio; /* the ratio of metric on imperial */
};

#define NUMUNITS 15

struct unit unit_list[NUMUNITS] = 
{
{"fl oz", "ml", 29.57},
{"pint", "ml", 473.15},
{"oz", "g", 28.35},
{"foot", "cm", 30.48},
{"feet", "cm", 30.48},
{"'", "cm", 30.48},
{"\"", "mm", 24.4},
{"in ", "mm ", 24.4},
{"cubic inch", "ml", 16.39},
{"proof", "%alc/vol", 2},
{"lb", "kg", 0.4536},
{"per lb", "per kg", 2.205},
{"/lb", "per kg", 2.205},
{"per pound", "per kg"},
{"cup", "cup", 1}, /* fake unit so "1 oz (2/3 cup)" is scaled */
};

#define MAXUNITNAMELEN 100 /* know from USDA database */

void
make_conv (char *, float, struct unit *);

/* using the given unit and value, an expression of the metric unit
 * is written into the buffer pointed to by arg 1 */

float
get_expr (char *, char *, char **, char **);

/* searches in arg 1 for arg 2. If found, the preceding number is
 * returned, its location returned in args 3 and 4. 
 * (-1 if not found) */

void
scale (char *string, float ratio);

/* scales all the units mentioned in its string by the factor */ 

/* function bodies */

void
metricate (gchar *imp_name, gchar **met_name, gfloat *factor)

{
  char buffer[MAXUNITNAMELEN];
  int i;
  float amount;
  char *begin, *end;
  gboolean phaseI = FALSE;

  for (i = 0; i<NUMUNITS; i++)
    {
      amount = get_expr (imp_name, unit_list[i].imperial, &begin, &end);
      if (begin == imp_name
	  && amount != -1) /* start of string */
	{ 
	  /* Phase I */
	  snprintf (buffer, MAXUNITNAMELEN, "%s%s", unit_list[i].metric, end);
	  scale (buffer, unit_list[i].ratio*amount);
	  *factor = unit_list[i].ratio*amount;
	  phaseI = TRUE;
	}
    }
  /* Phase II */
  if (!phaseI)
    { /* for something like "1 box (12 fl oz)" */
      strcpy( buffer, imp_name);
      scale( buffer, 1.0);
      *factor = 1.0;
    }
  *met_name = g_strdup( buffer); 
}


float 
get_expr (char *buffer, char *search, char **begin, char **end)

{
  float r = -1, denominator = 1, numerator = 0, index = 1, upper = -1;
  char *lastchar; /* last char in numeric expression */
  
  lastchar = strstr (buffer, search);
  if (lastchar != NULL)
    {
      *end = lastchar + strlen (search);
      if (lastchar > buffer) 
	/* transverse spaces between unit name and its value */
	{
	  lastchar--;
	  while ( (*lastchar == ' ' || *lastchar == '-') && lastchar > buffer)
	    lastchar--;
	}
      if (lastchar == buffer)
	{
	  /* unit is at start of string -- assume amount is 1 */
	  *begin = buffer;
	  return (1.0);
	}
      if (isdigit (*lastchar)) /* if we have a value */
	/* note this is where I deal with the conflict of "fl oz" and "oz".
	 * if we have found the "oz" in "fl oz" , then lastchar would now 
	 * point to the 'l' -- and so would fail */
	{
	  lastchar++;
	  do
	    {
	      lastchar--;
	      if (isdigit (*lastchar))
		{
		  numerator = numerator + (index * (*lastchar - '0'));
		  index *= 10;
		  *begin = lastchar;
		}
	      if (*lastchar == '/')
		{
		  denominator = numerator;
		  numerator = 0;
		  index = 1;
		}
	      if (*lastchar == '.')
		{
		  numerator = numerator / index;
		  index = 1;
		  *begin = lastchar;
		}
	      if (*lastchar == '-')
		{
		  if (denominator == 1)
		    /* this means something like 6-8 */
		    {
		      upper = numerator;
		      numerator  = 0;
		      index = 1;
		    }
		  else
		    {
		      /* means 1-1/2 */
		      numerator = numerator/denominator;
		      index = 1;
		      denominator = 1;
		    }
		}
	    }
	  while (lastchar > buffer && (isdigit (*lastchar) 
				    || *lastchar == '.' || *lastchar == '/'
				    || *lastchar == '-' || *lastchar == ' '));
	  r = numerator / denominator;
	  if (upper != -1)
	    {
	      if (r == 0)
		r = upper/denominator; 
	      /* for "1 in - 2 in", '-' not part of number */
	      else
		r = (r+upper)/2; /* average out */
	    }
	}
    }
  return r;
}
		  
void
scale (char *string, float ratio)

{
  int i;
  float amount;
  char *begin, *end, *searchpoint;
  char new_unit[MAXUNITNAMELEN];

  i = 0;
  searchpoint = string;
  while (i<NUMUNITS)
    {
      amount = get_expr (searchpoint, unit_list[i].imperial, &begin, &end);
      if (amount == -1)
	{
	  i++;
	  searchpoint = string;
	}
      else
	{
	  make_conv (new_unit, amount/ratio, unit_list+i);
	  searchpoint = begin + strlen (new_unit);
	  /* insert new unit */
	  strcat (new_unit, end);
	  strcpy (begin, new_unit);
	}
    }
}

void
make_conv (char *buffer, float amount, struct unit *unit)

{
   float value;
   char *name;
   double dummy; /* for modf () to suck on */

   value = amount*unit->ratio;
   name = unit->metric;

   if (value == 1.0)
     snprintf (buffer, MAXUNITNAMELEN, "%s", name);
   else if (modf (value, &dummy) == 0) /* if integer */
     snprintf (buffer, MAXUNITNAMELEN, "%.0f %s", value, name);
   else if (value < 2)
     snprintf (buffer, MAXUNITNAMELEN, "%.2f %s", value, name);
   else if (value < 10)
     snprintf (buffer, MAXUNITNAMELEN, "%.1f %s", value, name);
   else if (value < 100)
     snprintf (buffer, MAXUNITNAMELEN, "%.0f %s", value, name);
   else 
     snprintf (buffer, MAXUNITNAMELEN, "%.0f %s", 
	      modf (value/5, &dummy) < 0.5 /* round to the nearest 5 */
	        ? floor (value/5)*5 
	        : ceil (value/5)*5, name); 
}

/* relict code  */

#if 0

/* code to test the unit converter, functions as a filter */

main ()

{
  char line[100];
  node *list = NULL, *old, this;

  this.string = line;
  this.unit = 1;
  this.next = NULL;

  while (!feof (stdin))
    {
      gets (line);
      metricate (&list, &this);
      while (list != NULL)
	{
	  printf ("%s\n", list->string);
	  old = list;
	  list = old->next;
	  free (old);
	}
    }
  return 0;
}
  
#endif 









