// file_studio_setup.h: Schnittstelle für die Klasse FileStudio.
//
//////////////////////////////////////////////////////////////////////
#pragma once
#ifndef FILE_STUDIO_SETUP_H
#define FILE_STUDIO_SETUP_H

#include "interface/studio_setup_structs.h"
#include "common/crc.h"
#include "common/md5.h"
#include "common/software_version.h"

#include <cstdio>
#include <array>
#include <vector>
#include <string>
#include <bitset>

// Angabe ueber die maximal bekannte Firmware-Version.
// Aktuell: 04.01.09.99, 04.02.05.99, 04.03.12.99
// Siehe Function: FileStudio::IsSupportedFirmwareVersion

//#define FILESTUDIOHEADER_CHECK 0xDFDFDFDF ///< StructFileStudioHeader.check: Pruefwert fuer Gueltigkeit (Versionskennung) 
// Einen Dezimalwert 99 in Hexwert 0x99 wandeln.
//#define AESHEADER_DEC2VERSION(x) (x + (x / 10) * 6)
// Einen Hexwert 0x99 in Dezimalwert 99 wandeln.
//#define AESHEADER_VERSION2DEC(x) (x - (x / 16) * 6)
//#define FILESTUDIOHEADER_VERSION(master, major, minor, build) ((master & 0xFF) << 24 | (major & 0xFF) << 16 | (minor & 0xFF) << 8 | (build & 0xFF))
//#define FILESTUDIOHEADER_VERSION_UNSUPPORTED                  FILESTUDIOHEADER_VERSION(99, 99, 99, 99)

// Flags fuer den Dateiheader
//#define FILESTUDIOHEADER_FLAG_IGNORE_SHOULD_STUDIO_VERSION        0x00000001
//#define FILESTUDIOHEADER_FLAG_IGNORE_SHOULD_LIBRARY_VERSION       0x00000002
//#define FILESTUDIOHEADER_FLAG_IGNORE_SHOULD_FIRMWARE1_VERSION     0x00000004
//#define FILESTUDIOHEADER_FLAG_IGNORE_SHOULD_FIRMWARE2_VERSION     0x00000008
//#define FILESTUDIOHEADER_FLAG_IGNORE_SHOULD_FIRMWARE3_VERSION     0x00000010

// Flags fuer die Loadroutine Pruefung auf Versionen
//#define FILESTUDIOHEADER_IGNORE_MIN_STUDIO_VERSION            0x00000001
//#define FILESTUDIOHEADER_IGNORE_SHOULD_STUDIO_VERSION         0x00000002
//#define FILESTUDIOHEADER_IGNORE_MIN_LIBRARY_VERSION           0x00000004
//#define FILESTUDIOHEADER_IGNORE_SHOULD_LIBRARY_VERSION        0x00000008

#pragma pack(push, 1)
// Wird der Header verwendet, wird eine Firmware 04.00.xx nicht mehr unterstuetzt!

