/*
    Copyright (C) 2000 SAP AG

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
package com.sap.dbtech.util;

import java.math.*;
import java.sql.SQLException;
/**
 *
 */
public abstract class VDNNumber {
    private static final int zeroExponentValue_C = 128;
    private static final int tensComplement_C = 9;
    private static final int numberBytes_C = 20;
    private static final int numberDigits_C = 38;
    private static final String zerostring = "0000000000000000000000000000000000000000000000000000000000000000";

    /**
     *
     * @return byte[]
     * @param decimal java.math.BigDecimal
     */
    public static byte [] bigDecimal2number (BigDecimal decimal) {
        return bigDecimal2number (decimal, numberDigits_C);
    }



    public static String bigDecimal2PlainString(BigDecimal val) {
      String res;
      int scale =  val.scale();
      if (scale < 0){
        val = val.setScale(0);
        scale = 0;
      }
      if (scale == 0){
        res = val.toBigInteger().toString();
      } else{
         String unsignedIntVal = val.unscaledValue().abs().toString();
         String prefix = (val.signum()<0 ? "-0." : "0.");
        int pointPos = unsignedIntVal.length() - scale;
        if (pointPos == 0) {
            res = prefix + unsignedIntVal;
        } else if (pointPos > 0) {
            StringBuffer buf = new StringBuffer(unsignedIntVal);
            buf.insert(pointPos, '.');
            if (val.signum() < 0)
                buf.insert(0, '-');
            res = buf.toString();
        } else {
            StringBuffer buf = new StringBuffer(3-pointPos + unsignedIntVal.length());
            buf.append(prefix);
            for (int i=0; i<-pointPos; i++)
                buf.append('0');
            buf.append(unsignedIntVal);
            res = buf.toString();
        }
      }
      return res;
    }
    /**
     *
     * @return byte[]
     * @param decimal java.math.BigDecimal
     */
    public static byte [] bigDecimal2number (BigDecimal decimal, int validDigits) {
        byte [] number;
        String plain = bigDecimal2PlainString(decimal);
        int scale = (decimal.scale()<0) ? 0 : decimal.scale();
        char [] chars = plain.toCharArray ();
        boolean isNegative;
        int firstDigit;
        int exponent;
        int digitCount = chars.length;

        if (chars [0] == '-') {
            isNegative = true;
            firstDigit = 1;
        }
        else {
            isNegative = false;
            firstDigit = 0;
        }
        while (((chars [firstDigit] == '0') || (chars [firstDigit] == '.'))
                && (firstDigit < digitCount - 1)) {
            ++firstDigit;
        }
        exponent = chars.length - firstDigit - scale;
        digitCount = chars.length - firstDigit;
        if ((digitCount == 1) && (chars [firstDigit] == '0')) {
            return new byte[]{(byte) zeroExponentValue_C};
        }
        if ((exponent > 0) && (scale > 0)) {
            // adjust place taken by decimal point
            --exponent;
            --digitCount;
            System.arraycopy(chars, chars.length - scale, chars, chars.length - scale - 1, scale);
        }
        if ((digitCount > validDigits)) {
            if (chars [firstDigit + validDigits] >= '5') {
                ++chars [firstDigit + validDigits];
            }
            digitCount = validDigits;
        }
        for (int i = firstDigit; i < digitCount + firstDigit; ++i) {
            chars [i] -= '0';
        }
        number = new byte [digitCount + 1];
        packDigits (chars, firstDigit, digitCount, isNegative, number);
        if (isNegative) {
            exponent = 64 - exponent;
        }
        else {
            exponent += 192;
        }
        number [0] = (byte) exponent;
        return number;
    }
    /**
     *
     * @return byte[]
     * @param value int
     */
    public static byte [] long2number (long value) {
        boolean isNegative = false;
        int negativeVal = 1;
        byte [] number ;
        char [] scratch = new char [numberDigits_C + 1];
        char digit;
        int scratchPos = numberDigits_C - 1; 
        int exponent;

        if (value == 0) {
//            number [0] = (byte) zeroExponentValue_C;
//            return number;
           return new byte[]{(byte) zeroExponentValue_C};
        }
        if (value < 0) {
            negativeVal = -1;
            isNegative = true;
        }
        /*
         * calculate digits
         */
        while (value != 0) {
            digit =  (char) (negativeVal*(value % 10));
            scratch [scratchPos] = digit;
            value /= 10;
            --scratchPos;
        }
        exponent = numberDigits_C - scratchPos - 1;
        ++scratchPos;
        number = new byte [numberDigits_C - scratchPos +1 ];
        packDigits (scratch, scratchPos, numberDigits_C - scratchPos, isNegative, number);
        if (isNegative) {
            exponent = 64 - exponent;
        }
        else {
            exponent += 192;
        }
        number [0] = (byte) exponent;
        return number;
    }
    /**
     *
     * @return java.math.BigDecimal
     * @param rawNumber byte[]
     */
    public static BigDecimal number2BigDecimal(
        byte[] rawNumber) throws SQLException
    {
        BigDecimal result = null;
        int characteristic;
        int digitCount = (rawNumber.length - 1) * 2;
        int exponent;
        byte[] digits;
        int lastSignificant = 2;
        String numberString;
        try{
          characteristic = rawNumber[0] & 0xff;
          if (characteristic == zeroExponentValue_C) {
              return new BigDecimal (0);
          }
          digits = new byte [digitCount + 2];
          if (characteristic < zeroExponentValue_C) {
              exponent = - (characteristic - 64);
              digits [0] = (byte) '-';
              digits [1] = (byte) '.';

              for (int i = 1; i < rawNumber.length; ++i) {
                  int value;
                  value = ((((char) rawNumber [i]) & 0xff) >> 4);
                  if (value != 0) {
                      lastSignificant = i * 2;
                  }
                  digits [i * 2] = (byte) (tensComplement_C - value + '0');
                  value = (((char)rawNumber [i] ) & 0x0f);
                  if (value != 0) {
                      lastSignificant = i * 2 + 1;
                  }
                  digits [i * 2 + 1] = (byte) (tensComplement_C - value + '0');
              }
              ++digits [lastSignificant];
          }
          else {
              exponent = characteristic - 192;
              digits [0] = (byte) '0';
              digits [1] = (byte) '.';
              for (int i = 1; i < rawNumber.length; ++i) {
                  int value;
                  value = ((((char) rawNumber [i]) & 0xff) >> 4);
                  if (value != 0) {
                      lastSignificant = i * 2;
                  }
                  digits [i * 2] = (byte) (value + '0');
                  value = (((char)rawNumber [i] ) & 0x0f);
                  if (value != 0) {
                      lastSignificant = i * 2 + 1;
                  }
                  digits [i * 2 + 1] = (byte) (value + '0');
              }
          }
          numberString = new String(digits, 0, lastSignificant + 1);
          result = new BigDecimal(numberString);
          result = result.movePointRight(exponent);
          return result;
      }
      catch (Exception ex) {
         throw new com.sap.dbtech.jdbc.exceptions.SQLExceptionSapDB(
         MessageTranslator.translate(MessageKey.ERROR_CONVERSIONVDNnumber,
                                     Tracer.Hex2String(rawNumber)));
      }

    }

