#if !defined( _IOAICS307_H )
#define _IOAICS307_H

enum IoAICS307Grade { eCommercial, eIndustrial };

enum IoAICS307Power { eVDD5V, eVDD3V3 };

enum IoAICS307InputReference { eCrystal, eClock };

enum IoAICS307CLK2Configuration { eReferenceClock, eReferenceClockBy2, eOff, eClock1By2 };

class IoAICS307Register
{
public:
    // Constructor (including default constructor) + default register settings
    IoAICS307Register( double ReferenceClockHz = 0.0, IoAICS307Grade Grade = eCommercial,
                       IoAICS307Power Power = eVDD3V3, IoAICS307InputReference InputReference = eCrystal ) :
        m_ReferenceClockHz( 0.0 ),
        m_Grade( Grade ),
        m_Power( Power ),
        m_InputReference( InputReference ),
        m_iRegister( 0x230406 )  // power up register settings
    {
        SetReferenceClock( ReferenceClockHz );

//        SetDefaults( );
    }

    // Default settings (same as power up default values = 0x230406)
    void SetDefaults( )
    {  /*
        SetC( 0 );                // => crystal load capacitance = CrystalLoadCapacitance( )
        SetTTL( 1 );              // => duty cycle measured at VDD/2 (recommended for VDD = 3.3 volts)
        SetF( eReferenceClock );  // => CLK2 = REF
        SetS( 3 );                // => output divider = OD[ S ] = OD[ 3 ] = 4
        SetV( 8 );                // => CLK1 = REF * ( 2 * ( V + 8 ) ) / ( ( R + 2 ) * OD[ S ] )
        SetR( 6 );                //         = REF * ( 2 * 16 ) / ( 8 * 4 ) = REF
*/
        m_iRegister = 0x230406;
    }

    // Grade
    void SetGrade( IoAICS307Grade Grade ) { if ( Grade == eCommercial || Grade == eIndustrial ) m_Grade = Grade; }

    IoAICS307Grade Grade( ) const { return m_Grade; }

    int IsCommercialGrade( ) const { return m_Grade == eCommercial; }
    int IsIndustrialGrade( ) const { return m_Grade == eIndustrial; }

    // Power
    void SetPower( IoAICS307Power Power ) { if ( Power == eVDD5V || Power == eVDD3V3 ) m_Power = Power; }

    IoAICS307Power Power( ) const { return m_Power; }

    int Is5VPower ( ) const { return m_Power == eVDD5V;  }
    int Is3V3Power( ) const { return m_Power == eVDD3V3; }

    // Input reference
    void SetInputReference( IoAICS307InputReference InputReference )
    {
        if ( InputReference == eCrystal || InputReference == eClock ) m_InputReference = InputReference;
    }

    IoAICS307InputReference InputReference( ) const { return m_InputReference; }

    int IsCrytalInputReference( ) const { return m_InputReference == eCrystal; }
    int IsClockInputReference ( ) const { return m_InputReference == eClock;   }

    // Clock reference
    int SetReferenceClock( double ReferenceClockHz )
    {
        double ReferenceClockMHz = ReferenceClockHz / 1000000.0;

        if ( ( IsCrytalInputReference( ) && ( ReferenceClockMHz < 5.0 || ReferenceClockMHz > 27.0 ) ) ||
             ( IsClockInputReference ( ) && ( ReferenceClockMHz < 2.0 || ReferenceClockMHz > 50.0 ) ) ) return FALSE;

        m_ReferenceClockHz = ReferenceClockHz;

        return TRUE;
    }

    double ReferenceClockHz ( ) const { return m_ReferenceClockHz; }
    double ReferenceClockKHz( ) const { return m_ReferenceClockHz / 1000.0; }
    double ReferenceClockMHz( ) const { return m_ReferenceClockHz / 1000000.0; }

    int IsReferenceClockValid( ) const
    {
        return ( IsCrytalInputReference( ) && 5.0 <= ReferenceClockMHz( ) && ReferenceClockMHz( ) <= 27.0 ) ||
               ( IsClockInputReference ( ) && 2.0 <= ReferenceClockMHz( ) && ReferenceClockMHz( ) <= 50.0 );
    }

    // Frequency limits (internal + external)
    double MinimumVCOOutputMHz( ) const
    {
             if ( IsCommercialGrade( ) ) return 55.0;
        else if ( IsIndustrialGrade( ) ) return 60.0;
        else                             return  0.0;
    }

