#if !defined( _IOAAD539X_H )
#define _IOAAD539X_H

#include "Device.h"

class IoAAD539x : public IoADevice
{
protected:
    IoAAD539x( const char *Name, int Index, int nChannels, int nBits ) : IoADevice( Name, Index ),
        m_nChannels( nChannels ), m_nBits( nBits ), m_ReferenceVoltage( 0.0 )
    {
        int Gain, Offset;

        // Unit gain and zero offset coefficients
        if ( nBits == 12 )
        {
            Gain   = 0xffe;
            Offset = 0x800;
        }
        else if ( nBits == 14 )
        {
            Gain   = 0x3ffe;
            Offset = 0x2000;
        }

        for ( int Register = 0; Register < 2; Register++ )
        {
            for ( int Channel = 0; Channel < nChannels; Channel++ )
            {
                m_Gain  [ Register ][ Channel ] = Gain;
                m_Offset[ Register ][ Channel ] = Offset;
                m_Level [ Register ][ Channel ] = 0;
            }
        }

        m_CRBits = 0x000;
    }

public:
//    ~IoAAD539x( ) { Close( ); }  // not required if IoAAD539x::Close( ) is not defined

    void Detach( ) { Close( ); IoADevice::Detach( ); }  // have to override IoADevice::Detach( )

    // Open + close
    int Open( int Verify = FALSE );

//    void Close( int = 0 ) { IoADevice::Close( ) }  // no need to override IoADevice::Close( )

    int ChannelCount( ) const { return m_nChannels; }

    int BitCount( ) const { return m_nBits; }

    // Voltage reference
    void SetReferenceVoltage( double ReferenceVoltage )
    {
        if ( ReferenceVoltage > 0.0 ) m_ReferenceVoltage = ReferenceVoltage;
    }

    double ReferenceVoltage( ) const { return m_ReferenceVoltage; }

    int IsReferenceVoltageDefined( ) const { return m_ReferenceVoltage > 0.0; }

    int VoltageLevels( ) const { return 1 << BitCount( ); }

    double VoltageIncrement( ) const { return 2.0 * ReferenceVoltage( ) / (double) VoltageLevels( ); }

    // Set DAC output voltage
    int SetVoltage( int Channel, double Voltage )
    {
        if ( ! IsReferenceVoltageDefined( ) ) return FALSE;

        if ( Voltage < 0.0 || Voltage > 2.0 * ReferenceVoltage( ) - VoltageIncrement( ) ) return FALSE;

        int Level = (int) ( Voltage / VoltageIncrement( ) + 0.5 );

        if ( ! SetLevel( Channel, Level ) ) return FALSE;

        return TRUE;
    }

    double Voltage( int Channel ) const
    {
        return IsReferenceVoltageDefined( ) ? (double) Level( Channel ) * VoltageIncrement( ) : (double) 0.0;
    }

    double MinimumVoltage( ) const { return 0.0; }
    double MaximumVoltage( ) const { return (double) ( VoltageLevels( ) - 1 ) * VoltageIncrement( ); }

    // Set DAC output level
    int SetLevel( int Channel, int Level )
    {
        if ( ! IsOpen( ) ) return FALSE;

        if ( Channel < 0 || Channel >= ChannelCount( ) ) return FALSE;

        if ( Level < 0 || Level >= VoltageLevels( ) ) return FALSE;

        if ( Level != m_Level[ 0 ][ Channel ] )
        {
            if ( ! WriteLevel( 0, Channel, Level ) ) return FALSE;

            m_Level[ 0 ][ Channel ] = Level;

//            ReadLevel( 0, Channel, Level );  // diagnostic check
        }

        return TRUE;
    }

    int Level( int Channel ) const
    {
        return 0 <= Channel && Channel < ChannelCount( ) ? m_Level[ 0 ][ Channel ] : 0;
    }

    int MinimumLevel( ) const { return 0; }
    int MaximumLevel( ) const { return VoltageLevels( ) - 1; }

    int SetLevelCommandBits( int Channel, int Level )
    {
        int CommandBits24 = 0x000000;

        if ( IsOpen( ) )
        {
            if ( 0 <= Channel && Channel < ChannelCount( ) )
            {
                if ( 0 <= Level && Level < VoltageLevels( ) )
                {
                    int Bits[ 24 ];

                    Encode( Bits, 0, 0, Channel, 3, Level << ( 14 - BitCount( ) ) );

                    for ( int i = 0; i < 24; i++ ) CommandBits24 |= Bits[ i ] << ( 23 - i );
                }
            }
        }

        return CommandBits24;
    }

    // Activate DAC settings
//    int Load( ) const { return IsOpen( ) && Write( 0, 0, 0, 0, 0, TRUE ); }  // NOP with LOAD bit set

    // Control register
    int SetControlRegister( int CRBits );

