// Copyright (c) 2025 Metrolab Technology S.A., Geneva, Switzerland (www.metrolab.com)
// See the included file LICENSE.txt for the licensing conditions.

//////////////////////////////////////////////////////////////////////////
/// \file
/// \brief THM1176 API: method implementations for THM1176 types.

#include <iostream>
#include <iomanip>
#include <ctime>

#include "Eigen/SVD"

#include "THM1176.h"

using namespace MTL;
using namespace MTL::Instrument;
using namespace MTL::Instrument::THM1176Types;

namespace MTL {
namespace Instrument {
namespace THM1176Types {

//----------------------------------------------------------------------//
//								Status									//
//----------------------------------------------------------------------//
std::ostream &  operator<<(std::ostream & Stream, const eStatusRegisterSet & RegSet)
{
    Stream << ((RegSet == kStatusByte)                       ? "StatByte"    :
               (RegSet == kStandardEventStatusRegister)      ? "StdEvReg"    :
               (RegSet == kStatusQuestionableStatusRegister) ? "QuestReg"    :
               (RegSet == kStatusOperationStatusRegister)    ? "OperReg"     : "?");
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const eStatusRegisterType & RegType)
{
    Stream << ((RegType == kStatusEvent)        ? "Event"       :
               (RegType == kStatusCondition)    ? "Condition"   :
               (RegType == kStatusEnable)       ? "Enable"      : "?");
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const sStatusRegister & RegDef)
{
    Stream << "regdef(" << RegDef.Set << " " << RegDef.Type << ")";
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const RegisterList & RegList)
{
    Stream << "( ";
    for (auto l_Reg : RegList)
        Stream << l_Reg << " ";
    Stream << ")";
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const StatusValues & StatusList)
{
    Stream << "( ";
    for (auto l_Value : StatusList)
        Stream << l_Value << " ";
    Stream << ")";
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const uStatusByte & StatusByte)
{
    Stream << "(EAV=" << StatusByte.StatusByte.EAV <<
              " QSB=" << StatusByte.StatusByte.QSB <<
              " MAV=" << StatusByte.StatusByte.MAV <<
              " ESB=" << StatusByte.StatusByte.ESB <<
              " RQS=" << StatusByte.StatusByte.RQS <<
              " OSB=" << StatusByte.StatusByte.OSB << ")";
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const uStandardEvent & StdEventReg)
{
    Stream << "(OperationComplete="     << StdEventReg.StandardEvent.OperationComplete <<
              " QueryError="            << StdEventReg.StandardEvent.QueryError <<
              " DeviceDependentError="  << StdEventReg.StandardEvent.DeviceDependentError <<
              " ExecutionError="        << StdEventReg.StandardEvent.ExecutionError <<
              " CommandError="          << StdEventReg.StandardEvent.CommandError <<
              " PowerOn="               << StdEventReg.StandardEvent.PowerOn << ")";
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const uOPERation & OperReg)
{
    Stream << "(CALibrating="       << OperReg.OPERation.CALibrating <<
              " RANGing="           << OperReg.OPERation.RANGing <<
              " MEASuring="         << OperReg.OPERation.MEASuring <<
              " WaitingForTRIGger=" << OperReg.OPERation.WaitingForTRIGger << ")";
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const uQUEStionable & QuestReg)
{
    Stream << "(Frequency=" << QuestReg.QUEStionable.Frequency <<
              " Overrange=" << QuestReg.QUEStionable.Overrange << ")";
    return Stream;
}

//----------------------------------------------------------------------//
//								Flux									//
//----------------------------------------------------------------------//
std::ostream & operator<<(std::ostream & Stream, const CFluxList & FluxList)
{
    Stream << "( ";
    for (auto l_Flux : FluxList)
        Stream << l_Flux << " ";
    Stream << ")";
    return Stream;
}

//----------------------------------------------------------------------//
//								Units									//
//----------------------------------------------------------------------//
std::ostream &  operator<<(std::ostream & Stream, const eUnits & Units)
{
    Stream << ((Units == kT)     ? "T"       :
               (Units == kmT)    ? "mT"      :
               (Units == kuT)    ? "uT"      :
               (Units == knT)    ? "nT"      :
               (Units == kGauss) ? "Gauss"   :
               (Units == kkGauss)? "kGauss"  :
               (Units == kmGauss)? "mGauss"  :
               (Units == kMHzp)  ? "MHzp"    : "?");
    return Stream;
}
std::ostream & operator<<(std::ostream & Stream, const CUnitsList & UnitsList)
{
    Stream << "( ";
    for (auto l_Units : UnitsList)
        Stream << l_Units << " ";
    Stream << ")";
    return Stream;
}
std::ostream & operator<<(std::ostream & Stream, const CDivisorList & DivisorList)
{
    Stream << "( ";
    for (auto l_Divisor : DivisorList)
        Stream << l_Divisor << " ";
    Stream << ")";
    return Stream;
}

//----------------------------------------------------------------------//
//	Errors																//
//----------------------------------------------------------------------//
bool sError::operator==(sError other) const
{
    return (
                Code		== other.Code &&
                Description	== other.Description &&
                Context		== other.Context
                );
}
bool sError::operator!=(sError other) const
{
    return (!operator==(other));
}
std::ostream &  operator<<(std::ostream & Stream, const sError & Error)
{
    Stream << "err(code="   << Error.Code <<
              " desc="      << Error.Description <<
              " ctxt="      << Error.Context << ")";
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const CErrorList & ErrorList)
{
    for (auto l_Error : ErrorList)
        Stream << l_Error << std::endl;
    return Stream;
}

//----------------------------------------------------------------------//
//	Version																//
//----------------------------------------------------------------------//
void sVersion::clear(void)
{
    Major = 0;
    Minor = 0;
}
bool sVersion::operator==(sVersion other) const
{
    return (
                Major	== other.Major &&
                Minor	== other.Minor);
}
bool sVersion::operator!=(sVersion other) const
{
    return (!operator==(other));
}
std::ostream &  operator<<(std::ostream & Stream, const sVersion & Version)
{
    Stream << Version.Major << "." << Version.Minor;
    return Stream;
}

//----------------------------------------------------------------------//
//	Identifier															//
//----------------------------------------------------------------------//
void sIdentifier::clear(void)
{
    Manufacturer.clear();
    Model.clear();
    SerialNumber = 0;
    ElectronicsVersion.clear();
    ProbeVersion.clear();
    FirmwareVersion.clear();
    ModelRevision = eModelRevision::kA;
    InstrModel= eInstrModel::kUnknown;
}
bool sIdentifier::operator==(sIdentifier other) const
{
    return (
                Manufacturer		== other.Manufacturer &&
                Model				== other.Model &&
                SerialNumber		== other.SerialNumber &&
                ElectronicsVersion	== other.ElectronicsVersion &&
                ProbeVersion		== other.ProbeVersion &&
                FirmwareVersion		== other.FirmwareVersion
                );
}
bool sIdentifier::operator!=(sIdentifier other) const
{
    return (!operator==(other));
}
std::ostream &  operator<<(std::ostream & Stream, const sIdentifier & ID)
{
    Stream << "id(mfr=" << ID.Manufacturer <<
              " mdl="   << ID.Model <<
              " sn="    << ID.SerialNumber <<
              " elv="   << ID.ElectronicsVersion <<
              " prv="   << ID.ProbeVersion <<
              " fwv="   << ID.FirmwareVersion << ")";
    return Stream;
}

//----------------------------------------------------------------------//
//								Parameters								//
//----------------------------------------------------------------------//
std::ostream &  operator<<(std::ostream & Stream, const sAveraging<uParm> & AvgParm)
{
    Stream << AvgParm.NoPoints;
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const sAveraging<sBoundedParm> & AvgParm)
{
    Stream << "avg(val="    << AvgParm.NoPoints.Val <<
              " min="       << AvgParm.NoPoints.Min <<
              " max="       << AvgParm.NoPoints.Max <<
              " def="       << AvgParm.NoPoints.Def << ")";
    return Stream;
}

std::ostream &  operator<<(std::ostream & Stream, const eInputTriggerSource & TrgSrc)
{
    Stream <<   ((TrgSrc == kInputTrigSrcImmediate) ? "imm" :
                 (TrgSrc == kInputTrigSrcTimer)     ? "tim" :
                 (TrgSrc == kInputTrigSrcBus)       ? "bus" : "?");
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const sInputTrigger<uParm> & TrigParm)
{
    Stream << "trg(src="    << TrigParm.Source <<
              " per="       << TrigParm.Period_s <<
              " cnt="       << TrigParm.Count << ")";
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const sInputTrigger<sBoundedParm> & TrigParm)
{
    Stream << "trg(src="    << TrigParm.Source <<
              " per(val="   << TrigParm.Period_s.Val <<
              " min="       << TrigParm.Period_s.Min <<
              " max="       << TrigParm.Period_s.Max <<
              " def="       << TrigParm.Period_s.Def << ")" <<
              " cnt(val="   << TrigParm.Count.Val <<
              " min="       << TrigParm.Count.Min <<
              " max="       << TrigParm.Count.Max <<
              " def="       << TrigParm.Count.Def << "))";
    return Stream;
}

std::ostream &  operator<<(std::ostream & Stream, const eCommunicationFormat & CommFormat)
{
    Stream <<   ((CommFormat == kComFormatAscii)        ? "ascii" :
                 (CommFormat == kComFormatInteger)      ? "int" :
                 (CommFormat == kComFormatPacked2Byte)  ? "pack2" :
                 (CommFormat == kComFormatPacked1Byte)  ? "pack1" : "?");
    return Stream;
}

std::ostream &  operator<<(std::ostream & Stream, const sRange<uParm> & RangeParm)
{
    Stream << "range(auto=" << RangeParm.Auto <<
              " rng="       << RangeParm.Range << ")";
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const sRange<sBoundedParm> & RangeParm)
{
    Stream << "range(auto=" << RangeParm.Auto <<
              " rng(val="   << RangeParm.Range.Val <<
              " min="       << RangeParm.Range.Min <<
              " max="       << RangeParm.Range.Max <<
              " def="       << RangeParm.Range.Def << "))";
    return Stream;
}

//----------------------------------------------------------------------//
//	Files																//
//----------------------------------------------------------------------//
void sFile::clear()
{
    Path.clear();
    Type.clear();
    Size = 0;
}
bool sFile::operator==(sFile other) const
{
    return (
                Path	== other.Path &&
                Type	== other.Type &&
                Size	== other.Size
                );
}
bool sFile::operator!=(sFile other) const
{
    return (!operator==(other));
}
std::ostream &  operator<<(std::ostream & Stream, const sFile & FileInfo)
{
    Stream << "file(siz="   << FileInfo.Size <<
              " path="      << FileInfo.Path <<
              " type="      << FileInfo.Type << ")";
    return Stream;
}
std::ostream &  operator<<(std::ostream & Stream, const tFileList & FileList)
{
    for (auto l_FileInfo : FileList)
        Stream << l_FileInfo << std::endl;
    return Stream;
}

//----------------------------------------------------------------------//
//	Time stamp															//
//----------------------------------------------------------------------//
CAbsoluteTimestamp::CAbsoluteTimestamp(std::time_t Seconds, U64 Nanoseconds) :
    s(Seconds), ns(Nanoseconds)
{
    s += ns / NS_PER_SEC;
    ns = ns % NS_PER_SEC;
}
CAbsoluteTimestamp & CAbsoluteTimestamp::operator= (const CAbsoluteTimestamp & Value)
{
    s = Value.s;
    ns = Value.ns;
    return *this;
}
bool CAbsoluteTimestamp::operator==(CAbsoluteTimestamp other)
{
    return (s == other.s && ns == other.ns);
}
bool CAbsoluteTimestamp::operator!=(CAbsoluteTimestamp other)
{
    return (!operator==(other));
}
std::time_t	CAbsoluteTimestamp::seconds(void) const
{
    return s;
}
U64			CAbsoluteTimestamp::nanoseconds(void) const
{
    return ns;
}
void		CAbsoluteTimestamp::clear(void)
{
    s = 0;
    ns = 0;
}

//----------------------------------------------------------------------//
//	Timestamp addition / subtraction / insertion						//
//----------------------------------------------------------------------//
CAbsoluteTimestamp operator+ (CAbsoluteTimestamp a, CAbsoluteTimestamp b)
{
    std::time_t		s;
    U64				ns;

    s	= a.seconds() + b.seconds();
    ns	= a.nanoseconds() + b.nanoseconds();
    //Manage ns overlap to sec:
    s += ns / CAbsoluteTimestamp::NS_PER_SEC;
    ns = ns % CAbsoluteTimestamp::NS_PER_SEC;

    return CAbsoluteTimestamp(s, ns);
}

CAbsoluteTimestamp operator- (CAbsoluteTimestamp a, CAbsoluteTimestamp b)
{
    std::time_t		s;
    U64				ns;

    if (a.nanoseconds() > b.nanoseconds())
    {
        ns = a.nanoseconds() - b.nanoseconds();
        s  = (a.seconds() > b.seconds()) ? (a.seconds() - b.seconds()) : 0;
    }
    else
    {
        ns = a.nanoseconds() + (CAbsoluteTimestamp::NS_PER_SEC - b.nanoseconds());
        s  = (a.seconds() > (b.seconds() + 1)) ? (a.seconds() - (b.seconds() + 1)) : 0;
    }

    return CAbsoluteTimestamp(s, ns);
}

std::ostream & operator<<(std::ostream & Stream, const CAbsoluteTimestamp & Timestamp)
{
    std::time_t	l_Seconds		= Timestamp.seconds();
    U64			l_NanoSeconds	= Timestamp.nanoseconds();
    char		l_DateTimeString[32];
    std::strftime(l_DateTimeString, 31, "%FT%T", std::localtime(&l_Seconds));
    Stream << std::string(l_DateTimeString) << "."
           << std::setfill('0') << std::setw(9)
           << l_NanoSeconds
           << std::setfill(' ') << std::setw(0);
    return Stream;
}

//----------------------------------------------------------------------//
// Time stamp list														//
//----------------------------------------------------------------------//
bool CTimestampList::GetEstimatedPeriod(F64 & Period)
{
    // Make sure we have enough timestamps.
    if (this->size() < 2)
    {
        Period = 0.;
        return false;
    }

    // Set up the linear system:
    // Given n observations (0, T0), (1, T1), ... (n-1, Tn-1):
    //
    //        [ 1    0  ]                        [ T0   ]
    // Xnx2 = [ 1    1  ], b2x1 = [ b0 ], Ynx1 = [ T1   ],
    //        [   ...   ]         [ b1 ]         [ ...  ]
    //        [ 1   n-1 ]                        [ Tn-1 ]
    //
    // we will solve the overconstrained problem:
    //   Xnx2 b2x1 = Ynx1
    // The period is the coefficient b1.
    //
    MatrixXd	l_X;
    Vector2d	l_b;
    VectorXd	l_Y;

    size_t					l_n = this->size();
    l_X.resize(l_n, 2);
    l_Y.resize(l_n);

    // Initialize X and Y.
    U64 l_T0 = this->at(0);
    for (U32 l_i = 0; l_i < l_n; l_i++)
    {
        l_X(l_i, 0)	= 1.;
        l_X(l_i, 1)	= l_i;
        l_Y(l_i)	= static_cast<F64> (this->at(l_i) - l_T0);
    }

    // Solve the the linear least-squares system using the SVD decomposition.
    // See https://eigen.tuxfamily.org/dox/group__LeastSquares.html.
    l_b = l_X.jacobiSvd(ComputeThinU | ComputeThinV).solve(l_Y);

    // Return the result.
    Period = l_b(1);
    return true;
}

std::ostream &				operator<<(std::ostream & Stream, const CTimestampList & TimestampList)
{
    Stream << "( ";
    for (auto l_TimeStamp : TimestampList)
        Stream << l_TimeStamp << " ";
    Stream << ")";
    return Stream;
}

//----------------------------------------------------------------------//
// Measurements															//
//----------------------------------------------------------------------//
void sArbitraryMeasurements::clear()
{
    Bx = By = Bz = Temperature = Timestamp = true;
    NoMeasurements = 1;
}
bool sArbitraryMeasurements::operator==(sArbitraryMeasurements other) const
{
    return (
                Bx				== other.Bx &&
                By				== other.By &&
                Bz				== other.Bz &&
                Temperature		== other.Temperature &&
                Timestamp		== other.Timestamp &&
                NoMeasurements	== other.NoMeasurements
                );
}
bool sArbitraryMeasurements::operator!=(sArbitraryMeasurements other) const
{
    return (!operator==(other));
}

void sMeasurementConditions::clear()
{
    AveragingParms.clear();
    TriggerParms.clear();
    UseCalibration = true;
    Range = 0.;
}
bool sMeasurementConditions::operator==(sMeasurementConditions other) const
{
    return (
                AveragingParms	== other.AveragingParms &&
                TriggerParms	== other.TriggerParms &&
                UseCalibration	== other.UseCalibration &&
                Range           == other.Range
                );
}
bool sMeasurementConditions::operator!=(sMeasurementConditions other) const
{
    return (!operator==(other));
}
std::ostream &				operator<<(std::ostream & Stream, const sArbitraryMeasurements & MeasSpec)
{
    Stream << "arb(Bx=" << MeasSpec.Bx <<
              " By="    << MeasSpec.By <<
              " Bz="    << MeasSpec.Bz <<
              " temp="  << MeasSpec.Temperature <<
              " time="  << MeasSpec.Timestamp <<
              " count=" << MeasSpec.NoMeasurements << ")";
    return Stream;
}
std::ostream &				operator<<(std::ostream & Stream, const sMeasurementConditions & MeasCond)
{
    Stream << "meas_cond(avg="  << MeasCond.AveragingParms <<
              " trig="          << MeasCond.TriggerParms <<
              " cal="           << MeasCond.UseCalibration <<
              " range="         << MeasCond.Range << ")";
    return Stream;
}

} // namespace THM1176Types
} // namespace Instrument
} // namespace MTL