   /**
    * @return double
    */
    public static double shortnumber2Double(
        byte[] rawNumber) throws SQLException
    {
      try {
        long result=0;
        int characteristic;
        int exponent;
        boolean isNegative;

        characteristic = rawNumber[0] & 0xff;
        if (characteristic == zeroExponentValue_C) {
            return 0;
        }
        if (characteristic < zeroExponentValue_C) {
          exponent = - (characteristic - 64);
          isNegative = true;
          int value;
          int nullValCnt=1;

          for (int i = 1; i < rawNumber.length; ++i) {
              //first halfbyte
              value = ((((int) rawNumber [i]) & 0xff) >> 4);
              if (value == 9) {
                  nullValCnt++;
              }
              else {
                result *= double10pow[ nullValCnt+zeor10powindex];
                exponent-=nullValCnt;
                nullValCnt = 1;
                result += tensComplement_C - value;
              }

              //second halfbyte
              value = (((int)rawNumber [i] ) & 0x0f);
              if (value == 9) {
                  nullValCnt++;
              }
              else {
                result *= double10pow[ nullValCnt+zeor10powindex];
                exponent-=nullValCnt;
                nullValCnt = 1;
                result += tensComplement_C - value;
              }
          }
          ++result;
        }
        else {
          exponent = characteristic - 192;
          isNegative = false;
          int value;
          int nullValCnt=1;

          for (int i = 1; i < rawNumber.length; ++i) {
              //first halfbyte
              value = ((((int) rawNumber [i]) & 0xf0) >>> 4);
              if (value == 0) {
                  nullValCnt++;
              }
              else {
                result *= double10pow[ nullValCnt+zeor10powindex];
                exponent-=nullValCnt;
                nullValCnt = 1;
                result += value;
              }

              //second halfbyte
              value = (((int)rawNumber [i] ) & 0x0f);
              if (value == 0) {
                  nullValCnt++;
              }
              else {
                result *= double10pow[ nullValCnt+zeor10powindex];
                exponent-=nullValCnt;
                nullValCnt = 1;
                result += value;
              }
          }
        }
        double myresult = (double) result;
        myresult *= double10pow[exponent+zeor10powindex];
        if (isNegative) {
            myresult = -myresult;
        }
        return myresult;
      }
      catch (Exception ex) {
         throw new com.sap.dbtech.jdbc.exceptions.SQLExceptionSapDB(
         MessageTranslator.translate(MessageKey.ERROR_CONVERSIONVDNnumber,
                                     Tracer.Hex2String(rawNumber)));
      }
    }
    /**
     * @return long
     */
    public static long number2long (
        byte [] rawNumber) throws SQLException
    {
        long result = 0;
        int characteristic;
        int exponent;
        boolean isNegative;
        int numberDigits = rawNumber.length * 2 - 2;

        characteristic = rawNumber[0] & 0xff;
        if (characteristic == zeroExponentValue_C) {
            return 0;
        }
        if (characteristic < zeroExponentValue_C) {
            exponent = - (characteristic - 64);
            if ((exponent < 0) || (exponent > numberDigits)) {
                BigDecimal bigD = number2BigDecimal (rawNumber);
                return bigD.longValue();
            }
            isNegative = true;
            for (int i = 0; i < exponent; ++i) {
                int value = rawNumber [i / 2 + 1];
                if ((i % 2) == 0) {
                    value &= 0xf0;
                    value >>>=  4;
                }
                else {
                    value &= 0x0f;
                }
                result *= 10;
                result += tensComplement_C - value;
            }
            ++result;
        }
        else {
            exponent = characteristic - 192;
            if ((exponent < 0) || (exponent > numberDigits)) {
                BigDecimal bigD = number2BigDecimal (rawNumber);
                return bigD.longValue();
            }
            isNegative = false;
            for (int i = 0; i < exponent; ++i) {
                int value = rawNumber [i / 2 + 1];
                if ((i % 2) == 0) {
                    value &= 0xf0;
                    value >>>= 4;
                }
                else {
                    value &= 0x0f;
                }
                result *= 10;
                result += value;
            }
        }
        if (isNegative) {
            result = -result;
        }
        return result;
    }
    /**
     *
     * @return java.math.BigDecimal
     * @param rawNumber byte[]
     */
//    public static BigDecimal number2BigDecimal(byte[] rawNumber, int scale) throws SQLException{
//        return number2BigDecimal (rawNumber).setScale (scale);
//    }
    /**
     *
     * @return int
     * @param rawBytes byte[]
     */
    public static int number2int (byte [] rawBytes) throws SQLException{
        return (int) number2long(rawBytes);
    }
    /**
     *
     */
    private static void packDigits (
        char [] digits,
        int start,
        int count,
        boolean isNegative,
        byte [] number)
    {
        int lastDigit = start + count - 1;
        byte highNibble;
        byte lowNibble;

        if (isNegative) {
            // 10s complement
            for (int i = start; i < lastDigit; ++i) {
                digits [i] = (char) (9 - digits [i]);
            }
            digits [lastDigit] = (char) (10 - digits [lastDigit]);
            // handle overflow
            int digitPos = lastDigit;
            while (digits [digitPos] == 10) {
                digits [digitPos] = 0;
                ++digits [digitPos - 1];
                --digitPos;
            }
        }
        /*
         * pack digits into bytes
         */
        for (int i = 1; start <= lastDigit; ++i, start += 2) {
            highNibble = (byte) digits [start];
            if ((start + 1) <= lastDigit) {
                lowNibble = (byte) digits [start + 1];
            }
            else {
                lowNibble = 0;
            }
            number [i] = (byte) (highNibble << 4 | lowNibble);
        }
    }