struct StructFileStudioHeader
{
    unsigned int check;                      ///< Muss immer mit FILESTUDIOHEADER_CHECK belegt werden. 
    ///< Wird herangezogen um zu erkennen ob eine Headerstruktur in der Datei vorliegt.
    unsigned int bugfixSize;                 ///< Groesse der Struktur sizeof_StructFileStudioHeader_1, Version 1. Naeheres siehe Erleuterung zur bugfixSize weiter unten.
    ///< Ist auch gleichzeitig eine Art Datei-Version. Die Struktur kann derzeit maximal (EEP_SIZE - EEP_SIZE_SU) gross werden.
    unsigned int flags;                      ///< Flags fuer z. B. "Gelesen mit DFComDLL, jedoch noch nicht bearbeitet"
    unsigned int studioVersion;              ///< Studioversion mit der die Datei zuletzt abgespeichert wurde.
    ///< Wurde die Datei ueber die DLL ausgelesen liegt hier keine Angabe vor!
    // Mindestversionen
    unsigned int minStudioVersion;           ///< Mindestversion des einzusetzenden Studios, um alle enthaltenen Daten zu unterstuetzen.
    unsigned int shouldStudioVersion;        ///< Sollteversion des einzusetzenden Studios, um alle enthaltenen Daten zu unterstuetzen.
    unsigned int minLibraryVersion;          ///< Mindestversion der zu verwendenden DFComDLL fuer die korrekte Uebertragung der Setupdaten.
    unsigned int shouldLibraryVersion;       ///< Sollteversion der zu verwendenden DFComDLL fuer die korrekte Uebertragung der Setupdaten.
    unsigned int minFirmware1Version;        ///< Mindestversion der zu verwendenden Firmware 04.01.xx.xx, um alle gewaehlten Einstellungen zu unterstuetzen.
    unsigned int shouldFirmware1Version;     ///< Sollteversion der zu verwendenden Firmware 04.01.xx.xx, um alle gewaehlten Einstellungen zu unterstuetzen.
    unsigned int minFirmware2Version;        ///< Mindestversion der zu verwendenden Firmware 04.02.xx.xx, um alle gewaehlten Einstellungen zu unterstuetzen.
    unsigned int shouldFirmware2Version;     ///< Sollteversion der zu verwendenden Firmware 04.02.xx.xx, um alle gewaehlten Einstellungen zu unterstuetzen.
    // CRC der Dateibereiche
    unsigned int crcEep;                     ///< CRC ueber Eep-Bereich.
    unsigned int crcEepExtended;             ///< CRC ueber EepExtended-Bereich (dort liegt auch Header jedoch mit eigenem CRC)
    unsigned int crcFlash;                   ///< CRC ueber Flash-Bereich.
    unsigned int crcListFile;                ///< CRC ueber die Listendateiangaben.
    unsigned int crcConfig;                  ///< CRC ueber die Config-Struktur.
    unsigned int crcFlashExtended;           ///< CRC ueber FlashExtended-Bereich.
    unsigned int crcListEntranceFile;        ///< CRC ueber die Listendateiangaben der ZK1.
    unsigned int crcListEntrance2File;       ///< CRC ueber die Listendateiangaben der ZK2.
    unsigned int crcListExtendedFile;        ///< CRC ueber die Zusaetzlichen Listendateiangaben.
    // Mindestversionen (Hinzugefuegt ab Version 04.03.xx)
    unsigned int bugfixCRC;                  ///< CRC der Headerstruktur Version 1. Naeheres siehe Erleuterung zum bugfixCRC weiter unten.
    ///< Folgende Angaben sind ab VERSION 2 verfuegbar.
    unsigned int size;                       ///< Groesse der Struktur sizeof(StructFileStudioHeader) ab Version 2.
    ///< ACHTUNG: Bei Verwendung wird eine Mindestversion 04.03.xx des Studio und der Library vorausgesetzt.
    unsigned int minFirmware3Version;        ///< Mindestversion der zu verwendenden Firmware 04.03.xx.xx, um alle gewaehlten Einstellungen zu unterstuetzen.
    unsigned int shouldFirmware3Version;     ///< Sollteversion der zu verwendenden Firmware 04.03.xx.xx, um alle gewaehlten Einstellungen zu unterstuetzen.
    //...
    unsigned int crc;                        ///< CRC ueber alle Kopfdaten
};

#define sizeof_StructFileStudioHeader_1     88 // Strukturgroesse der Version 1 ist: 88Byte
#define sizeof_StructFileStudioHeader_2    104 // Strukturgroesse der Version 2 ist: 104Byte

#pragma pack()

/*
Erlaeuterung zum bugfixCRC.
Vorgaengerversionen der Software pruefen zuerst den CRC ueber ihren direkten Strukturzugriff (Version 1). Da sich der CRC bei aktuelleren Versionen jedoch weiter nach hinten
verschiebt, fuehrt dieses dazu, dass es zu einem Head-CRC-Fehler kommt bevor die Pruefung auf die Mindestversion der Library oder des Studios kommt.
Ab dieser Version wird der CRC in den letzten 4 Byte der Struktur auf Grundlage ihrer size Angabe erwartet. Zukuenftig hinzugefuegte Werte in der Struktur werden
unangetastet mitverarbeitet. Muss eine dieser Variablen beachtet werden, muss die Version der Library und des Studios auf die entsprechende Mindestversion gesetzt werden. 
Wird zum Beispiel der Parameter minFirmware3Version verwendet, muss die Library und Studio Version auf mindestens 4.3.0.0 gestellt werden, damit dieser Parameter auch
beachtet und verarbeitet wird.
*/

/*
template<std::size_t N, typename Enum>
class FlagSet : public std::bitset<N>
{
public:
    const FlagSet( const FlagSet& other ) : bitset( other.bitset )
    {

    }
};
*/

