/*------------------------------------------------------------------------------
// ViewTms V1.3.1
// Copyright (c) 2001-2011 Tellert Elektronik GmbH
//------------------------------------------------------------------------------
// The minimum required TIL_TMS.DLL version is 1.2.0 (due to the strict result 
// check of TilGetNextIteratorItems for tilDataMeasGroupSignalDataIterator). 
// This limitation can be bypassed by ignoring the corresponding function 
// result:
// #define SUPPORT_OLDER_TILTMS_VERSIONS
//----------------------------------------------------------------------------*/

#define _CRT_SECURE_NO_WARNINGS
#include <string.h>
#include <stdio.h>
#include <tchar.h>

#include "til.h"

#if 1
  /* Release settings */
  #define ACCESS_RAW_DATA TIL_FALSE
  #define QUERY_TIME64    TIL_FALSE
  #define SAMPLES_MAX     SIZE_MAX
#else
  /* Test settings */
  #define ACCESS_RAW_DATA TIL_TRUE
  #define QUERY_TIME64    TIL_TRUE
  #define SAMPLES_MAX     1000
  #undef  SUPPORT_OLDER_TILTMS_VERSIONS
#endif

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//  char, double, TSignalData vectors
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/

#define NewCharVector(n)      (char*) malloc(n)
#define DeleteCharVector(p)   free(p)

#define NewDoubleVector(n)    (double*) malloc( (n) * sizeof(double) )
#define DeleteDoubleVector(p) free(p)

typedef struct {
    char *RawData;
    double *Data;
} TSignalData;
#define NewInitializedSignalDataVector(n) (TSignalData*) calloc(n, sizeof(TSignalData))
#define DeleteSignalDataVector(p)         free(p)

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//  String
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/

TIL_PCTSTR InvalidString = TIL_TEXT("");

TIL_PCTSTR NewString(TIL_PPROP prop)
{
    TIL_STRLEN len;

    len = TIL_QUERY_STRLEN; /* Initialize because TilGetString will fail due to an invalid buffer */
    TilGetString(prop, NULL, TIL_QUERY_STRLEN, &len);
    if (len != TIL_QUERY_STRLEN) {
        /* allocate one additional character for the terminating NUL */
        TIL_PTSTR str = (TIL_PTSTR) malloc( (len+1) * sizeof(TIL_TCHAR) );
        if (str) {
            if (TilGetString(prop, str, len, NULL)) {
                return str;
            }
            free(str);
        }
    }

    return InvalidString;
}

