using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.Text;

namespace HWModules
{
    public class EnergyMonitorLTC2946 : I2CPortBase
    {
        /// <summary>
        ///     Input wiring pin for voltage source to measure.
        /// </summary>
        public enum VoltageSource
        {
            SensePlus,
            Adin
        }

        private const double minVoltage = 26.5;

        private readonly byte[] LTC2946_ADIN_MSB_REG = {0x28};
  
        private readonly byte[] LTC2946_CONTROLA_REGISTER = { 0x00 };
        private readonly byte[] LTC2946_CONTROLB_REGISTER = { 0x01 };

        private readonly byte[] LTC2946_POWER_MSB2 = { 0x05 };
        private readonly byte[] LTC2946_POWER_MSB1 = { 0x06 };
        private readonly byte[] LTC2946_POWER_LSB = { 0x07 };
        
        private readonly byte[] LTC2946_DELTA_SENSE_MSB = { 0x14 };
        private readonly byte[] LTC2946_VIN_MSB_REG = { 0x1E };

        private readonly byte[] LTC2946_TIMECOUNTER_MSB3 = { 0x34 };
        private readonly byte[] LTC2946_TIMECOUNTER_MSB2 = { 0x35 };
        private readonly byte[] LTC2946_TIMECOUNTER_MSB1 = { 0x36 };
        private readonly byte[] LTC2946_TIMECOUNTER_LSB = { 0x37 };

        private readonly byte[] LTC2946_CHARGE_MSB3 = { 0x38 };
        private readonly byte[] LTC2946_CHARGE_MSB2 = { 0x39 };
        private readonly byte[] LTC2946_CHARGE_MSB1 = { 0x3A };
        private readonly byte[] LTC2946_CHARGE_LSB = { 0x3B };

        private readonly byte[] LTC2946_ENERGY_MSB3 = { 0x3C };
        private readonly byte[] LTC2946_ENERGY_MSB2 = { 0x3D };
        private readonly byte[] LTC2946_ENERGY_MSB1 = { 0x3E };
        private readonly byte[] LTC2946_ENERGY_LSB = { 0x3F };
        
        
        private readonly byte[] registerByte = new byte[1];
        private readonly byte[] registerBytes2 = new byte[2];
        private readonly byte[] registerBytes3 = new byte[3];
        private readonly byte[] registerBytes4 = new byte[4];

        private EM_VandI emVI;
        private I2CDevice.I2CReadTransaction _rt;
        private I2CDevice.I2CWriteTransaction _wt;

        private int _xActionReturn;

        private I2CDevice.I2CTransaction[] _xActions2946 = new I2CDevice.I2CTransaction[2];

        public EnergyMonitorLTC2946(I2CDevice.Configuration config, int slotnumber)
            : base(config, slotnumber)
        {
            Source = VoltageSource.Adin;
            milliAmpsPerLSB = 0.5;
            voltsPerLSB = 0.009828;

            // let's set up the chip so that we are really sampling on adin
            if (Source == VoltageSource.Adin)
                SetCtlARegister(RegCTLA.CHCFG_ALTVI, RegCTLA.VOLT_ADIN, RegCTLA.OFFSETCAL_EVERY, RegCTLA.ADINCFG_GND);
        }

        public EnergyMonitorLTC2946(I2CDevice.Configuration config, VoltageSource source, double voltspercount,
            double milliampspercount, int slotnumber = 0)
            : base(config, slotnumber)
        {
            Source = source;

            voltsPerLSB = voltspercount;
            milliAmpsPerLSB = milliampspercount;
            uWattsPerLSB = milliAmpsPerLSB * voltsPerLSB;
            milliSecondsPerLSB = 16.39543;
            uCoulombsPerLSB = milliAmpsPerLSB * milliSecondsPerLSB;
            milliJoulesPerLSB = milliAmpsPerLSB * voltsPerLSB * milliSecondsPerLSB;

            // let's set up the chip so that we are really sampling on adin
            if (Source == VoltageSource.Adin)
                SetCtlARegister(RegCTLA.CHCFG_ALTVI, RegCTLA.VOLT_ADIN, RegCTLA.OFFSETCAL_EVERY, RegCTLA.ADINCFG_GND);
        }