class FileStudio
{
public:
    // Flags fuer den Dateiheader
    enum Enum_headerflagname_ignore_version
    {
        hfiv_studio          = 0x00000001,
        hfiv_library         = 0x00000002,
        hfiv_firmware1       = 0x00000004,
        hfiv_firmware2       = 0x00000008,
        hfiv_firmware3       = 0x00000010,
    };
    
    // Flags fuer die Loadroutine Pruefung auf Versionen
    enum Enum_flag_ignore_version
    {
        fiv_min_studio       = 0x00000001,
        fiv_should_studio    = 0x00000002,
        fiv_min_library      = 0x00000004,
        fiv_should_library   = 0x00000008
    };

public:
    FileStudio();
    virtual ~FileStudio();

    void SetDefault( int flags = 0 );
    void SetDefaultHeader();

    int load( const std::string& file_path, int *error, Enum_flag_ignore_version flags );
    int save( const std::string& file_path, int *error );
    unsigned int GetFlashSetupSize();
    unsigned int GetExtendedFlashSetupSize();
    unsigned int GetTablesSize( bool lists );  ///< Ermittelt die Groesse der Listen- oder Datensazbeschreibungen.
    int NeededVersion();

    unsigned short endian_reverse( unsigned short val );
    unsigned int endian_reverse( unsigned int val );

    int buildHeader();
    int checkHeader();

    static unsigned int magic_header_number()
    {
        return 0xDFDFDFDF;
    }
    static bool is_magic_header_number( unsigned int number )
    {
        return (number == magic_header_number());
    }
    static unsigned int header_version(unsigned char master, unsigned char major, unsigned char minor, unsigned char build)
    {
        return (((master & 0xFF) << 24) | ((major & 0xFF) << 16) | ((minor & 0xFF) << 8) | (build & 0xFF));
    }
    static unsigned int invalid_header_version()
    {
        return header_version( 99, 99, 99, 99 );
    }
    static bool is_invalid_header_version( unsigned int version )
    {
        return (invalid_header_version() == version);
    }
    inline int IsHeader()
    {
        return is_magic_header_number( m_file_header.check );
    }

    void SetStudioVersion( const char *version )
    {
        m_studioVersion.parse( version );
        m_file_header.studioVersion = header_version( m_studioVersion.master(), m_studioVersion.major(), m_studioVersion.minor(), m_studioVersion.build() );
    }

    // Anpassung auf eventuell benoetigte Mindestversion.
    void NeedStudioVersion( const char *version )
    {
        SoftwarebuildVersion minStudioVersion( m_file_header.minStudioVersion );
        SoftwarebuildVersion minVersion( version );
        if ( minStudioVersion < minVersion )
            m_file_header.minStudioVersion = header_version( minVersion.master(), minVersion.major(), minVersion.minor(), minVersion.build() );
    }

    void ShouldStudioVersion( const char *version )
    {
        SoftwarebuildVersion studioVersion( m_file_header.shouldStudioVersion );
        SoftwarebuildVersion shouldVersion( version );
        if ( studioVersion < shouldVersion )
            m_file_header.shouldStudioVersion = header_version( shouldVersion.master(), shouldVersion.major(), shouldVersion.minor(), shouldVersion.build() );
    }

    void SetLibraryVersion( const char *version )
    {
        m_libraryVersion.parse( version );
    }

    void NeedLibraryVersion( const char *version )
    {
        SoftwarebuildVersion minLibraryVersion( m_file_header.minLibraryVersion );
        SoftwarebuildVersion minVersion( version );
        if ( minLibraryVersion < minVersion )
            m_file_header.minLibraryVersion = header_version( minVersion.master(), minVersion.major(), minVersion.minor(), minVersion.build() );
    }

    void ShouldLibraryVersion( const char *version )
    {
        SoftwarebuildVersion libraryVersion( m_file_header.shouldLibraryVersion );
        SoftwarebuildVersion shouldVersion( version );
        if ( libraryVersion < shouldVersion )
            m_file_header.shouldLibraryVersion = header_version( shouldVersion.master(), shouldVersion.major(), shouldVersion.minor(), shouldVersion.build() );
    }

