/*
 * ipmi_sensor_factors.cpp
 *
 * Copyright (c) 2004 by FORCE Computers
 * Copyright (c) 2005 by ESO Technologies.
 *
 * Note that this file is based on parts of OpenIPMI
 * written by Corey Minyard <minyard@mvista.com>
 * of MontaVista Software. Corey's code was helpful
 * and many thanks go to him. He gave the permission
 * to use this code in OpenHPI under BSD license.
 *
 * 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.  This
 * file and program are licensed under a BSD style license.  See
 * the Copying file included with the OpenHPI distribution for
 * full licensing terms.
 *
 * Authors:
 *     Thomas Kanngieser <thomas.kanngieser@fci.com>
 *     Pierre Sangouard  <psangouard@eso-tech.com>
 */


#include "ipmi_sensor_factors.h"
#include <math.h>


static const char *analoge_data_format[] =
{
  "Unsigned",
  "1Compl",
  "2Compl",
  "Analog"
};


const char *
IpmiAnalogeDataFormatToString( tIpmiAnalogeDataFormat fmt )
{
  if ( (int)fmt <= eIpmiAnalogDataFormatNotAnalog )
       return analoge_data_format[fmt];

  return "Invalid";
}


static const char *linearization_map[] =
{
  "Linear",
  "Ln",
  "Log10",
  "Log2",
  "E",
  "Exp10",
  "Exp2",
  "1OverX",
  "Sqr",
  "Cube",
  "Sqrt",
  "1OverCube"
};


const char *
IpmiLinearizationToString( tIpmiLinearization val )
{
  if ( val == eIpmiLinearizationNonlinear )
       return "NonLinear";

  if ( val <= eIpmiLinearization1OverCube )
       return linearization_map[val];

  return "Invalid";
}


cIpmiSensorFactors::cIpmiSensorFactors()
  : m_analog_data_format( eIpmiAnalogDataFormatUnsigned ),
    m_linearization( eIpmiLinearizationLinear ),
    m_is_non_linear( false ),
    m_m( 0 ),
    m_tolerance( 0 ),
    m_b( 0 ),
    m_r_exp( 0 ),
    m_accuracy_exp( 0 ),
    m_accuracy( 0 ),
    m_b_exp( 0 )
{
}


cIpmiSensorFactors::~cIpmiSensorFactors()
{
}


bool
cIpmiSensorFactors::GetDataFromSdr( const cIpmiSdr *sdr )
{
  m_analog_data_format = (tIpmiAnalogeDataFormat)((sdr->m_data[20] >> 6) & 3);
  m_linearization = (tIpmiLinearization)(sdr->m_data[23] & 0x7f);

  if ( m_linearization <= 11 )
     {
       m_m            = sdr->m_data[24] | ((sdr->m_data[25] & 0xc0) << 2);
       m_tolerance    = sdr->m_data[25] & 0x3f;
       m_b            = sdr->m_data[26] | ((sdr->m_data[27] & 0xc0) << 2);
       m_accuracy     = ((sdr->m_data[27] & 0x3f)
                            | ((sdr->m_data[28] & 0xf0) << 2));
       m_accuracy_exp = (sdr->m_data[28] >> 2) & 0x3;
       m_r_exp        = (sdr->m_data[29] >> 4) & 0xf;
       m_b_exp        = sdr->m_data[29] & 0xf;
       m_accuracy_factor = (m_accuracy * pow( 10.0, m_accuracy_exp)) / 100.0;
     }

  if ( m_linearization == eIpmiLinearizationLinear )
      m_is_non_linear = false;
  else
      m_is_non_linear = true;

  return true;
}


bool
cIpmiSensorFactors::Cmp( const cIpmiSensorFactors &sf ) const
{
  if ( m_analog_data_format != sf.m_analog_data_format )
       return false;

  if ( m_linearization != sf.m_linearization )
       return false;

  if ( m_linearization <= 11 )
     {
       if ( m_m            != sf.m_m            )
            return false;

       if ( m_tolerance    != sf.m_tolerance    )
            return false;

       if ( m_b            != sf.m_b            )
            return false;

       if ( m_accuracy     != sf.m_accuracy     )
            return false;

       if ( m_accuracy_exp != sf.m_accuracy_exp )
            return false;

       if ( m_r_exp        != sf.m_r_exp        )
            return false;

       if ( m_b_exp        != sf.m_b_exp        )
            return false;
     }

  return true;
}


static double
c_linear( double val )
{
  return val;
}


static double
c_exp10( double val )
{
  return pow( 10.0, val );
}


static double
c_exp2( double val )
{
  return pow( 2.0, val );
}


static double
c_1_over_x( double val )
{
  return 1.0 / val;
}


static double
c_sqr( double val )
{
  return pow( val, 2.0 );
}


static double
c_cube( double val )
{
  return pow( val, 3.0 );
}


static double
c_1_over_cube( double val )
{
  return 1.0 / pow( val, 3.0 );
}

// We have to define our own log2 function
// because some versions of FreeBSD do not provide it
// and some versions of FreeBSD provide it in a very
// specific way.
static double ipmi_log2( double val )
{
  return log( val ) / M_LN2;
}

typedef double (*linearizer)( double val );
static linearizer linearize[12] =
{
  c_linear,
  log,
  log10,
  ipmi_log2,
  exp,
  c_exp10,
  c_exp2,
  c_1_over_x,
  c_sqr,
  c_cube,
  sqrt,
  c_1_over_cube
};