    // Control register bits
    enum 
    {
        eToggleModeEnable0_7 = 0,
        eToggleModeEnable8_15,
        eThermalMonitorEnable = 6,
        eChannelMonitorEnable,
        eVoltageReferenceSelect,
        eCurrentBoostEnable,
        eInternalReferenceVoltageSelect,
        ePowerDownImpedanceSelect,
        eControlRegisterBits
    };

    int SetControlRegisterBit( int BitIndex, int BitValue );

    // Toggle mode
    int DisableToggleMode0_7( ) { return SetControlRegisterBit( eToggleModeEnable0_7, 0 ); }
    int EnableToggleMode0_7 ( ) { return SetControlRegisterBit( eToggleModeEnable0_7, 1 ); }

    int IsToggleMode0_7Enabled ( ) const { return ( m_CRBits & ( 0x1 << eToggleModeEnable0_7 ) ) != 0; }
    int IsToggleMode0_7Disabled( ) const { return ! IsToggleMode0_7Enabled( ); }

    int DisableToggleMode8_15( ) { return SetControlRegisterBit( eToggleModeEnable8_15, 0 ); }
    int EnableToggleMode8_15 ( ) { return SetControlRegisterBit( eToggleModeEnable8_15, 1 ); }

    int IsToggleMode8_15Enabled ( ) const { return ( m_CRBits & ( 0x1 << eToggleModeEnable8_15 ) ) != 0; }
    int IsToggleMode8_15Disabled( ) const { return ! IsToggleMode8_15Enabled( ); }

    // Thermal monitor
    int DisableThermalMonitor( ) { return SetControlRegisterBit( eThermalMonitorEnable, 0 ); }
    int EnableThermalMonitor ( ) { return SetControlRegisterBit( eThermalMonitorEnable, 1 ); }

    int IsThermalMonitorEnabled ( ) const { return ( m_CRBits & ( 0x1 << eThermalMonitorEnable ) ) != 0; }
    int IsThermalMonitorDisabled( ) const { return ! IsThermalMonitorEnabled( ); }

    // Channel monitor
    int DisableChannelMonitor( ) { return SetControlRegisterBit( eChannelMonitorEnable, 0 ); }
    int EnableChannelMonitor ( ) { return SetControlRegisterBit( eChannelMonitorEnable, 1 ); }

    int IsChannelMonitorEnabled ( ) const { return ( m_CRBits & ( 0x1 << eChannelMonitorEnable ) ) != 0; }
    int IsChannelMonitorDisabled( ) const { return ! IsChannelMonitorEnabled( ); }

    // Voltage reference
    int SelectExternalReference( ) { return SetControlRegisterBit( eVoltageReferenceSelect, 0 ); }
    int SelectInternalReference( ) { return SetControlRegisterBit( eVoltageReferenceSelect, 1 ); }

    int IsInternalReferenceSelected( ) const { return ( m_CRBits & ( 0x1 << eVoltageReferenceSelect ) ) != 0; }
    int IsExternalReferenceSelected( ) const { return ! IsInternalReferenceSelected( ); }

    // Current boost
    int DisableCurrentBoost( ) { return SetControlRegisterBit( eCurrentBoostEnable, 0 ); }
    int EnableCurrentBoost ( ) { return SetControlRegisterBit( eCurrentBoostEnable, 1 ); }

    int IsCurrentBoostEnabled ( ) const { return ( m_CRBits & ( 0x1 << eCurrentBoostEnable ) ) != 0; }
    int IsCurrentBoostDisabled( ) const { return ! IsCurrentBoostEnabled( ); }

    // Internal reference voltage
    int Select1V25InternalReference( ) { return SetControlRegisterBit( eInternalReferenceVoltageSelect, 0 ); }
    int Select2V5InternalReference ( ) { return SetControlRegisterBit( eInternalReferenceVoltageSelect, 1 ); }

    int Is2V5InternalReferenceSelected ( ) const { return ( m_CRBits & ( 0x1 << eInternalReferenceVoltageSelect ) ) != 0; }
    int Is1V25InternalReferenceSelected( ) const { return ! Is2V5InternalReferenceSelected( ); }

    // Power-down impedance
    int SelectPowerDown100KImpedance( ) { return SetControlRegisterBit( ePowerDownImpedanceSelect, 0 ); }
    int SelectPowerDownHighImpedance( ) { return SetControlRegisterBit( ePowerDownImpedanceSelect, 1 ); }

    int IsPowerDownHighImpedanceSelected( ) const { return ( m_CRBits & ( 0x1 << ePowerDownImpedanceSelect ) ) != 0; }
    int IsPowerDown100KImpedanceSelected( ) const { return ! IsPowerDownHighImpedanceSelected( ); }

