/*
 *  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 long EXPONENT_BASE;
        long long MAX_EXPONENT;
        long long MIN_EXPONENT;
        long long MANTISSA_MASK;
        long long sign;
        long long exponent;
        long 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
         * @return 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 lower-case 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
         * @return the parsed double value
         */
        static double parseDouble(const std::string& hexString);

        /*
         * Parses the hex string to a float number.
         * @param hexString - string to parse
         * @return 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
         * @return 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_*/