static int
sign_extend( int m, int bits )
{
    if ( m & (1 << (bits-1)) )
	return m | (-1 << bits);
    else
	return m & (~(-1 << bits));
}


bool
cIpmiSensorFactors::ConvertFromRaw( unsigned int val,
                                    double      &result,
                                    bool        is_hysteresis) const
{
  double m, b, b_exp, r_exp, fval;
  linearizer c_func;

  if ( m_linearization == eIpmiLinearizationNonlinear )
       c_func = c_linear;
  else if ( m_linearization <= 11 )
       c_func = linearize[m_linearization];
  else
       return false;

  val &= 0xff;

  m     = m_m;
  b     = m_b;
  r_exp = m_r_exp;
  b_exp = m_b_exp;

  if ( is_hysteresis == true )
  {
      if ( val == 0 )
      {
          result = 0;
          return true;
      }
      // For hysteresis : no offset + abs value
      b = 0;
      if ( m < 0 )
          m = -m;
  }

  switch( m_analog_data_format )
     {
       case eIpmiAnalogDataFormatUnsigned:
	    fval = val;
	    break;

       case eIpmiAnalogDataFormat1Compl:
	    val = sign_extend( val, 8 );
	    if ( val == 0xffffffff )
                 val += 1;

	    fval = val;
	    break;

       case eIpmiAnalogDataFormat2Compl:
	    fval = sign_extend( val, 8 );
	    break;

       default:
	    return false;
     }

  result = c_func( ((m * fval) + (b * pow(10, b_exp))) * pow(10, r_exp) );

  return true;
}


bool
cIpmiSensorFactors::ConvertToRaw( tIpmiRound    rounding,
                                  double        val,
                                  unsigned int &result,
                                  bool        is_hysteresis,
                                  bool        swap_thresholds ) const
{
  bool rv;
  bool swap;
  double cval;
  int    lowraw, highraw, raw, maxraw, minraw, next_raw;

  if (is_hysteresis == true)
    swap = false;
  else
    swap = swap_thresholds;

  switch( m_analog_data_format )
     {
       case eIpmiAnalogDataFormatUnsigned:
	    lowraw   = 0;
	    highraw  = 255;
	    minraw   = 0;
	    maxraw   = 255;
	    next_raw = 128;
	    break;

       case eIpmiAnalogDataFormat1Compl:
	    lowraw   = -127;
	    highraw  = 127;
	    minraw   = -127;
	    maxraw   = 127;
	    next_raw = 0;
	    break;

       case eIpmiAnalogDataFormat2Compl:
	    lowraw   = -128;
	    highraw  = 127;
	    minraw   = -128;
	    maxraw   = 127;
	    next_raw = 0;
	    break;

       default:
	    return false;
    }

  // We do a binary search for the right value.  Yuck, but I don't
  // have a better plan that will work with non-linear sensors.
  do
     {
       raw = next_raw;
       rv = ConvertFromRaw( raw, cval, is_hysteresis );

       if ( !rv )
	    return false;

       // If swap == true, when raw value increases
       // the corresponding interpreted value decreases
       // so we have to take that into account when searching
       if ((( swap == false) && ( cval < val ))
            || (( swap == true) && ( cval > val )))
          {
	    next_raw = ((highraw - raw) / 2) + raw;
	    lowraw = raw;
          } 
       else
          {
	    next_raw = ((raw - lowraw) / 2) + lowraw;
	    highraw = raw;
          }
     }
  while( raw != next_raw );

  // The above loop gets us to within 1 of what it should be, we
  // have to look at rounding to make the final decision.
  switch( rounding )
     {
       case eRoundNormal:
            // If swap == true, when raw value increases
            // the corresponding interpreted value decreases
            // so we have to take that into account when searching
            if ((( swap == false ) && ( val > cval ))
                || (( swap == true ) && ( val < cval )))
               {
                 if ( raw < maxraw )
                    {
                      double nval;

                      rv = ConvertFromRaw( raw + 1, nval, is_hysteresis );
                      if ( !rv )
                           return false;

                      nval = cval + ((nval - cval) / 2.0);
                      if ((( swap == false ) && ( val >= nval ))
                            || (( swap == true ) && ( val <= nval )))
                           raw++;
                    }
               }
            else
               {
                 if ( raw > minraw )
                    {
                      double pval;
                      rv = ConvertFromRaw( raw-1, pval, is_hysteresis );
                      if ( !rv )
                           return false;

                      pval = pval + ((cval - pval) / 2.0);
                      if ((( swap == false ) && ( val < pval ))
                            || (( swap == true ) && ( val > pval )))
                           raw--;
                    }
               }
	    break;

       case eRoundUp:
            // If swap == true, when raw value increases
            // the corresponding interpreted value decreases
            // so we have to take that into account when searching
            if ( swap == false )
               {
               if ((val > cval) && (raw < maxraw))
                    raw++;
               }
            else
               {
               if ((val < cval) && (raw < maxraw))
                    raw++;
               };
	    break;

       case eRoundDown:
            // If swap == true, when raw value increases
            // the corresponding interpreted value decreases
            // so we have to take that into account when searching
            if ( swap == false )
               {
                if ( ( val < cval) && (raw > minraw ) )
                    raw--;
               }
            else
               {
                if ( ( val > cval) && (raw > minraw ) )
                    raw--;
               };
	    break;
     }

  if ( m_analog_data_format == eIpmiAnalogDataFormat1Compl )
       if ( raw < 0 )
	    raw -= 1;

  result = raw & 0xff;
  return true;
}