    // Special device functions
    int NOP      ( ) const { return IsOpen( ) && WriteSFR(  0, 0 ); }
    int CLR      ( ) const { return IsOpen( ) && WriteSFR(  2, 0 ); }
    int PowerDown( ) const { return IsOpen( ) && WriteSFR(  8, 0 ); }
    int PowerUp  ( ) const { return IsOpen( ) && WriteSFR(  9, 0 ); }
    int Reset    ( ) const { return IsOpen( ) && WriteSFR( 15, 0 ); }

    // Set other DAC properties
    int SetCLRCode( int CLRCode ) const { return IsOpen( ) && WriteSFR( 1, CLRCode << ( 14 - BitCount( ) ) ); }

    int SetMonitorChannel( int Channel ) const { return IsOpen( ) && WriteSFR( 10, Channel << 8 ); }

private:
    // Control register access
    int ReadControlRegister( int& CRBits ) const
    {
        if ( ! ReadSFR( 12, CRBits ) ) return FALSE;

        CRBits >>= 2;

        return TRUE;
    }

    int WriteControlRegister( int CRBits ) const
    {
        return WriteSFR( 12, CRBits << 2 );
    }

    // Register access
    int ReadSFR( int Function, int &Argument ) const
    {
        int BNA = 0, RNW = 1, REG = 0;

        if ( ! Write( BNA, RNW, Function, REG, 0 ) ) return FALSE;

        if ( ! Read( Argument ) ) return FALSE;
        
        return TRUE;
    }

    int ReadGain( int Register, int Channel, int &Gain ) const
    {
        int BNA = Register != 0, RNW = 1, REG = 1;

        if ( ! Write( BNA, RNW, Channel, REG, 0 ) ) return FALSE;

        if ( ! Read( Gain ) ) return FALSE;

        Gain >>= 14 - BitCount( );
        
        return TRUE;
    }

    int ReadOffset( int Register, int Channel, int &Offset ) const
    {
        int BNA = Register != 0, RNW = 1, REG = 2;

        if ( ! Write( BNA, RNW, Channel, REG, 0 ) ) return FALSE;

        if ( ! Read( Offset ) ) return FALSE;

        Offset >>= 14 - BitCount( );
        
        return TRUE;
    }

    int ReadLevel( int Register, int Channel, int &Level ) const
    {
        int BNA = Register != 0, RNW = 1, REG = 3;

        if ( ! Write( BNA, RNW, Channel, REG, 0 ) ) return FALSE;

        if ( ! Read( Level ) ) return FALSE;

        Level >>= 14 - BitCount( );
        
        return TRUE;
    }

    int WriteSFR( int Function, int Argument ) const
    {
        int BNA = 0, RNW = 0, REG = 0;

        return Write( BNA, RNW, Function, REG, Argument );
    }

    int WriteGain( int Register, int Channel, int Gain ) const
    {
        int BNA = Register != 0, RNW = 0, REG = 1;

        return Write( BNA, RNW, Channel, REG, Gain << ( 14 - BitCount( ) ) );
    }

    int WriteOffset( int Register, int Channel, int Offset ) const
    {
        int BNA = Register != 0, RNW = 0, REG = 2;

        return Write( BNA, RNW, Channel, REG, Offset << ( 14 - BitCount( ) ) );
    }

    int WriteLevel( int Register, int Channel, int Level ) const
    {
        int BNA = Register != 0, RNW = 0, REG = 3;

        return Write( BNA, RNW, Channel, REG, Level << ( 14 - BitCount( ) ) );
    }

    // Command format
    void Encode( int Bits[ 24 ], int  BNA, int  RNW, int  A, int  REG, int  DB ) const;
    void Decode( int Bits[ 24 ], int& BNA, int& RNW, int& A, int& REG, int& DB ) const;

    // Serial data access
    int Read( int &DB ) const;

    int Write( int BNA, int RNW, int A, int REG, int DB, int Load = FALSE ) const;

    double m_ReferenceVoltage;

    int m_nChannels;  // number of DAC channels
    int m_nBits;      // DAC resolution

    int m_CRBits;  // control register contents

    int m_Gain  [ 2 ][ 16 ];
    int m_Offset[ 2 ][ 16 ];  // DAC register contents
    int m_Level [ 2 ][ 16 ];
};

class IoAAD5390 : public IoAAD539x
{
public:
    IoAAD5390( int Index = 0 ) : IoAAD539x( "AD5390", Index, 16, 14 ) { }
};

class IoAAD5391 : public IoAAD539x
{
public:
    IoAAD5391( int Index = 0 ) : IoAAD539x( "AD5391", Index, 16, 12 ) { }
};

class IoAAD5392 : public IoAAD539x
{
public:
    IoAAD5392( int Index = 0 ) : IoAAD539x( "AD5392", Index, 8, 14 ) { }
};

#endif