        //Properties
        public VoltageSource Source { get; private set; }
        public double milliAmpsPerLSB { get; private set; }
        public double voltsPerLSB { get; private set; }
        public double uWattsPerLSB { get; private set; }
        public double milliSecondsPerLSB { get; private set; }
        public double uCoulombsPerLSB { get; private set; }
        public double milliJoulesPerLSB { get; private set; }

        public double getMilliAmps()
        {
            double milliAmps = double.NaN;

            // create write buffer to read current
            _wt = CreateWriteTransaction(LTC2946_DELTA_SENSE_MSB);
            // create read buffer to read the returned data
            _rt = CreateReadTransaction(registerBytes2);

            // execute transaction
            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };
            _xActionReturn = Execute(_xActions2946, 500);

            if (_xActionReturn == 0)
            {
                EngrLogger.Comment("***ERROR getMilliAmps() executing 2946 Transaction = " + _xActionReturn.ToString());
            }
            else
            {
                milliAmps = milliAmpsPerLSB * ((registerBytes2[0] << 4) | (registerBytes2[1] >> 4));
            }

            return milliAmps;
        }

        public double getVolts()
        {
            double volts = double.NaN;

            // create write buffer to read voltage
            switch (Source)
            {
                case VoltageSource.SensePlus:
                    _wt = CreateWriteTransaction(LTC2946_VIN_MSB_REG);
                    break;
                case VoltageSource.Adin:
                    _wt = CreateWriteTransaction(LTC2946_ADIN_MSB_REG);
                    break;
                default:
                    EngrLogger.Comment("***ERROR getVolts() invalid VoltageSource");
                    return volts;
            }

            // create read buffer to read the returned data
            _rt = CreateReadTransaction(registerBytes2);

            // execute transaction
            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };
            _xActionReturn = Execute(_xActions2946, 500);

            if (_xActionReturn == 0)
            {
                EngrLogger.Comment("***ERROR getVolts() executing 2946 Transaction = " + _xActionReturn.ToString());
            }
            else
            {
                volts = voltsPerLSB * ((registerBytes2[0] << 4) | (registerBytes2[1] >> 4));
            }