    void ShouldFirmware1Version( const char *version )
    {
        SoftwarebuildVersion firmwareVersion( m_file_header.shouldFirmware1Version );
        SoftwarebuildVersion shouldVersion( version );
        if ( firmwareVersion < shouldVersion )
        {
            m_file_header.shouldFirmware1Version = header_version( shouldVersion.master(), shouldVersion.major(), shouldVersion.minor(), shouldVersion.build() );
            NeedLibraryVersion( "04.02.02.08" );
            NeedStudioVersion( "04.02.02.08" );
        }
    }

    void NeedFirmware1Version( const char *version )
    {
        SoftwarebuildVersion minFirmwareVersion( m_file_header.minFirmware1Version );
        SoftwarebuildVersion minVersion( version );
        if ( minFirmwareVersion < minVersion )
        {
            m_file_header.minFirmware1Version = header_version( minVersion.master(), minVersion.major(), minVersion.minor(), minVersion.build() );
            NeedLibraryVersion( "04.02.02.08" );
            NeedStudioVersion( "04.02.02.08" );
        }
    }

    void UnsupportFirmware1Version()
    {
        m_file_header.minFirmware1Version = invalid_header_version();
    }

    bool supportFirmware1Version()
    {
        return !is_invalid_header_version( m_file_header.minFirmware1Version );
    }

    void ShouldFirmware2Version( const char *version )
    {
        SoftwarebuildVersion firmwareVersion( m_file_header.shouldFirmware2Version );
        SoftwarebuildVersion shouldVersion( version );
        if ( firmwareVersion < shouldVersion )
        {
            m_file_header.shouldFirmware2Version = header_version( shouldVersion.master(), shouldVersion.major(), shouldVersion.minor(), shouldVersion.build() );
            NeedLibraryVersion( "04.02.02.08" );
            NeedStudioVersion( "04.02.02.08" );
        }
    }

    void NeedFirmware2Version( const char *version )
    {
        SoftwarebuildVersion minFirmwareVersion( m_file_header.minFirmware2Version );
        SoftwarebuildVersion minVersion( version );
        if ( minFirmwareVersion < minVersion )
        {
            m_file_header.minFirmware2Version = header_version( minVersion.master(), minVersion.major(), minVersion.minor(), minVersion.build() );
            NeedLibraryVersion( "04.02.02.08" );
            NeedStudioVersion( "04.02.02.08" );
        }
    }

    void UnsupportFirmware2Version()
    {
        m_file_header.minFirmware2Version = invalid_header_version();
    }

    bool supportFirmware2Version()
    {
        return !is_invalid_header_version( m_file_header.minFirmware2Version );
    }

    void ShouldFirmware3Version( const char *version )
    {
        SoftwarebuildVersion firmwareVersion( m_file_header.shouldFirmware3Version );
        SoftwarebuildVersion shouldVersion( version );
        if ( firmwareVersion < shouldVersion )
        {
            m_file_header.shouldFirmware3Version = header_version( shouldVersion.master(), shouldVersion.major(), shouldVersion.minor(), shouldVersion.build() );
            NeedLibraryVersion( "04.03.00.08" );
            NeedStudioVersion( "04.03.00.08" );
        }
    }

    void NeedFirmware3Version( const char *version )
    {
        SoftwarebuildVersion minFirmwareVersion( m_file_header.minFirmware3Version );
        SoftwarebuildVersion minVersion( version );
        if ( minFirmwareVersion < minVersion )
        {
            m_file_header.minFirmware3Version = header_version( minVersion.master(), minVersion.major(), minVersion.minor(), minVersion.build() );
            NeedLibraryVersion( "04.03.00.08" );
            NeedStudioVersion( "04.03.00.08" );
        }
    }

    void UnsupportFirmware3Version()
    {
        m_file_header.minFirmware3Version = invalid_header_version();
        NeedLibraryVersion( "04.03.00.08" );
        NeedStudioVersion( "04.03.00.08" );
    }

    bool supportFirmware3Version()
    {
        return !is_invalid_header_version( m_file_header.minFirmware3Version );
    }

    void NeedFirmwareVersion( const char *version )
    {
        SoftwarebuildVersion fwVersion( version );
        // Pruefung auf 04.01.xx
        if ( fwVersion >= "04.01.01.00" && fwVersion < "04.02.00.00" )
        {
            // Mindestversion der Firmware 04.01.xx.yy setzen.
            NeedFirmware1Version( version );
        }
        else if ( fwVersion >= "04.02.00.00" && fwVersion < "04.03.00.00" )
        {
            // Mindestversion der Firmware 04.02.xx.yy setzen.
            NeedFirmware2Version( version );
        }
        else if ( fwVersion >= "04.03.00.00" && fwVersion < "04.04.00.00" )
        {
            // Mindestversion der Firmware 04.03.xx.yy setzen.
            NeedFirmware3Version( version );
        }
    }

