/*
 * Copyright (c) 2008-2015 Freescale Semiconductor, Inc.
 * Copyright 2016-2018 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "Version.h"
#include "EndianUtilities.h"

using namespace elftosb;

/*!
 * Parses a string in the form "xxx.xxx.xxx" (where x is a digit) into
 * three version fields for major, minor, and revision. The output is
 * right aligned BCD in host-natural byte order.
 *
 * \param versionString String containing the version.
 */
void version_t::set(const std::string &versionString)
{
    size_t length = versionString.size();
    unsigned version = 0;
    unsigned index = 0;

    typedef enum
    {
        kVersionStateNone,
        kVersionStateMajor,
        kVersionStateMinor,
        kVersionStateRevision
    } VersionParseState;

    // set initial versions to 0s
    m_major = 0;
    m_minor = 0;
    m_revision = 0;

    VersionParseState parseState = kVersionStateNone;
    bool done = false;
    for (; index < length && !done; ++index)
    {
        char c = versionString[index];

        if (isdigit(c))
        {
            switch (parseState)
            {
                case kVersionStateNone:
                    parseState = kVersionStateMajor;
                    version = c - '0';
                    break;
                case kVersionStateMajor:
                case kVersionStateMinor:
                case kVersionStateRevision:
                    version = (version << 4) | (c - '0');
                    break;
            }
        }
        else if (c == '.')
        {
            switch (parseState)
            {
                case kVersionStateNone:
                    parseState = kVersionStateNone;
                    break;
                case kVersionStateMajor:
                    m_major = version;
                    version = 0;
                    parseState = kVersionStateMinor;
                    break;
                case kVersionStateMinor:
                    m_minor = version;
                    version = 0;
                    parseState = kVersionStateRevision;
                    break;
                case kVersionStateRevision:
                    m_revision = version;
                    version = 0;
                    done = true;
                    break;
            }
        }
        else
        {
            switch (parseState)
            {
                case kVersionStateNone:
                    parseState = kVersionStateNone;
                    break;
                case kVersionStateMajor:
                    m_major = version;
                    done = true;
                    break;
                case kVersionStateMinor:
                    m_minor = version;
                    done = true;
                    break;
                case kVersionStateRevision:
                    m_revision = version;
                    done = true;
                    break;
            }
        }
    }

    switch (parseState)
    {
        case kVersionStateMajor:
            m_major = version;
            break;
        case kVersionStateMinor:
            m_minor = version;
            break;
        case kVersionStateRevision:
            m_revision = version;
            break;
        default:
            // do nothing
            break;
    }
}

//! \brief Converts host endian BCD version values to the equivalent big-endian BCD values.
//!
//! The output is a half-word. And BCD is inherently big-endian, or byte ordered, if
//! you prefer to think of it that way. So for little endian systems, we need to convert
//! the output half-word in reverse byte order. When it is written to disk or a
//! buffer it will come out big endian.
//!
//! For example:
//!     - The input is BCD in host endian format, so 0x1234. Written to a file, this would
//!       come out as 0x34 0x12, reverse of what we want.
//!     - The desired BCD output is the two bytes 0x12 0x34.
//!     - So the function's uint16_t result must be 0x3412 on a little-endian host.
//!
//! On big endian hosts, we don't have to worry about byte swapping.
void version_t::fixByteOrder()
{
    m_major = ENDIAN_HOST_TO_BIG_U16(m_major);
    m_minor = ENDIAN_HOST_TO_BIG_U16(m_minor);
    m_revision = ENDIAN_HOST_TO_BIG_U16(m_revision);
}