    /**
     * Converts a VDN number directly into a string.
     * @param number The number.
     * @param fixedtype Indicator, whether the number is of a fixed type.
     * @param logicallength The logical length of the number (overall number of digits)
     * @param frac The number of fractional digits.
     */
    public static String number2string(byte[] number, boolean fixedtype, int logicalLength, int frac)
        throws SQLException
    {
        int characteristic;

        try {
            characteristic = number[0] & 0xFF;
            if(characteristic == zeroExponentValue_C) {
                return "0";
            }
            char digits[] = new char[logicalLength];
            int exponent;
            int lastsignificant=0;
            boolean isnegative=false;
            if(characteristic < zeroExponentValue_C) {
                isnegative=true;
                exponent = - (characteristic - 64);
                for(int i=0; i<logicalLength; ++i) {
                    int v1;
                    if(i % 2 == 0) {
                        v1 = (number[1 + i/2] & 0xff) >> 4;
                    } else {
                        v1 = (number[1 + i/2] & 0xF);
                    }
                    if(v1 != 0) {
                        lastsignificant=i;
                    }
                    digits[i]= (char) ((9 - v1) + '0');
                }
                ++digits[lastsignificant];
            } else {
                exponent = characteristic - 192;
                for(int i=0; i<logicalLength; ++i) {
                    int v1;
                    if(i % 2 == 0) {
                        v1 = (number[1 + i/2] & 0xFF) >> 4;
                    } else {
                        v1 = (number[1 + i/2] & 0xF);
                    }
                    if(v1 != 0) {
                        lastsignificant=i;
                    }
                    digits[i]= (char)((v1) + '0');
                }
            }
            String sign=(isnegative ? "-" : "");
            String numberstr=new String(digits, 0, lastsignificant+1);
            if(fixedtype) {
                if(exponent > 0) {
                    if(numberstr.length() < logicalLength) {
                        numberstr=numberstr + zerostring.substring(0, logicalLength - numberstr.length());
                    }
                    if(frac!=0) {
                        return sign + numberstr.substring(0, exponent) + "." + numberstr.substring(exponent, exponent + frac);
                    } else {
                        return sign + numberstr.substring(0, exponent);
                    }
                } else {
                    int zeroend=frac - (-exponent) - numberstr.length();
                    if(zeroend <0) zeroend = 0;
                    return sign + "0." +
                        zerostring.substring(0, -exponent) + numberstr + zerostring.substring(0, zeroend);
                }
            } else {
                if(exponent <-3 || exponent >7) {
                    return sign + "0." + numberstr + "E" + exponent;
                } else {
                    switch(exponent) {
                    case -3:
                        return sign + "0.000" + numberstr;
                    case -2:
                        return sign + "0.00" + numberstr;
                    case -1:
                        return sign + "0.0" + numberstr;
                    case 0:
                        return sign + "0." + numberstr;
                    }
                    if(numberstr.length() <= exponent) {
                        return sign + numberstr + zerostring.substring(0, exponent - numberstr.length());
                    } else {
                        return sign + numberstr.substring(0, exponent) + "." + numberstr.substring(exponent);
                    }
                }
            }
        } catch(Exception ex) {
            throw new com.sap.dbtech.jdbc.exceptions.SQLExceptionSapDB
                (MessageTranslator.translate(MessageKey.ERROR_CONVERSIONVDNnumber,
                                             Tracer.Hex2String(number)));
        }
    }



