using System;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.SPOT.Hardware;

namespace HWModules
{
     //TODO P2 need to lock all the I2C operations
    public class I2CPortBase
    {
        private bool _isDiposed;
        public I2CDevice.Configuration Config;

        protected static I2CDevice G400I2C;
        private static int _instanceCount;

        private MotherBoard _mb;

        private static FlPorts _flPorts;

        public int SlotNumber { get; protected set; }

        public I2CPortBase(I2CDevice.Configuration config, int slotnumber)
        {
            if (config == null)
                throw new ArgumentNullException("Argument Null Exception in I2CPortBase Constructor");

            this.Config = config;

            if (G400I2C == null) {
                G400I2C = new I2CDevice(config);
            }

            SlotNumber = slotnumber;

            _mb = MotherBoard.Instance;

            _instanceCount++;
            _flPorts |= (FlPorts) (0x01 << slotnumber);
            EnablePort();
            Thread.Sleep(1);
        }

        public I2CDevice.I2CReadTransaction CreateReadTransaction(byte[] buffer)
        {
            if (this._isDiposed)
                throw new ObjectDisposedException("Object Disposed Exception in I2CPortBase CreateReadTransaction");
            return I2CDevice.CreateReadTransaction(buffer);
        }

        public I2CDevice.I2CWriteTransaction CreateWriteTransaction(byte[] buffer)
        {
            if (this._isDiposed)
                throw new ObjectDisposedException("Object Disposed Exception in I2CPortBase CreateWriteTransaction");
            return I2CDevice.CreateWriteTransaction(buffer);
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void Dispose(bool fDisposing)
        {
            if (!this._isDiposed) {
                try {
                    if (_instanceCount <= 0) return;
                    _instanceCount--;
                    if (_instanceCount != 0) return;
                    if (G400I2C == null) return;
                    G400I2C.Dispose();
                    G400I2C = null;
                }
                finally {
                    this._isDiposed = true;
                }
            }
        }

        private static object _executelock = new object();
        [MethodImpl(MethodImplOptions.Synchronized)]
        public int Execute(I2CDevice.I2CTransaction[] xActions, int timeout)
        {
            try
            {
                if (this._isDiposed)
                    throw new ObjectDisposedException("Object Disposed Exception in I2CPortBase Execute");

                int retcode = -1;
                lock (_executelock) {
                    G400I2C.Config = this.Config;
                    //IsolatePort();
                    retcode = G400I2C.Execute(xActions, timeout);
                    //UnisolatePort();
                }
                return retcode;
            }
            catch
            {
                EngrLogger.writeToColumns("[ERROR] in I2CPortBase.Execute");
                return (-1);
            }
        }

        private void EnablePort()
        {
            _mb.EnableI2CPort(SlotNumber);
        }

        private void DisablePort()
        {
            _mb.DisableI2CPort(SlotNumber);
        }

        /// <summary>
        /// Isolates the i2c communication to slots 
        /// by disable all other buffers that have 
        /// been enabled before.
        /// </summary>
        public void IsolatePort()
        {
           ChangeOtherPorts(false);
        }

        /// <summary>
        /// Re-enables all of the other ports that 
        /// have been available in the past. 
        /// </summary>
        public void UnisolatePort()
        {
            ChangeOtherPorts(true);
        }

        /// <summary>
        /// Changes the state of the i2c port enabling buffer to
        /// disable or re-enable ports to isolate the one used here. 
        /// </summary>
        /// <param name="enable"></param>
        private void ChangeOtherPorts(bool enable)
        {

            //if (Config.Address != (0xCE >> 1) &&
            //    Config.Address != (0xD2 >> 1) &&
            //    Config.Address != (0xD8 >> 1))
            //{
            //    return;
            //}

            FlPorts mask;
            for (int i = 1; i <= 6; i++)
            {
                mask = (_flPorts) & ((FlPorts)(0x01 << i));
                
                if (mask == 0 || SlotNumber == i) 
                    continue;

                if (enable)
                    _mb.EnableI2CPort(i);
                else 
                    _mb.DisableI2CPort(i);
                Thread.Sleep(20); //allows hardware to catch up

            }
        }

        ~I2CPortBase()
        {
            this.Dispose(false);
        }

        [Flags]
        private enum FlPorts
        {
            En0 = 0x01, //Port 0
            En1 = 0x02, //Port 1
            En2 = 0x04, //Port 2
            En3 = 0x08, //Port 3
            En4 = 0x10, //Port 4
            En5 = 0x20, //Port 5
            En6 = 0x40, //Port 6
        }
    }
}
