/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#ifndef _DECAF_INTERNAL_UTIL_HEXSTRINGPARSER_H_
#define _DECAF_INTERNAL_UTIL_HEXSTRINGPARSER_H_

#include <decaf/util/Config.h>
#include <string>

namespace decaf{
namespace internal{
namespace util{

    class HexStringParser {
    private:

        static const unsigned int DOUBLE_EXPONENT_WIDTH = 11;
        static const unsigned int DOUBLE_MANTISSA_WIDTH = 52;
        static const unsigned int FLOAT_EXPONENT_WIDTH = 8;
        static const unsigned int FLOAT_MANTISSA_WIDTH = 23;
        static const unsigned int HEX_RADIX = 16;
        static const unsigned int MAX_SIGNIFICANT_LENGTH = 15;

        static const std::string HEX_SIGNIFICANT;
        static const std::string BINARY_EXPONENT;
        static const std::string FLOAT_TYPE_SUFFIX;
        static const std::string HEX_PATTERN;

        // TODO
        //static final Pattern PATTERN = Pattern.compile(HEX_PATTERN);

    private:

        int EXPONENT_WIDTH;
        int MANTISSA_WIDTH;
        long EXPONENT_BASE;
        long MAX_EXPONENT;
        long MIN_EXPONENT;
        long MANTISSA_MASK;
        long sign;
        long exponent;
        long mantissa;
        std::string abandonedNumber;

    public:

        /**
         * Create a new HexParser
         * @param exponentWidth - Width of the exponent for the type to parse
         * @param mantissaWidth - Width of the mantissa for the type to parse
         */
        HexStringParser( int exponentWidth, int mantissaWidth );

        virtual ~HexStringParser() {}

        /**
         * Parses a hex string using the specs given in the constructor
         * and returns a long long with the bits of the parsed string, the
         * caller can then convert those to a float or doulbe as needed.
         * @param hexString - string to parse
         * @returns the bits parsed from the string
         */
        long long parse( const std::string& hexString );

    private:

        /*
         * Parses the sign field.
         * @param sign string to parse
         */
        void parseHexSign( const std::string& signStr ) {
            this->sign = signStr.compare("-") == 0 ? 1 : 0;
        }

        /*
         * Parses the exponent field.
         * @param exponent string to parse
         */
        void parseExponent( const std::string& exponentStr );

        /*
         * Parses the mantissa field.
         * @param mantissa string to parse
         */
        void parseMantissa( const std::string& significantStr );

        void setInfinite() {
            exponent = MAX_EXPONENT;
            mantissa = 0;
        }

        void setZero() {
            exponent = 0;
            mantissa = 0;
        }

        /*
         * Sets the exponent variable to Long::MAX_VALUE or -Long::MAX_VALUE if
         * overflow or underflow happens.
         * @param the offset to set
         */
        void checkedAddExponent( long long offset );

        void processNormalNumber();
        void processSubNormalNumber();
        int countBitsLength( long long value );

        /*
         * Adjusts the mantissa to desired width for further analysis.
         */
        void fitMantissaInDesiredWidth( int desiredWidth );

        /*
         * Stores the discarded bits to abandonedNumber.
         */
        void discardTrailingBits( long long num );

        /*
         * The value is rounded up or down to the nearest infinitely precise result.
         * If the value is exactly halfway between two infinitely precise results,
         * then it should be rounded up to the nearest infinitely precise even.
         */
        void round();

        /*
         * Returns the normalized significand after removing the leading zeros.
         */
        std::string getNormalizedSignificand( const std::string& strIntegerPart,
                                              const std::string& strDecimalPart );

        /*
         * Calculates the offset between the normalized number and unnormalized
         * number. In a normalized representation, significand is represented by the
         * characters "0x1." followed by a lowercase hexadecimal representation of
         * the rest of the significand as a fraction.
         */
        int getOffset( const std::string& strIntegerPart,
                       const std::string& strDecimalPart );

    public:  // Statics

        /*
         * Parses the hex string to a double number.
         * @param hexString - string to parse
         * @returns the parsed double value
         */
        static double parseDouble( const std::string& hexString );

        /*
         * Parses the hex string to a float number.
         * @param hexString - string to parse
         * @returns the parsed float value
         */
        static float parseFloat( const std::string& hexString );


    private:  // Static

        /*
         * Analyzes the hex string and extracts the sign and digit segments.
         * @param hexString - string to parse
         * @returns array of three strings holding the segments caller owns
         */
        static std::string* getSegmentsFromHexString( const std::string& hexString );

        std::string& replaceFirst( std::string& target,
                                   const std::string& find,
                                   const std::string& replace ) {

            std::string::size_type pos = std::string::npos;

            if( ( pos = target.find_first_of( find, 0 ) ) != std::string::npos ) {
                return target.replace( pos, find.length(), replace );
            }

            return target;
        }

        std::string& replaceAll( std::string& target,
                                 const std::string& find,
                                 const std::string& replace ) {

            std::string::size_type pos = std::string::npos;
            while( ( pos = target.find( find ) ) != std::string::npos ) {
                target.replace( pos, find.length(), replace );
            }

            return target;
        }

    };

}}}

#endif /*_DECAF_INTERNAL_UTIL_HEXSTRINGPARSER_H_*/