    /**
     *
     */
    static public void
    dumpVDN (
        String comment,
        byte [] number)
    {
        StructuredBytes struct = new StructuredBytes (number);
        System.out.println (comment); //#print
        struct.traceOn (System.out);
    }

    private static final int zeor10powindex = 64;
    private static final double double10pow[] = {
                1.0e-64, 1.0e-63, 1.0e-62, 1.0e-61,
       1.0e-60, 1.0e-59, 1.0e-58, 1.0e-57, 1.0e-56,
       1.0e-55, 1.0e-54, 1.0e-53, 1.0e-52, 1.0e-51,
       1.0e-50, 1.0e-49, 1.0e-48, 1.0e-47, 1.0e-46,
       1.0e-45, 1.0e-44, 1.0e-43, 1.0e-42, 1.0e-41,
       1.0e-40, 1.0e-39, 1.0e-38, 1.0e-37, 1.0e-36,
       1.0e-35, 1.0e-34, 1.0e-33, 1.0e-32, 1.0e-31,
       1.0e-30, 1.0e-29, 1.0e-28, 1.0e-27, 1.0e-26,
       1.0e-25, 1.0e-24, 1.0e-23, 1.0e-22, 1.0e-21,
       1.0e-20, 1.0e-19, 1.0e-18, 1.0e-17, 1.0e-16,
       1.0e-15, 1.0e-14, 1.0e-13, 1.0e-12, 1.0e-11,
       1.0e-10, 1.0e-9 , 1.0e-8 , 1.0e-7 , 1.0e-6 ,
       1.0e-5 , 1.0e-4 , 1.0e-3 , 1.0e-2 , 1.0e-1 ,
       1.0e0,
       1.0e1 , 1.0e2 , 1.0e3 , 1.0e4 , 1.0e5,
       1.0e6 , 1.0e7 , 1.0e8 , 1.0e9 , 1.0e10,
       1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15,
       1.0e16, 1.0e17, 1.0e18, 1.0e19, 1.0e20,
       1.0e21, 1.0e22, 1.0e23, 1.0e24, 1.0e25,
       1.0e26, 1.0e27, 1.0e28, 1.0e29, 1.0e30,
       1.0e31, 1.0e32, 1.0e33, 1.0e34, 1.0e35,
       1.0e36, 1.0e37, 1.0e38, 1.0e39, 1.0e40,
       1.0e41, 1.0e42, 1.0e43, 1.0e44, 1.0e45,
       1.0e46, 1.0e47, 1.0e48, 1.0e49, 1.0e50,
       1.0e51, 1.0e52, 1.0e53, 1.0e54, 1.0e55,
       1.0e56, 1.0e57, 1.0e58, 1.0e59, 1.0e60,
       1.0e61, 1.0e62, 1.0e63, 1.0e64,
    };
}