    double MaximumVCOOutputMHz( ) const
    {
             if ( IsCommercialGrade( ) ) return 400.0;
        else if ( IsIndustrialGrade( ) ) return 360.0;
        else                             return   0.0;
    }

    double MinimumCLK1MHz( ) const { return MinimumVCOOutputMHz( ) / 10.0; }
    double MaximumCLK1MHz( ) const { return MaximumVCOOutputMHz( ) /  2.0; }

    double MinimumCLK1KHz( ) const { return 1000.0 * MinimumVCOOutputMHz( ) / 10.0; }
    double MaximumCLK1KHz( ) const { return 1000.0 * MaximumVCOOutputMHz( ) /  2.0; }

    double MinimumCLK1Hz( ) const { return 1000000.0 * MinimumVCOOutputMHz( ) / 10.0; }
    double MaximumCLK1Hz( ) const { return 1000000.0 * MaximumVCOOutputMHz( ) /  2.0; }

    double MinimumCLK2MHz( ) const { return MinimumCLK1MHz( ) / 2.0; }
    double MaximumCLK2MHz( ) const { return MaximumCLK1MHz( ) / 2.0; }

    double MinimumCLK2KHz( ) const { return MinimumCLK1KHz( ) / 2.0; }
    double MaximumCLK2KHz( ) const { return MaximumCLK1KHz( ) / 2.0; }

    double MinimumCLK2Hz( ) const { return MinimumCLK1Hz( ) / 2.0; }
    double MaximumCLK2Hz( ) const { return MaximumCLK1Hz( ) / 2.0; }

    // Register variables
    int C  ( ) const { return (int) ( ( m_iRegister >> 22 ) & 0x003 ); }  // internal load capacitance (2 bits)
    int TTL( ) const { return (int) ( ( m_iRegister >> 21 ) & 0x001 ); }  // output duty cycle configuration (1 bit)
    int F  ( ) const { return (int) ( ( m_iRegister >> 19 ) & 0x003 ); }  // CLK2 output configuration (2 bits)
    int S  ( ) const { return (int) ( ( m_iRegister >> 16 ) & 0x007 ); }  // output divider index (3 bits)
    int V  ( ) const { return (int) ( ( m_iRegister >>  7 ) & 0x1ff ); }  // VCO divider (9 bits)
    int R  ( ) const { return (int) ( ( m_iRegister >>  0 ) & 0x07f ); }  // reference divider (7 bits)

    void SetC  ( int Value ) { m_iRegister &= ~( 0x003 << 22 ); m_iRegister |= ( Value & 0x003 ) << 22; }
    void SetTTL( int Value ) { m_iRegister &= ~( 0x001 << 21 ); m_iRegister |= ( Value & 0x001 ) << 21; }
    void SetF  ( int Value ) { m_iRegister &= ~( 0x003 << 19 ); m_iRegister |= ( Value & 0x003 ) << 19; }
    void SetS  ( int Value ) { m_iRegister &= ~( 0x007 << 16 ); m_iRegister |= ( Value & 0x007 ) << 16; }
    void SetV  ( int Value ) { m_iRegister &= ~( 0x1ff <<  7 ); m_iRegister |= ( Value & 0x1ff ) <<  7; }
    void SetR  ( int Value ) { m_iRegister &= ~( 0x07f <<  0 ); m_iRegister |= ( Value & 0x07f ) <<  0; }

    static const int OutputDividerLUT[ 8 ];

    int OutputDivider( ) const { return OutputDividerLUT[ S( ) ]; }

    double CrystalLoadCapacitance( ) const;

    // Output clock frequency
    int SetCLK1( double ClockHz );
    int SetCLK2( double ClockHz )
    {
        SetF( eClock1By2 );  // configure device for CLK2 = CLK1 / 2 => F = eClock1By2 = 3

        return SetCLK1( 2.0 * ClockHz );
    }

    int IsValid( ) const;

    double CLK1Hz ( ) const;
    double CLK1KHz( ) const { return CLK1Hz( ) / 1000.0; }
    double CLK1MHz( ) const { return CLK1Hz( ) / 1000000.0; }

    double CLK2Hz ( ) const;
    double CLK2KHz( ) const { return CLK2Hz( ) / 1000.0; }
    double CLK2MHz( ) const { return CLK2Hz( ) / 1000000.0; }

    IoAICS307CLK2Configuration CLK2Configuration( ) const { return (IoAICS307CLK2Configuration) F( ); }

    int CLK2On ( ) const { return CLK2Configuration( ) != eOff; }
    int CLK2Off( ) const { return CLK2Configuration( ) == eOff; }