    void ShouldFirmwareVersion( const char *version )
    {
        SoftwarebuildVersion fwVersion( version );
        // Pruefung auf 04.01.xx
        if ( fwVersion >= "04.01.01.00" && fwVersion < "04.02.00.00" )
        {
            // Empfohlene Firmware 04.01.xx.yy eintragen.
            ShouldFirmware1Version( version );
        }
        else if ( fwVersion >= "04.02.00.00" && fwVersion < "04.03.00.00" )
        {
            // Empfohlene Firmware 04.02.xx.yy pruefen?
            ShouldFirmware2Version( version );
        }
        else if ( fwVersion >= "04.03.00.00" && fwVersion < "04.04.00.00" )
        {
            // Empfohlene Firmware 04.03.xx.yy pruefen?
            ShouldFirmware3Version( version );
        }
    }

    int IsSupportedFirmwareVersion( const char *version )
    {
        SoftwarebuildVersion fwVersion( version );
        // Pruefung auf 04.xx.yy
        if ( (fwVersion >= "04.01.01.00" && fwVersion <= "04.01.09.99") 
            || (fwVersion >= "04.02.00.00" && fwVersion <= "04.02.05.99") 
            || (fwVersion >= "04.03.00.00" && fwVersion <= "04.03.12.99") )
        {
            return 1;
        }

        return 0;
    }
    int ValidFirmwareVersion( SoftwarebuildVersion &fwVersion );

    // CRC ueber Dateibereiche
    unsigned int GetCrcEep();                ///< CRC ueber Eep-Bereich.
    unsigned int GetCrcEepExtended();        ///< CRC ueber Eep-Bereich.
    unsigned int GetCrcFlash();              ///< CRC ueber Flash-Bereich.
    unsigned int GetCrcListFile();           ///< CRC ueber die Listendateiangaben.
    unsigned int GetCrcConfig();             ///< CRC ueber die Config-Struktur.
    unsigned int GetCrcFlashExtended();      ///< CRC ueber FlashExtended-Bereich.
    unsigned int GetCrcListEntranceFile();   ///< CRC ueber die Listendateiangaben der ZK1.
    unsigned int GetCrcListEntrance2File();  ///< CRC ueber die Listendateiangaben der ZK2.
    unsigned int GetCrcListExtendedFile();   ///< CRC ueber die Zusaetzlichen Listendateiangaben.
    // MD5 ueber Dateibereiche.
    char *GetMD5Header();                    ///< MD5 ueber die Struktur fuer den Dateikopf.
    char *GetMD5Tables( bool lists );        ///< MD5 ueber die Listenbeschreibungen.

    uint32_t write_cstring( const std::string& s, std::FILE* fp );
    uint32_t read_cstring( std::string& s, std::FILE* fp );

    unsigned int get_crc_cstring( const std::string& s, unsigned int crc = 0xFFFFFFFF, bool closure = true );
public:
    SoftwarebuildVersion m_studioVersion;
    SoftwarebuildVersion m_libraryVersion;

public:
    std::array<unsigned char, EEP_SIZE_SU> m_eep_mem;
    StructFlashHeader& m_flash_header;
    unsigned char* m_eep;

    std::array<unsigned char, EEP_SIZE - EEP_SIZE_SU> m_file_header_mem;
    StructFileStudioHeader& m_file_header;

    std::array<unsigned char, sizeof(StructConfig)> m_config_mem;
    StructConfig &m_config;

    std::array<unsigned char, FLASH_SIZE_SU + FLASH_SIZE_SU2> m_flash_mem;
    unsigned char* m_flash;

    std::array<unsigned char, EXTENDED_SIZE_SU> m_extended_mem;
    StructExtendedFlash& m_extended_header;
    unsigned char* m_flashExtended;
    unsigned int m_flashSizeSU2;
    unsigned int m_flashMemOffset;

public:
    // Pfadangaben der Listen / Zusatzpfade
    std::array<std::string, 20> m_listFileName;
    std::array<std::string, 4> m_listEntranceFileName;
    std::array<std::string, 20> m_listEntrance2FileName;
    std::array<std::string, 20> m_strExtendedFilePath;
};

#endif