            return volts;
        }

        public double getUWatts()
        {
            double uWatts = double.NaN;

            // create write buffer to read current
            _wt = CreateWriteTransaction(LTC2946_POWER_MSB2);
            // create read buffer to read the returned data
            _rt = CreateReadTransaction(registerBytes3);

            // execute transaction
            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };
            _xActionReturn = Execute(_xActions2946, 500);

            if (_xActionReturn == 0)
            {
                EngrLogger.Comment("***ERROR getUWatts() executing 2946 Transaction = " + _xActionReturn.ToString());
            }
            else
            {
                uWatts = uWattsPerLSB * ((double)(registerBytes4[0] * 65536) + (double)(registerBytes4[1] * 256) + (double)(registerBytes4[2]));
            }

            return uWatts;
        }

        public double getMilliSeconds()
        {
            double milliSeconds = double.NaN;

            // create write buffer to read current
            _wt = CreateWriteTransaction(LTC2946_TIMECOUNTER_MSB3);
            // create read buffer to read the returned data
            _rt = CreateReadTransaction(registerBytes4);

            // execute transaction
            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };
            _xActionReturn = Execute(_xActions2946, 500);

            if (_xActionReturn == 0)
            {
                EngrLogger.Comment("***ERROR getMilliSeconds() executing 2946 Transaction = " + _xActionReturn.ToString());
            }
            else
            {
                milliSeconds = milliSecondsPerLSB * ((double)(registerBytes4[0] * 16777216) + (double)(registerBytes4[1] * 65536) +
                    (double)(registerBytes4[2] * 256) + (double)(registerBytes4[3]));
            }

            return milliSeconds;
        }

        public double getUCoulombs()
        {
            double uCoulombs = double.NaN;

            // create write buffer to read current
            _wt = CreateWriteTransaction(LTC2946_CHARGE_MSB3);
            // create read buffer to read the returned data
            _rt = CreateReadTransaction(registerBytes4);

            // execute transaction
            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };
            _xActionReturn = Execute(_xActions2946, 500);

            if (_xActionReturn == 0)
            {
                EngrLogger.Comment("***ERROR getUCoulombs() executing 2946 Transaction = " + _xActionReturn.ToString());
            }
            else
            {
                uCoulombs = uCoulombsPerLSB * ((double)(registerBytes4[0] * 16777216) + (double)(registerBytes4[1] * 65536) +
                    (double)(registerBytes4[2] * 256) + (double)(registerBytes4[3]));
            }

            return uCoulombs;
        }

        public double getMilliJoules()
        {
            double milliJoules = double.NaN;

            // create write buffer to read current
            _wt = CreateWriteTransaction(LTC2946_ENERGY_MSB3);
            // create read buffer to read the returned data
            _rt = CreateReadTransaction(registerBytes4);

            // execute transaction
            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };
            _xActionReturn = Execute(_xActions2946, 500);

            if (_xActionReturn == 0)
            {
                EngrLogger.Comment("***ERROR getMilliJoules() executing 2946 Transaction = " + _xActionReturn.ToString());
            }
            else
            {
                milliJoules = milliJoulesPerLSB * ((double)(registerBytes4[0] * 16777216) + (double)(registerBytes4[1] * 65536) +
                    (double)(registerBytes4[2] * 256) + (double)(registerBytes4[3]));
            }

            return milliJoules;
        }


        private emIVWTCE ivwtce;

        public emIVWTCE getIVWTCE()
        {
            ivwtce.milliAmps = double.NaN;
            ivwtce.volts = double.NaN;
            ivwtce.uWatts = double.NaN;
            ivwtce.milliSeconds = double.NaN;
            ivwtce.uCoulombs = double.NaN;
            ivwtce.milliJoules = double.NaN;

            ivwtce.milliAmps = getMilliAmps();
            ivwtce.volts = getVolts();
            ivwtce.uWatts = getUWatts();
            ivwtce.milliSeconds = getMilliSeconds();
            ivwtce.uCoulombs = getUCoulombs();
            ivwtce.milliJoules = getMilliJoules();

            return ivwtce;
        }

        public EM_VandI getVandI()
        {
            emVI.amps = double.NaN;
            emVI.volts = double.NaN;
            // create write buffer to read voltage
            switch (Source)
            {
                case VoltageSource.SensePlus:
                    _wt = CreateWriteTransaction(LTC2946_VIN_MSB_REG);
                    break;
                case VoltageSource.Adin:
                    _wt = CreateWriteTransaction(LTC2946_ADIN_MSB_REG);
                    break;
                default:
                    return emVI;
            }

            // create read buffer to read the returned data
            _rt = CreateReadTransaction(registerBytes2);

            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };
            _xActionReturn = Execute(_xActions2946, 500);

            if (_xActionReturn == 0)
            {
                //EngrLogger.writeToColumns("[ERROR] executing 2946 Transaction = " + xActionReturn.ToString());
            }
            else
            {
                emVI.volts = voltsPerLSB * ((registerBytes2[0] << 4) | (registerBytes2[1] >> 4));
            }

            // create write buffer to read current
            _xActions2946[0] = CreateWriteTransaction(LTC2946_DELTA_SENSE_MSB);
            _xActionReturn = Execute(_xActions2946, 500);

            if (_xActionReturn == 0)
            {
                //EngrLogger.writeToColumns("[ERROR] executing 2946 Transaction = " + xActionReturn.ToString());
            }
            else
            {
                emVI.amps = milliAmpsPerLSB * ((registerBytes2[0] << 4) | (registerBytes2[1] >> 4));
            }

            return emVI;
        }

        private EM_VandI vi;
        private EM_VICE vice;

        private StringBuilder sbTemp = new StringBuilder(64);

        public EM_VICE getVICE()
        {
            vice.volts = double.NaN;
            vice.milliAmps = double.NaN;
            vice.uCoulombs = double.NaN;
            vice.milliJoules = double.NaN;

            vi = getVandI();
            vice.volts = vi.volts;
            vice.milliAmps = vi.amps;

            // create write buffer to read 4 bytes of charge accumulator
            _wt = CreateWriteTransaction(LTC2946_CHARGE_MSB3);

            // create read buffer to read the returned data
            _rt = CreateReadTransaction(registerBytes4);
            registerBytes4[0] = 0;
            registerBytes4[1] = 0;
            registerBytes4[2] = 0;
            registerBytes4[3] = 0;

            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };
            _xActionReturn = Execute(_xActions2946, 500);

            if (_xActionReturn == 0)
            {
                //EngrLogger.writeToColumns("Error executing 2946 Transaction = " + xActionReturn.ToString());
            }
            else
            {
                vice.uCoulombs = (double)(registerBytes4[0] * 16777216) + (double)(registerBytes4[1] * 65536)
                                + (double)(registerBytes4[2] * 256) + (double)(registerBytes4[3]);

                //For engineering units: coulombs = counts * mA/count *  factor for reading upper 32 bits of 36 bit register * seconds per reading * 1/1000 amps per milliamp
                vice.uCoulombs = vice.uCoulombs * milliAmpsPerLSB * 16 * 0.01639542 / 1000.0;

                sbTemp.Clear();
                sbTemp.Append("charge bytes 0-3:\t");
                sbTemp.Append(registerBytes4[0]);
                sbTemp.Append("\t");
                sbTemp.Append(registerBytes4[1]);
                sbTemp.Append("\t");
                sbTemp.Append(registerBytes4[2]);
                sbTemp.Append("\t");
                sbTemp.Append(registerBytes4[3]);
                //Debug.Print(sbTemp.ToString());
            }

            // create write buffer to read 4 bytes of energy accumulator
            _wt = CreateWriteTransaction(LTC2946_ENERGY_MSB3);

            // create read buffer to read the returned data
            _rt = CreateReadTransaction(registerBytes4);
            registerBytes4[0] = 0;
            registerBytes4[1] = 0;
            registerBytes4[2] = 0;
            registerBytes4[3] = 0;

            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };
            _xActionReturn = Execute(_xActions2946, 500);

            if (_xActionReturn == 0)
            {
                //EngrLogger.writeToColumns("Error executing 2946 Transaction = " + xActionReturn.ToString());
            }
            else
            {
                vice.milliJoules = (double)(registerBytes4[0] * 16777216) + (double)(registerBytes4[1] * 65536)
                                + (double)(registerBytes4[2] * 256) +  (double)registerBytes4[3];

                //For engineering units: joules = counts * mA/count * V/count * factor for reading upper 32 bits of 48 bit register * seconds per reading * 1/1000 amps per milliamp
                vice.milliJoules = vice.milliJoules * milliAmpsPerLSB * voltsPerLSB * 65536 * 0.01639542 / 1000.0;

                sbTemp.Clear();
                sbTemp.Append("joule bytes 0-3:\t");
                sbTemp.Append(registerBytes4[0]);
                sbTemp.Append("\t");
                sbTemp.Append(registerBytes4[1]);
                sbTemp.Append("\t");
                sbTemp.Append(registerBytes4[2]);
                sbTemp.Append("\t");
                sbTemp.Append(registerBytes4[3]);
                //Debug.Print(sbTemp.ToString());
            }

            return vice;
        }

        public bool ResetAccumulators()
        {
            registerBytes2[0] = LTC2946_CONTROLB_REGISTER[0];

            byte resetAccumulator = 2;
            byte disableAutoReset = 0;

            registerBytes2[1] = resetAccumulator;
            _wt = CreateWriteTransaction(registerBytes2);
            _rt = CreateReadTransaction(registerByte);
            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };

            _xActionReturn = Execute(_xActions2946, 500);

            if (_xActionReturn == 0)
            {
                //Debug.Print("Error: Failed to Set CtlA Register");
            }
            else
            {
                if (registerByte[0] != resetAccumulator)
                    return false;
                //Debug.Print("GOT Control Register");
            }

            registerBytes2[1] = disableAutoReset;
            _wt = CreateWriteTransaction(registerBytes2);
            _rt = CreateReadTransaction(registerByte);
            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };

            _xActionReturn = Execute(_xActions2946, 500);

            if (_xActionReturn == 0)
            {
                //Debug.Print("Error: Failed to Set CtlA Register");
            }
            else
            {
                if (registerByte[0] != disableAutoReset)
                    return false;
                //Debug.Print("GOT Control Register");
            }

            return true;
        }

        public byte ReadCtlARegister()
        {
            _wt = CreateWriteTransaction(LTC2946_CONTROLA_REGISTER);
            _rt = CreateReadTransaction(registerByte);

            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };
            _xActionReturn = Execute(_xActions2946, 500);

            return _xActionReturn == 0 ? byte.MaxValue : registerByte[0];
        }
        
        public byte ReadCtlBRegister()
        {
            _wt = CreateWriteTransaction(LTC2946_CONTROLB_REGISTER);
            _rt = CreateReadTransaction(registerByte);

            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };
            _xActionReturn = Execute(_xActions2946, 500);

            return _xActionReturn == 0 ? byte.MaxValue : registerByte[0];
        }

        private bool SetCtlARegister(RegCTLA chcfgCtla, RegCTLA voltCtla, RegCTLA offsetCtla, RegCTLA adinCtla)
        {
            var ctlRegister = (byte)(chcfgCtla | voltCtla | offsetCtla | adinCtla);
            registerBytes2[0] = LTC2946_CONTROLA_REGISTER[0];
            registerBytes2[1] = ctlRegister;

            _wt = CreateWriteTransaction(registerBytes2);
            _rt = CreateReadTransaction(registerByte);
            _xActions2946 = new I2CDevice.I2CTransaction[] { _wt, _rt };

            _xActionReturn = Execute(_xActions2946, 500);

            if (_xActionReturn == 0)
            {
                //Debug.Print("[ERROR] Failed to Set CtlA Register");
            }
            else
            {
                if (registerByte[0] == ctlRegister)
                    return true;
                //Debug.Print("GOT Control Register");
            }

            return false;
        }
    
 

        [Flags]
        private enum RegCTLA : byte
        {
            CHCFG_ALTVI = 0x00,
            CHCFG_VI16DC = 0x01,
            CHCFG_VI128DC = 0x02,
            CHCFG_ADINNODIV = 0x03,
            CHCFG_ADIN32DC = 0x04,
            CHCFG_ADIN256DC = 0x05,
            CHCFG_VONE_IINF = 0x06,
            CHCFG_SNAPSHOT = 0x07,

            VOLT_DELTA = 0x00 << 3,
            VOLT_VDD = 0x01 << 3,
            VOLT_ADIN = 0x02 << 3,
            VOLT_SENSEP = 0x03 << 3,

            OFFSETCAL_EVERY = 0x00 << 5,
            OFFSETCAL_EVERY16 = 0x01 << 5,
            OFFSETCAL_EVERY128 = 0x02 << 5,
            OFFSETCAL_POWERUP = 0x03 << 5,

            ADINCFG_GND = 0x00 << 7,
            ADINCFG_INTVCC = 0x01 << 7
        }

        public struct EM_VandI
        {
            public double volts;
            public double amps;
            public override string ToString()
            {
                return "Volts: " + volts.ToString() + " Milliamps: " + amps.ToString();
            }
        }

        public struct EM_VICE
        {
            public double volts;
            public double milliAmps;
            public double uCoulombs;
            public double milliJoules;
            public override string ToString()
            {
                return "Volts: " + volts.ToString() + " Milliamps: " + milliAmps.ToString() + "  Charge: " + uCoulombs.ToString() + " Joules: " + milliJoules.ToString(); ;
            }
        }

        public struct emIVWTCE
        {
            public double milliAmps;
            public double volts;
            public double uWatts;
            public double milliSeconds;
            public double uCoulombs;
            public double milliJoules;
            
            public override string ToString()
            {
                return "Volts: " + volts.ToString() + " Milliamps: " + milliAmps.ToString() + "uWatts: " + uWatts.ToString()
                    + "  Charge: " + uCoulombs.ToString() + " Joules: " + milliJoules.ToString(); ;
            }
        }

        public struct SystemVICE
        {
            public double pumpVolts;
            public double pumpMilliamps;
            public double pumpCoulombs;
            public double pumpJoules;
            public double hotelVolts;
            public double hotelMilliamps;
            public double hotelCoulombs;
            public double hotelJoules;
        }
    }
}