void DeleteString(TIL_PCTSTR str)
{
    if (str != InvalidString) {
        free((void*)str);
    }
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//  Redundant TIL_TIME64 convertion
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/

#if QUERY_TIME64

#include <windows.h>

TIL_BOOL TilTime64ToTilDateTime(TIL_TIME64 t, TIL_PDATETIME pdt)
{
    SYSTEMTIME    st;
    FILETIME      ft;
    LARGE_INTEGER li;

    static TIL_INT64 UndoWinApiRounding = 0;
#if 1
    /* unnecessary WIN32 API check */
    static TIL_BOOL WinApiCheckDone = TIL_FALSE;
    if (!WinApiCheckDone) {
        /* check whether the WIN32 API does round the millisecond part */
        ft.dwHighDateTime = 0;
        ft.dwLowDateTime  = 29999;
        FileTimeToSystemTime(&ft, &st);
        if (st.wMilliseconds != 2) {
            _putts(_TEXT("\nWARNING: FileTimeToSystemTime(&ft, &st) unexpectedly rounds ft!"));
            ft.dwLowDateTime = 24999;
            FileTimeToSystemTime(&ft, &st);
            if (st.wMilliseconds == 2) {
                ft.dwLowDateTime = 25000;
                FileTimeToSystemTime(&ft, &st);
                if (st.wMilliseconds == 3) {
                    _putts(_TEXT("Undoing FileTimeToSystemTime rounding."));
                    UndoWinApiRounding = 5000;
                }
            }
        }
        WinApiCheckDone = TIL_TRUE;
    }
#endif

    if (pdt == NULL) {
        return TIL_FALSE;
    }

    memset(pdt, '\0', sizeof(TIL_DATETIME));
    if (t < -109205 || t > 10565994) {
        return TIL_FALSE;
    }
    if (t == 0) {
        return TIL_TRUE;
    }

    li.QuadPart  = (LONGLONG) (t * 864000000000i64);
    li.QuadPart += 94353120000000005i64;
    if (UndoWinApiRounding) {
        li.QuadPart -= UndoWinApiRounding;
    }
    ft.dwLowDateTime  = li.LowPart;
    ft.dwHighDateTime = li.HighPart;
    FileTimeToSystemTime(&ft, &st);
    if (UndoWinApiRounding) {
        li.QuadPart += UndoWinApiRounding;
    }
    pdt->year  = (TIL_UINT16) st.wYear;
    pdt->month = (TIL_UINT16) st.wMonth;
    pdt->day   = (TIL_UINT16) st.wDay;
    pdt->hour  = (TIL_UINT16) st.wHour;
    pdt->min   = (TIL_UINT16) st.wMinute;
    pdt->sec   = (TIL_UINT16) st.wSecond;
    pdt->usec  = (TIL_UINT32) (li.QuadPart % 10000000) / 10;

    return TIL_TRUE;
}

#endif

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//  Cast operations
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/

unsigned DataSizeToUnsigned(TIL_DATASIZE ds)
{
    unsigned result = (unsigned) ds;

    if (ds != (TIL_DATASIZE) result) {
        _putts(_TEXT("ERROR: Object too large!"));
        return 0;
    }

    return result;
}

TIL_BOOL ValidBlockSize(TIL_UINT64 cnt, size_t elt_size)
{
    if (cnt > SIZE_MAX / elt_size) {
        _putts(_TEXT("ERROR: Object too large!"));
        return TIL_FALSE;
    }

    return TIL_TRUE;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//  Auxiliary functions
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/

const _TCHAR *GetDataTypeText(int DataType, TIL_UINT64 SampleCount)
{
    #define BUFF_COUNT 256
    static _TCHAR Str[BUFF_COUNT];
    switch(DataType) {
    case dtSampleNumber:
        _sntprintf(Str, BUFF_COUNT, _TEXT("sample number [0;%I64u]; size: 0 bytes"), SampleCount-1);
        return Str;
    case dt_u8:
        return _TEXT("unsigned char (Intel); size: 1 byte");
    case dt_s8:
        return _TEXT("signed char (Intel); size: 1 byte");
    case dt_u16:
        return _TEXT("unsigned short (Intel); size: 2 bytes");
    case dt_s16:
        return _TEXT("signed short (Intel); size: 2 bytes");
    case dt_u32:
        return _TEXT("unsigned long (Intel); size: 4 bytes");
    case dt_s32:
        return _TEXT("signed long (Intel); size: 4 bytes");
    case dt_u64:
        return _TEXT("unsigned __int64 (Intel); size: 8 bytes");
    case dt_s64:
        return _TEXT("signed __int64 (Intel); size: 8 bytes");
    case dt_f32:
        return _TEXT("float (Intel); size: 4 bytes");
    case dt_f64:
        return _TEXT("double (Intel); size: 8 bytes");
    case dt_f80:
        return _TEXT("long double (Intel); size: 10 bytes");
    }

    return _TEXT("unknown");
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//  main()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/

int __cdecl _tmain(int argc, _TCHAR *argv[])
{
    TIL_POBJECT pObject;
    TIL_PPROP   Root, Control, FileName, Prop;
    TIL_PCTSTR  Str;

    if (argc < 2) {
        _putts(
            _TEXT("VIEW_TMS V1.3.1 - Copyright (c) 2001-2011 Tellert Elektronik GmbH\n")
            _TEXT("SYNTAX: VIEW_TMS filename.tms")
            );
        return 87;
    }

    /*
       Note, it is OK to pass on a possible NULL pointer to the Til-functions. However,
       the corresponding function will then report a failure.
    */
    pObject  = TilCreateObject(TEMES_TIL_GUID);
    Root     = TilGetRootProperty(pObject);
    Control  = TilGetProperty(Root, tilControl);
    FileName = TilGetProperty(Control, tilControlFileName);
    if (!TilSetString(FileName, argv[1])) {
        _tprintf(_TEXT("Cannot open \"%s\"!\n"), argv[1]);
    } else {
        TIL_PPROP Data   = TilGetProperty(Root, tilData);
        TIL_PPROP Meas   = TilGetProperty(Data, tilDataMeas);
        TIL_PPROP Meas0  = TilGetVectorItem(Meas, 0);
        TIL_PPROP Groups = TilGetProperty(Meas0, tilDataMeasGroup);
        unsigned nGroups = DataSizeToUnsigned(TilGetVectorSize(Groups));
        unsigned g;

        for (g = 0; g < nGroups; g++) {
            TIL_PPROP    Group, Info, Time, Time64, Sections, Signals;
            unsigned     nSections, nSignals, s, k;
            TSignalData  *sd;
            TIL_UINT64   SampleCount;
            double       SampleRate;
#if QUERY_TIME64
            TIL_TIME64   time64;
            TIL_DATETIME dt;
#endif

            /* Initialize property values */
            SampleCount = 0;
            SampleRate  = 0;

            _tprintf(_TEXT("\nGroup %u/%u:\n"), g+1, nGroups);
            Group = TilGetVectorItem(Groups, g);
            Prop  = TilGetProperty(Group, tilDataMeasGroupSampleCount);
            if (TilGetUInt64(Prop, &SampleCount)) {
                _tprintf(_TEXT("  SampleCount = %I64u\n"), SampleCount);
            }
            Prop = TilGetProperty(Group, tilDataMeasGroupSampleRate);
            if (TilGetFloat64(Prop, &SampleRate)) {
                _tprintf(_TEXT("  SampleRate = %lg s\n"), SampleRate);
            }
            Info = TilGetProperty(Group, tilDataMeasGroupInfo);
            Time = TilGetProperty(Info, tilDataMeasGroupInfoTime);
            Time64 = TilGetProperty(Time, tilDataMeasGroupInfoTimeHWSetup);
            Str  = NewString(Time64);
            if (*Str) {
                _tprintf(_TEXT("  HWSetup = %s"), Str);
#if QUERY_TIME64
                time64 = 0;
                TilGetTime64(Time64, &time64);
                TilTime64ToTilDateTime(time64, &dt);
                _tprintf(_TEXT(" (%04u-%02u-%02u %02u:%02u:%02u.%06u)"),
                    (unsigned)dt.year, (unsigned)dt.month, (unsigned)dt.day,
                    (unsigned)dt.hour, (unsigned)dt.min, (unsigned)dt.sec,
                    (unsigned)dt.usec);
#endif
                _tprintf(_TEXT("\n"));
            }
            DeleteString(Str);
            Time64 = TilGetProperty(Time, tilDataMeasGroupInfoTimeStart);
            Str = NewString(Time64);
            if (*Str) {
                _tprintf(_TEXT("  Start = %s"), Str);
#if QUERY_TIME64
                time64 = 0;
                TilGetTime64(Time64, &time64);
                TilTime64ToTilDateTime(time64, &dt);
                _tprintf(_TEXT(" (%04u-%02u-%02u %02u:%02u:%02u.%06u)"),
                    (unsigned)dt.year, (unsigned)dt.month, (unsigned)dt.day,
                    (unsigned)dt.hour, (unsigned)dt.min, (unsigned)dt.sec,
                    (unsigned)dt.usec);
#endif
                _tprintf(_TEXT("\n"));
            }
            DeleteString(Str);
            Time64 = TilGetProperty(Time, tilDataMeasGroupInfoTimeHWReadOut);
            Str = NewString(Time64);
            if (*Str) {
                _tprintf(_TEXT("  HWReadOut = %s"), Str);
#if QUERY_TIME64
                time64 = 0;
                TilGetTime64(Time64, &time64);
                TilTime64ToTilDateTime(time64, &dt);
                _tprintf(_TEXT(" (%04u-%02u-%02u %02u:%02u:%02u.%06u)"),
                    (unsigned)dt.year, (unsigned)dt.month, (unsigned)dt.day,
                    (unsigned)dt.hour, (unsigned)dt.min, (unsigned)dt.sec,
                    (unsigned)dt.usec);
#endif
                _tprintf(_TEXT("\n"));
            }
            Sections  = TilGetProperty(Time, tilDataMeasGroupInfoTimeSection);
            nSections = DataSizeToUnsigned(TilGetVectorSize(Sections));

            for (s = 0; s < nSections; s++) {
                TIL_PPROP  Section = TilGetVectorItem(Sections, s);
                TIL_UINT64 SampleIndex, LastSampleIndex;

                /* Initialize property values */
                SampleIndex     = 0;
                LastSampleIndex = 0;

                Prop = TilGetProperty(Section, tilDataMeasGroupInfoTimeSectionSampleIndex);
                TilGetUInt64(Prop, &SampleIndex);

                if (s < nSections-1) {
                    TIL_PPROP NextSection = TilGetVectorItem(Sections, s+1);
                    Prop = TilGetProperty(NextSection, tilDataMeasGroupInfoTimeSectionSampleIndex);
                    if (!TilGetUInt64(Prop, &LastSampleIndex)) {
                        LastSampleIndex = 0;
                    }
                    if (LastSampleIndex) {
                        --LastSampleIndex;
                    }
                } else {
                    LastSampleIndex = SampleCount ? SampleCount - 1 : 0;
                }

                Time64 = TilGetProperty(Section, tilDataMeasGroupInfoTimeSectionStart);
                Str = NewString(Time64);
                _tprintf(_TEXT("  Sample range of section%u = [%I64u;%I64u] at %s"),
                  s+1, SampleIndex, LastSampleIndex, Str);
#if QUERY_TIME64
                time64 = 0;
                TilGetTime64(Time64, &time64);
                TilTime64ToTilDateTime(time64, &dt);
                _tprintf(_TEXT(" (%04u-%02u-%02u %02u:%02u:%02u.%06u)"),
                    (unsigned)dt.year, (unsigned)dt.month, (unsigned)dt.day,
                    (unsigned)dt.hour, (unsigned)dt.min, (unsigned)dt.sec,
                    (unsigned)dt.usec);
#endif
                _tprintf(_TEXT("\n"));
                DeleteString(Str);
            }
            Signals  = TilGetProperty(Group, tilDataMeasGroupSignal);
            nSignals = DataSizeToUnsigned(TilGetVectorSize(Signals));
            sd       = NewInitializedSignalDataVector(nSignals);
            for (s = 0; s < nSignals; s++) {
                TIL_PPROP  Signal, Comm, Data, Raw, File, Flags, View;
                TIL_UINT64 FileOffset;
                unsigned   DataType, DataSize, Gap, Quantized;
                double     Factor, Offset, Min, Max;

                /* Initialize property values */
                DataType   = 0;
                DataSize   = 0;
                FileOffset = 0;
                Gap        = 0;
                Factor     = 1.;
                Offset     = 0.;
                Quantized  = TIL_FALSE;
                Min        = 0.;
                Max        = 0.;

                _tprintf(_TEXT("\n  Signal %u/%u%s:\n"), s+1, nSignals,
                  ((s == 0) ? _TEXT(" (= Reference Signal)") : _TEXT("")));
                Signal = TilGetVectorItem(Signals, s);
                Comm   = TilGetProperty(Signal, tilDataMeasGroupSignalComm);
                Str    = NewString(TilGetProperty(Comm, tilDataMeasGroupSignalCommName));
                if (Str != InvalidString) {
                    _tprintf(_TEXT("    Name = %s\n"), Str);
                }
                DeleteString(Str);
                Str = NewString(TilGetProperty(Signal, tilDataMeasGroupSignalUnit));
                if (Str != InvalidString) {
                    _tprintf(_TEXT("    Unit = %s\n"), Str);
                }
                DeleteString(Str);
                Data = TilGetProperty(Signal, tilDataMeasGroupSignalData);
                Raw  = TilGetProperty(Data, tilDataMeasGroupSignalDataRaw);
                Prop = TilGetProperty(Raw, tilDataMeasGroupSignalDataRawType);
                TilGetUInt(Prop, &DataType);
                Prop = TilGetProperty(Raw, tilDataMeasGroupSignalDataRawFactor);
                TilGetFloat64(Prop, &Factor);
                Prop = TilGetProperty(Raw, tilDataMeasGroupSignalDataRawOffset);
                TilGetFloat64(Prop, &Offset);
                _tprintf(_TEXT("    DataType = %s\n"), GetDataTypeText(DataType, SampleCount));
                _tprintf(_TEXT("    Factor = %lg\n"), Factor);
                _tprintf(_TEXT("    Offset = %lg\n"), Offset);
                File = TilGetProperty(Raw, tilDataMeasGroupSignalDataRawFile);
                if (File) {
                    /*
                        Note that the file property may not exist for data type dtSampleNumber
                    */
                    Str = NewString(TilGetProperty(File, tilDataMeasGroupSignalDataRawFileName));
                    if (*Str) {
                        _tprintf(_TEXT("    FileName = %s\n"), Str);
                    }
                    DeleteString(Str);
                    Prop = TilGetProperty(File, tilDataMeasGroupSignalDataRawFileOffset);
                    TilGetUInt64(Prop, &FileOffset);
                    Prop = TilGetProperty(File, tilDataMeasGroupSignalDataRawFileDataSize);
                    TilGetUInt(Prop, &DataSize);
                    Prop = TilGetProperty(File, tilDataMeasGroupSignalDataRawFileGap);
                    TilGetUInt(Prop, &Gap);
                    _tprintf(_TEXT("    FileOffset = %I64u\n"), FileOffset);
                    _tprintf(_TEXT("    FileDataSize = %u\n"), DataSize);
                    _tprintf(_TEXT("    FileGap = %u\n"), Gap);
                }
                Flags = TilGetProperty(Data, tilDataMeasGroupSignalDataFlags);
                Prop = TilGetProperty(Flags, tilDataMeasGroupSignalDataFlagsQuantized);
                if (Prop) {
                    /*
                        Note that signal flag properties may not exist if they are
                        false.
                    */
                    TilGetUInt(Prop, &Quantized);
                }
                _tprintf(_TEXT("    FlagQuantized = %u\n"), Quantized);
                View = TilGetProperty(Signal, tilDataMeasGroupSignalView);
                Prop = TilGetProperty(View, tilDataMeasGroupSignalViewMin);
                TilGetFloat64(Prop, &Min);
                Prop = TilGetProperty(View, tilDataMeasGroupSignalViewMax);
                TilGetFloat64(Prop, &Max);
                _tprintf(_TEXT("    ViewMin = %lg\n"), Min);
                _tprintf(_TEXT("    ViewMax = %lg\n"), Max);

#if ACCESS_RAW_DATA
                /* Raw signal data (not required; only for demonstration purposes) */
                Prop = TilGetProperty(Raw, tilDataMeasGroupSignalDataRawIterator);
                if (Prop ) {
                    /*
                        Note that the raw iterator property may not exist for data type
                        dtSampleNumber.
                    */
                    size_t DataSize = TilGetIteratorItemSize(Prop);
                    if (ValidBlockSize(SampleCount, DataSize)) {
                        sd[s].RawData = NewCharVector(DataSize * (size_t)SampleCount);
                        if (sd[s].RawData) {
                            size_t RemainingSamples = (size_t)SampleCount;
                            char   *pNextSamples    = sd[s].RawData;
                            do {
                                size_t BlockSampleCount = (RemainingSamples > SAMPLES_MAX) ? SAMPLES_MAX : RemainingSamples;
                                if (!TilGetNextIteratorItems(Prop, pNextSamples, BlockSampleCount)) {
                                    memset(pNextSamples, '\0', DataSize * RemainingSamples);
                                    break;
                                }
                                pNextSamples += DataSize * BlockSampleCount;
                                RemainingSamples -= BlockSampleCount;
                            } while (RemainingSamples);
                        }
                    }
                }
#endif

                /* "double" signal data */
                Prop = TilGetProperty(Data, tilDataMeasGroupSignalDataIterator);
                if (Prop) {
                    /*
                        Note that the iterator property may not exist for the data type dtFloat80 on
                        Windows x64 platforms.
                    */
                    if (ValidBlockSize(SampleCount, sizeof(double))) {
                        sd[s].Data = NewDoubleVector((size_t)SampleCount);
                        if (sd[s].Data) {
                            size_t RemainingSamples = (size_t)SampleCount;
                            double *pNextSamples    = sd[s].Data;
                            do {
                                size_t BlockSampleCount = (RemainingSamples > SAMPLES_MAX) ? SAMPLES_MAX : RemainingSamples;
#ifndef SUPPORT_OLDER_TILTMS_VERSIONS
                                if (!TilGetNextIteratorItems(Prop, pNextSamples, BlockSampleCount)) {
                                    memset(pNextSamples, '\0', sizeof(double) * RemainingSamples);
                                    break;
                                }
#else
								memset(pNextSamples, '\0', sizeof(double) * BlockSampleCount);
                                TilGetNextIteratorItems(Prop, pNextSamples, BlockSampleCount);
#endif
                                pNextSamples += BlockSampleCount;
                                RemainingSamples -= BlockSampleCount;
                            } while (RemainingSamples);
                        }
                    }
                }
            }

            _tprintf(_TEXT("\n  Sample values:\n"));
            for (k = 0; k < SampleCount; k++) {
                for (s = 0; s < nSignals; s++) {
                    if (s == 0) {
                        _tprintf(_TEXT("    "));
                    }
                    if (sd[s].Data == 0) {
                        _tprintf(_TEXT("XXX"));
                    } else {
                        _tprintf(_TEXT("%lg"), sd[s].Data[k]);
                    }
                    _tprintf((s+1 == nSignals) ? _TEXT("\n") : _TEXT("\t"));
                }
            }

            /*
                The signal data is no longer needed and can be freed.
                Note that CreateSignalDataVector initialized its members with
                zeros, thus it is safe to call Delete...Vector(sd[s]...) even
                if we never allocated the corresponding vector.
            */
            for (s = 0; s < nSignals; s++) {
                DeleteCharVector(sd[s].RawData);
                DeleteDoubleVector(sd[s].Data);
            }
            DeleteSignalDataVector(sd);
        }
    }

    /*
        We don't longer need any TEMES property and can therefore free the resources.
        Note that afterwards also a possible temporary TEMES data file can then no
        longer be opened.
    */
    TilDestroyObject(pObject);

    return 0;
}