    // Register bits - functions inlined for efficiency
    int Bit( int Index ) const
    {
        int iBit = 0;

        if ( 0 <= Index && Index < 24 )
        {
            unsigned int iMask = (unsigned int) 0x1 << Index;

            iBit = ( m_iRegister & iMask ) != 0;
        }

        return iBit;
    }

    void SetBit( int Index, int Bit )
    {
        if ( 0 <= Index && Index < 24 )
        {
            unsigned int iMask = (unsigned int) 0x1 << Index;

            if ( Bit )
            {
                m_iRegister |= iMask;  // set bit
            }
            else
            {
                m_iRegister &= ~iMask;  // clear bit
            }
        }
    }

    unsigned int Bits( ) const { return m_iRegister; }

    void SetBits( unsigned int Bits ) { m_iRegister = Bits & 0xffffff; }

    // Compare register contents only
    int IsEqual( const IoAICS307Register& Register ) const { return m_iRegister == Register.m_iRegister; }

    int operator==( const IoAICS307Register& Register ) const { return   IsEqual( Register ); }
    int operator!=( const IoAICS307Register& Register ) const { return ! IsEqual( Register ); }

private:

    // Chip configuration
    IoAICS307Grade          m_Grade;
    IoAICS307Power          m_Power;
    IoAICS307InputReference m_InputReference;

    double m_ReferenceClockHz;  // reference clock frequency

    unsigned int m_iRegister;  // register contents
};

#include "Device.h"

class IoAICS307 : public IoADevice
{
public:
    IoAICS307( int Index = 0 ) : IoADevice( "ICS307", Index ) { }
    IoAICS307( const IoAICS307Register& Register, int Index = 0 ) : IoADevice( "ICS307", Index ),
                                                                    m_Register( Register ) { }

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

    const IoAICS307Register& Register( ) { return m_Register; }
    operator const IoAICS307Register&( ) { return Register( ); }

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

    // Open + close
    int Open( int = 0 );  // have to override IoADevice::Open( )

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

    int Synchronize( const IoAICS307Register& Register );

    // Grade
    void SetGrade( IoAICS307Grade Grade ) { m_Register.SetGrade( Grade ); }

    IoAICS307Grade Grade( ) const { return m_Register.Grade( ); }

    int IsCommercialGrade( ) const { return m_Register.IsCommercialGrade( ); }
    int IsIndustrialGrade( ) const { return m_Register.IsIndustrialGrade( ); }

    // Power
    void SetPower( IoAICS307Power Power ) { m_Register.SetPower( Power ); }

    IoAICS307Power Power( ) const { m_Register.Power( ); }

    int Is5VPower ( ) const { return m_Register.Is5VPower ( ); }
    int Is3V3Power( ) const { return m_Register.Is3V3Power( ); }

    // Input reference
    void SetInputReference( IoAICS307InputReference InputReference ) { m_Register.SetInputReference( InputReference ); }

    IoAICS307InputReference InputReference( ) const { return m_Register.InputReference( ); }

    int IsCrytalInputReference( ) const { return m_Register.IsCrytalInputReference( ); }
    int IsClockInputReference ( ) const { return m_Register.IsClockInputReference ( ); }

    // Clock reference frequency
    int SetReferenceClock( double ReferenceClockHz ) { return m_Register.SetReferenceClock( ReferenceClockHz ); }

    int SetReferenceClock( double ReferenceClockHz, IoAICS307InputReference InputReference )
    {
        m_Register.SetInputReference( InputReference );

        return m_Register.SetReferenceClock( ReferenceClockHz );
    }

    double ReferenceClockHz ( ) const { return m_Register.ReferenceClockHz ( ); }
    double ReferenceClockKHz( ) const { return m_Register.ReferenceClockKHz( ); }
    double ReferenceClockMHz( ) const { return m_Register.ReferenceClockMHz( ); }

    int IsReferenceClockValid( ) const { return m_Register.IsReferenceClockValid( ); }

    // Output clock frequency
    double CLK1Hz ( ) const { return m_Register.CLK1Hz ( ); }
    double CLK1KHz( ) const { return m_Register.CLK1KHz( ); }
    double CLK1MHz( ) const { return m_Register.CLK1MHz( ); }

    double CLK2Hz ( ) const { return m_Register.CLK2Hz ( ); }
    double CLK2KHz( ) const { return m_Register.CLK2KHz( ); }
    double CLK2MHz( ) const { return m_Register.CLK2MHz( ); }

private:
    int Write( const IoAICS307Register& Register ) const;  // device is write-only

    IoAICS307Register m_Register;
};

#endif
