/***************************************************************************
 *   Copyright (c) 2011 Jürgen Riegel <juergen.riegel@web.de>              *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#include "PreCompiled.h"
#ifndef _PreComp_
# include <sstream>
# include <stdlib.h>
#endif

#include "Unit.h"
#include "Quantity.h"
#include "Exception.h"

using namespace Base;

static inline void checkRange(const char * op, int length, int mass, int time, int electricCurrent,
                              int thermodynamicTemperature, int amountOfSubstance, int luminousIntensity, int angle)
{
    if ( ( length                   >=  (1 << (UnitSignatureLengthBits                   - 1)) ) ||
         ( mass                     >=  (1 << (UnitSignatureMassBits                     - 1)) ) ||
         ( time                     >=  (1 << (UnitSignatureTimeBits                     - 1)) ) ||
         ( electricCurrent          >=  (1 << (UnitSignatureElectricCurrentBits          - 1)) ) ||
         ( thermodynamicTemperature >=  (1 << (UnitSignatureThermodynamicTemperatureBits - 1)) ) ||
         ( amountOfSubstance        >=  (1 << (UnitSignatureAmountOfSubstanceBits        - 1)) ) ||
         ( luminousIntensity        >=  (1 << (UnitSignatureLuminousIntensityBits        - 1)) ) ||
         ( angle                    >=  (1 << (UnitSignatureAngleBits                    - 1)) ) )
        throw Base::OverflowError((std::string("Unit overflow in ") + std::string(op)).c_str());
    if ( ( length                   <  -(1 << (UnitSignatureLengthBits                   - 1)) ) ||
         ( mass                     <  -(1 << (UnitSignatureMassBits                     - 1)) ) ||
         ( time                     <  -(1 << (UnitSignatureTimeBits                     - 1)) ) ||
         ( electricCurrent          <  -(1 << (UnitSignatureElectricCurrentBits          - 1)) ) ||
         ( thermodynamicTemperature <  -(1 << (UnitSignatureThermodynamicTemperatureBits - 1)) ) ||
         ( amountOfSubstance        <  -(1 << (UnitSignatureAmountOfSubstanceBits        - 1)) ) ||
         ( luminousIntensity        <  -(1 << (UnitSignatureLuminousIntensityBits        - 1)) ) ||
         ( angle                    <  -(1 << (UnitSignatureAngleBits                    - 1)) ) )
        throw Base::OverflowError((std::string("Unit underflow in ") + std::string(op)).c_str());
}

Unit::Unit(int8_t Length,
           int8_t Mass,
           int8_t Time,
           int8_t ElectricCurrent,
           int8_t ThermodynamicTemperature,
           int8_t AmountOfSubstance,
           int8_t LuminousIntensity,
           int8_t Angle)
{
    checkRange("unit",
               (int32_t)Length,
               (int32_t)Mass,
               (int32_t)Time,
               (int32_t)ElectricCurrent,
               (int32_t)ThermodynamicTemperature,
               (int32_t)AmountOfSubstance,
               (int32_t)LuminousIntensity,
               (int32_t)Angle);

    Sig.Length                   = Length;
    Sig.Mass                     = Mass;
    Sig.Time                     = Time;
    Sig.ElectricCurrent          = ElectricCurrent;
    Sig.ThermodynamicTemperature = ThermodynamicTemperature;
    Sig.AmountOfSubstance        = AmountOfSubstance;
    Sig.LuminousIntensity        = LuminousIntensity;
    Sig.Angle                    = Angle;
}


Unit::Unit()
{
    Sig.Length                   = 0;
    Sig.Mass                     = 0;
    Sig.Time                     = 0;
    Sig.ElectricCurrent          = 0;
    Sig.ThermodynamicTemperature = 0;
    Sig.AmountOfSubstance        = 0;
    Sig.LuminousIntensity        = 0;
    Sig.Angle                    = 0;
}

Unit::Unit(const Unit& that)
{
    this->Sig = that.Sig;
}

Unit::Unit(const QString& expr)
{
    try {
        *this = Quantity::parse(expr).getUnit();
    }
    catch (...) {
        Sig.Length                   = 0;
        Sig.Mass                     = 0;
        Sig.Time                     = 0;
        Sig.ElectricCurrent          = 0;
        Sig.ThermodynamicTemperature = 0;
        Sig.AmountOfSubstance        = 0;
        Sig.LuminousIntensity        = 0;
        Sig.Angle                    = 0;
    }
}

Unit Unit::pow(signed char exp) const
{
    checkRange("pow()",
               (int32_t)Sig.Length * (int32_t)exp,
               (int32_t)Sig.Mass * (int32_t)exp,
               (int32_t)Sig.Time * (int32_t)exp,
               (int32_t)Sig.ElectricCurrent * (int32_t)exp,
               (int32_t)Sig.ThermodynamicTemperature * (int32_t)exp,
               (int32_t)Sig.AmountOfSubstance * (int32_t)exp,
               (int32_t)Sig.LuminousIntensity * (int32_t)exp,
               (int32_t)Sig.Angle * (int32_t)exp);

    Unit result;
    result.Sig.Length                   = Sig.Length                    * exp;
    result.Sig.Mass                     = Sig.Mass                      * exp;
    result.Sig.Time                     = Sig.Time                      * exp;
    result.Sig.ElectricCurrent          = Sig.ElectricCurrent           * exp;
    result.Sig.ThermodynamicTemperature = Sig.ThermodynamicTemperature  * exp;
    result.Sig.AmountOfSubstance        = Sig.AmountOfSubstance         * exp;
    result.Sig.LuminousIntensity        = Sig.LuminousIntensity         * exp;
    result.Sig.Angle                    = Sig.Angle                     * exp;

    return result;
}

bool Unit::isEmpty(void)const
{
    return (this->Sig.Length == 0)
        && (this->Sig.Mass == 0)
        && (this->Sig.Time == 0)
        && (this->Sig.ElectricCurrent == 0)
        && (this->Sig.ThermodynamicTemperature == 0)
        && (this->Sig.AmountOfSubstance == 0)
        && (this->Sig.LuminousIntensity == 0)
        && (this->Sig.Angle == 0);
}

bool Unit::operator ==(const Unit& that) const
{
    return (this->Sig.Length == that.Sig.Length)
        && (this->Sig.Mass == that.Sig.Mass)
        && (this->Sig.Time == that.Sig.Time)
        && (this->Sig.ElectricCurrent == that.Sig.ElectricCurrent)
        && (this->Sig.ThermodynamicTemperature == that.Sig.ThermodynamicTemperature)
        && (this->Sig.AmountOfSubstance == that.Sig.AmountOfSubstance)
        && (this->Sig.LuminousIntensity == that.Sig.LuminousIntensity)
        && (this->Sig.Angle == that.Sig.Angle);
}


Unit Unit::operator *(const Unit &right) const
{
    checkRange("* operator",
               (int32_t)Sig.Length + (int32_t)right.Sig.Length,
               (int32_t)Sig.Mass + (int32_t)right.Sig.Mass,
               (int32_t)Sig.Time + (int32_t)right.Sig.Time,
               (int32_t)Sig.ElectricCurrent + (int32_t)right.Sig.ElectricCurrent,
               (int32_t)Sig.ThermodynamicTemperature + (int32_t)right.Sig.ThermodynamicTemperature,
               (int32_t)Sig.AmountOfSubstance + (int32_t)right.Sig.AmountOfSubstance,
               (int32_t)Sig.LuminousIntensity + (int32_t)right.Sig.LuminousIntensity,
               (int32_t)Sig.Angle + (int32_t)right.Sig.Angle);

    Unit result;
    result.Sig.Length                   = Sig.Length                    + right.Sig.Length;
    result.Sig.Mass                     = Sig.Mass                      + right.Sig.Mass;
    result.Sig.Time                     = Sig.Time                      + right.Sig.Time;
    result.Sig.ElectricCurrent          = Sig.ElectricCurrent           + right.Sig.ElectricCurrent;
    result.Sig.ThermodynamicTemperature = Sig.ThermodynamicTemperature  + right.Sig.ThermodynamicTemperature;
    result.Sig.AmountOfSubstance        = Sig.AmountOfSubstance         + right.Sig.AmountOfSubstance;
    result.Sig.LuminousIntensity        = Sig.LuminousIntensity         + right.Sig.LuminousIntensity;
    result.Sig.Angle                    = Sig.Angle                     + right.Sig.Angle;

    return result;
}

Unit Unit::operator /(const Unit &right) const
{
    checkRange("/ operator",
               (int32_t)Sig.Length - (int32_t)right.Sig.Length,
               (int32_t)Sig.Mass - (int32_t)right.Sig.Mass,
               (int32_t)Sig.Time - (int32_t)right.Sig.Time,
               (int32_t)Sig.ElectricCurrent - (int32_t)right.Sig.ElectricCurrent,
               (int32_t)Sig.ThermodynamicTemperature - (int32_t)right.Sig.ThermodynamicTemperature,
               (int32_t)Sig.AmountOfSubstance - (int32_t)right.Sig.AmountOfSubstance,
               (int32_t)Sig.LuminousIntensity - (int32_t)right.Sig.LuminousIntensity,
               (int32_t)Sig.Angle - (int32_t)right.Sig.Angle);

    Unit result;
    result.Sig.Length                   = Sig.Length                    - right.Sig.Length;
    result.Sig.Mass                     = Sig.Mass                      - right.Sig.Mass;
    result.Sig.Time                     = Sig.Time                      - right.Sig.Time;
    result.Sig.ElectricCurrent          = Sig.ElectricCurrent           - right.Sig.ElectricCurrent;
    result.Sig.ThermodynamicTemperature = Sig.ThermodynamicTemperature  - right.Sig.ThermodynamicTemperature;
    result.Sig.AmountOfSubstance        = Sig.AmountOfSubstance         - right.Sig.AmountOfSubstance;
    result.Sig.LuminousIntensity        = Sig.LuminousIntensity         - right.Sig.LuminousIntensity;
    result.Sig.Angle                    = Sig.Angle                     - right.Sig.Angle;

    return result;
}

Unit& Unit::operator = (const Unit &New)
{
    Sig.Length                   = New.Sig.Length;
    Sig.Mass                     = New.Sig.Mass;
    Sig.Time                     = New.Sig.Time;
    Sig.ElectricCurrent          = New.Sig.ElectricCurrent;
    Sig.ThermodynamicTemperature = New.Sig.ThermodynamicTemperature;
    Sig.AmountOfSubstance        = New.Sig.AmountOfSubstance;
    Sig.LuminousIntensity        = New.Sig.LuminousIntensity;
    Sig.Angle                    = New.Sig.Angle;

    return *this;
}

QString Unit::getString(void) const
{
    std::stringstream ret;

    if (isEmpty())
        return QString();

    if (Sig.Length                  > 0 ||
        Sig.Mass                    > 0 ||
        Sig.Time                    > 0 ||
        Sig.ElectricCurrent         > 0 ||
        Sig.ThermodynamicTemperature> 0 ||
        Sig.AmountOfSubstance       > 0 ||
        Sig.LuminousIntensity       > 0 ||
        Sig.Angle                   > 0 ){

        bool mult = false;
        if (Sig.Length > 0) {
            mult = true;
            ret << "mm";
            if (Sig.Length > 1)
                ret << "^" << Sig.Length;
        }

        if (Sig.Mass > 0) {
            if (mult)
                ret<<'*';
            mult = true;
            ret << "kg";
            if (Sig.Mass > 1)
                ret << "^" << Sig.Mass;
        }

        if (Sig.Time > 0) {
            if (mult)
                ret<<'*';
            mult = true;
            ret << "s";
            if (Sig.Time > 1)
                ret << "^" << Sig.Time;
        }

        if (Sig.ElectricCurrent > 0) {
            if (mult) ret<<'*';
                mult = true;
            ret << "A";
            if (Sig.ElectricCurrent > 1)
                ret << "^" << Sig.ElectricCurrent;
        }

        if (Sig.ThermodynamicTemperature > 0) {
            if (mult)
                ret<<'*';
            mult = true;
            ret << "K";
            if (Sig.ThermodynamicTemperature > 1)
                ret << "^" << Sig.ThermodynamicTemperature;
        }

        if (Sig.AmountOfSubstance > 0){
            if (mult)
                ret<<'*';
            mult = true;
            ret << "mol";
            if (Sig.AmountOfSubstance > 1)
                ret << "^" << Sig.AmountOfSubstance;
        }

        if (Sig.LuminousIntensity > 0) {
            if (mult)
                ret<<'*';
            mult = true;
            ret << "cd";
            if (Sig.LuminousIntensity > 1)
                ret << "^" << Sig.LuminousIntensity;
        }

        if (Sig.Angle > 0) {
            if (mult)
                ret<<'*';
            mult = true;
            ret << "deg";
            if (Sig.Angle > 1)
                ret << "^" << Sig.Angle;
        }
    }
    else {
        ret << "1";
    }

    if (Sig.Length                  < 0 ||
        Sig.Mass                    < 0 ||
        Sig.Time                    < 0 ||
        Sig.ElectricCurrent         < 0 ||
        Sig.ThermodynamicTemperature< 0 ||
        Sig.AmountOfSubstance       < 0 ||
        Sig.LuminousIntensity       < 0 ||
        Sig.Angle                   < 0 ){
        ret << "/";

        int nnom = 0;
        nnom += Sig.Length<0?1:0;
        nnom += Sig.Mass<0?1:0;
        nnom += Sig.Time<0?1:0;
        nnom += Sig.ElectricCurrent<0?1:0;
        nnom += Sig.ThermodynamicTemperature<0?1:0;
        nnom += Sig.AmountOfSubstance<0?1:0;
        nnom += Sig.LuminousIntensity<0?1:0;
        nnom += Sig.Angle<0?1:0;

        if (nnom > 1)
            ret << '(';

        bool mult=false;
        if (Sig.Length < 0) {
            ret << "mm";
            mult = true;
            if (Sig.Length < -1)
                ret << "^" << abs(Sig.Length);
        }

        if (Sig.Mass < 0) {
            if (mult)
                ret<<'*';
            mult = true;
            ret << "kg";
            if (Sig.Mass < -1)
                ret << "^" << abs(Sig.Mass);
        }

        if (Sig.Time < 0) {
            if (mult)
                ret<<'*';
            mult = true;
            ret << "s";
            if (Sig.Time < -1)
                ret << "^" << abs(Sig.Time);
        }

        if (Sig.ElectricCurrent < 0) {
            if (mult)
                ret<<'*';
            mult = true;
            ret << "A";
            if (Sig.ElectricCurrent < -1)
                ret << "^" << abs(Sig.ElectricCurrent);
        }

        if (Sig.ThermodynamicTemperature < 0) {
            if (mult)
                ret<<'*';
            mult = true;
            ret << "K";
            if (Sig.ThermodynamicTemperature < -1)
                ret << "^" << abs(Sig.ThermodynamicTemperature);
        }

        if (Sig.AmountOfSubstance < 0) {
            if (mult)
                ret<<'*';
            mult = true;
            ret << "mol";
            if (Sig.AmountOfSubstance < -1)
                ret << "^" << abs(Sig.AmountOfSubstance);
        }

        if (Sig.LuminousIntensity < 0) {
            if (mult)
                ret<<'*';
            mult = true;
            ret << "cd";
            if (Sig.LuminousIntensity < -1)
                ret << "^" << abs(Sig.LuminousIntensity);
        }

        if (Sig.Angle < 0) {
            if (mult)
                ret<<'*';
            mult = true;
            ret << "deg";
            if (Sig.Angle < -1)
                ret << "^" << abs(Sig.Angle);
        }

        if (nnom > 1)
            ret << ')';
    }

    return QString::fromUtf8(ret.str().c_str());
}

QString Unit::getTypeString(void) const
{
    if(*this == Unit::Length                      )       return QString::fromLatin1("Length");
    if(*this == Unit::Area                        )       return QString::fromLatin1("Area");
    if(*this == Unit::Volume                      )       return QString::fromLatin1("Volume");
    if(*this == Unit::Mass                        )       return QString::fromLatin1("Mass");
    if(*this == Unit::Angle                       )       return QString::fromLatin1("Angle");
    if(*this == Unit::Density                     )       return QString::fromLatin1("Density");
    if(*this == Unit::TimeSpan                    )       return QString::fromLatin1("TimeSpan");
    if(*this == Unit::Frequency                   )       return QString::fromLatin1("Frequency");
    if(*this == Unit::Velocity                    )       return QString::fromLatin1("Velocity");
    if(*this == Unit::Acceleration                )       return QString::fromLatin1("Acceleration");
    if(*this == Unit::Temperature                 )       return QString::fromLatin1("Temperature");
    if(*this == Unit::ElectricCurrent             )       return QString::fromLatin1("ElectricCurrent");
    if(*this == Unit::ElectricPotential           )       return QString::fromLatin1("ElectricPotential");
    if(*this == Unit::ElectricCharge              )       return QString::fromLatin1("ElectricCharge");
    if(*this == Unit::MagneticFieldStrength       )       return QString::fromLatin1("MagneticFieldStrength");
    if(*this == Unit::MagneticFlux                )       return QString::fromLatin1("MagneticFlux");
    if(*this == Unit::MagneticFluxDensity         )       return QString::fromLatin1("MagneticFluxDensity");
    if(*this == Unit::ElectricalCapacitance       )       return QString::fromLatin1("ElectricalCapacitance");
    if(*this == Unit::ElectricalInductance        )       return QString::fromLatin1("ElectricalInductance");
    if(*this == Unit::ElectricalConductance       )       return QString::fromLatin1("ElectricalConductance");
    if(*this == Unit::ElectricalResistance        )       return QString::fromLatin1("ElectricalResistance");
    if(*this == Unit::AmountOfSubstance           )       return QString::fromLatin1("AmountOfSubstance");
    if(*this == Unit::LuminousIntensity           )       return QString::fromLatin1("LuminousIntensity");
    if(*this == Unit::Pressure                    )       return QString::fromLatin1("Pressure");
    if(*this == Unit::Force                       )       return QString::fromLatin1("Force");
    if(*this == Unit::Work                        )       return QString::fromLatin1("Work");
    if(*this == Unit::Power                       )       return QString::fromLatin1("Power");
    if(*this == Unit::SpecificEnergy              )       return QString::fromLatin1("SpecificEnergy");
    if(*this == Unit::ThermalConductivity         )       return QString::fromLatin1("ThermalConductivity");
    if(*this == Unit::ThermalExpansionCoefficient )       return QString::fromLatin1("ThermalExpansionCoefficient");
    if(*this == Unit::VolumetricThermalExpansionCoefficient )       return QString::fromLatin1("VolumetricThermalExpansionCoefficient");
    if(*this == Unit::SpecificHeat                )       return QString::fromLatin1("SpecificHeat");
    if(*this == Unit::ThermalTransferCoefficient  )       return QString::fromLatin1("ThermalTransferCoefficient");
    if(*this == Unit::HeatFlux                    )       return QString::fromLatin1("HeatFlux");
    if(*this == Unit::DynamicViscosity            )       return QString::fromLatin1("DynamicViscosity");
    if(*this == Unit::KinematicViscosity          )       return QString::fromLatin1("KinematicViscosity");
    if(*this == Unit::VacuumPermittivity          )       return QString::fromLatin1("VacuumPermittivity");

    return QString();

}

Unit Unit::Length(1);
Unit Unit::Area(2);
Unit Unit::Volume(3);
Unit Unit::Mass(0,1);

// Angle, deg
Unit Unit::Angle           (0,0,0,0,0,0,0,1);
Unit Unit::AngleOfFriction (0,0,0,0,0,0,0,1);

Unit Unit::Density(-3,1);

Unit Unit::TimeSpan(0,0,1);
Unit Unit::Frequency(0,0,-1);
Unit Unit::Velocity(1,0,-1);
Unit Unit::Acceleration(1,0,-2);
Unit Unit::Temperature(0,0,0,0,1);

Unit Unit::ElectricCurrent(0,0,0,1);
Unit Unit::ElectricPotential(2,1,-3,-1);
Unit Unit::ElectricCharge(0,0,1,1);
Unit Unit::MagneticFieldStrength(-1,0,0,1);
Unit Unit::MagneticFlux(2,1,-2,-1);
Unit Unit::MagneticFluxDensity(0,1,-2,-1);
Unit Unit::ElectricalCapacitance(-2,-1,4,2);
Unit Unit::ElectricalInductance(2,1,-2,-2);
Unit Unit::ElectricalConductance(-2,-1,3,2);
Unit Unit::ElectricalResistance(2,1,-3,-2);
Unit Unit::AmountOfSubstance(0,0,0,0,0,1);
Unit Unit::LuminousIntensity(0,0,0,0,0,0,1);

// Pressure, kg/m*s^2 or N/m^2 or PSI or MPa
Unit Unit::CompressiveStrength     (-1,1,-2);
Unit Unit::Pressure                (-1,1,-2);
Unit Unit::ShearModulus            (-1,1,-2);
Unit Unit::Stress                  (-1,1,-2);
Unit Unit::UltimateTensileStrength (-1,1,-2);
Unit Unit::YieldStrength           (-1,1,-2);
Unit Unit::YoungsModulus           (-1,1,-2);

Unit Unit::Force   (1,1,-2);
Unit Unit::Work    (2,1,-2);
Unit Unit::Power   (2,1,-3);

Unit Unit::SpecificEnergy              (2,0,-2);
Unit Unit::ThermalConductivity         (1,1,-3,0,-1);
Unit Unit::ThermalExpansionCoefficient (0,0,0,0,-1);
Unit Unit::VolumetricThermalExpansionCoefficient (0,0,0,0,-1);
Unit Unit::SpecificHeat                (2,0,-2,0,-1);
Unit Unit::ThermalTransferCoefficient  (0,1,-3,0,-1);
Unit Unit::HeatFlux                    (0,1,-3,0,0);
Unit Unit::DynamicViscosity            (-1,1,-1);  // SI unit: kg/m/s
Unit Unit::KinematicViscosity          (2,0,-1);  // SI unit: m^2/s, https://en.wikipedia.org/wiki/Viscosity#Kinematic_viscosity
Unit Unit::VacuumPermittivity          (-3,-1,4,2); // SI unit: A²*s⁴/kg/m³ https://en.wikipedia.org/wiki/Permittivity#Vacuum_permittivity
