using System;
using System.IO;
using Mono.Unix.Native;

namespace Inspectron.Devices.Raspberry
{
    public unsafe class I2CLinux
    {
        string device;
        int fd = -1;

        public I2CLinux(int index)
        {
            device = "/dev/i2c-" + index;
            Open();
            //Close();
        }

        public void Open()
        {
            fd = Syscall.open(device, OpenFlags.O_RDWR);
            if (fd < 0)
                throw new IOException(device);
        }

        void IoCtl(byte devAddr)
        {
            int ret = LunixNatives.ioctl(fd, LunixNatives.I2C_SLAVE, devAddr);
            if (ret < 0)
                throw new IOException(device + ": ioctl");
        }

        public byte readBytes(byte devAddr, byte regAddr, byte length, byte[] data, int offset, ushort timeout = 0)
        {
            if (length > 127)
                throw new IOException(device + ": length > 127");

            //Open();

            IoCtl(devAddr);

            //fixed(byte* p = &regAddr)
            {
                int ret = (int)Syscall.write(fd, &regAddr, 1);
                if (ret != 1)
                    throw new IOException(device + ": write");
            }

            int count;
            fixed (byte* p = &data[offset])
            {
                count = (int)Syscall.read(fd, p, (ulong)length);
                if (count < 0)
                    throw new IOException(device + ": read");
                else if (count != length)
                    throw new IOException(device + ": read short: length = " + length + " > " + count);
            }

            //Close();

            return (byte)count;
        }

        public byte readBytes(byte devAddr, byte regAddr, byte length, byte[] data, ushort timeout = 0)
        {
            return readBytes(devAddr, regAddr, length, data, 0, timeout);
        }

        /** Write multiple bytes to an 8-bit device register.
        * @param devAddr I2C slave device address
        * @param regAddr First register address to write to
        * @param length Number of bytes to write
        * @param data Buffer to copy new data from
        * @return Status of operation (true = success)
        */
        public void writeBytes(byte devAddr, byte regAddr, byte length, byte[] data)
        {
            if (length > 127)
                throw new IOException(device + ": length > 127");

            //Open();
            IoCtl(devAddr);

            byte[] buffer = new byte[128];
            buffer[0] = regAddr;
            Array.Copy(data, 0, buffer, 1, length);

            int count;
            fixed (byte* p = buffer)
            {
                count = (int)Syscall.write(fd, p, (ulong)(length + 1));
            }

            if (count < 0)
            {
                throw new IOException(device + ": write = " + count);
            }
            else if (count != length + 1)
            {
                throw new IOException(device + ": write short = " + count);
            }

            //Close();
        }


        /** Write multiple words to a 16-bit device register.
        * @param devAddr I2C slave device address
        * @param regAddr First register address to write to
        * @param length Number of words to write
        * @param data Buffer to copy new data from
        * @return Status of operation (true = success)
        */
        public void writeWords(byte devAddr, byte regAddr, byte length, ushort[] data)
        {
            int count = 0;
            byte[] buf = new byte[128];
            int i;

            // Should do potential byteswap and call writeBytes() really, but that
            // messes with the callers buffer

            if (length > 63)
            {
                throw new IOException(device + ": length > 63");
            }

            //Open();
            IoCtl(devAddr);

            buf[0] = regAddr;
            for (i = 0; i < (int)length; i++)
            {
                buf[i * 2 + 1] = (byte)(data[i] >> 8);
                buf[i * 2 + 2] = (byte)data[i];
            }
            fixed (byte* p = buf)
            {
                count = (int)Syscall.write(fd, p, (ulong)(length * 2 + 1));
            }
            if (count < 0)
            {
                throw new IOException(device + ": write");
            }
            else if (count != length * 2 + 1)
            {
                throw new IOException(device + ": write short");
            }
            //Close();
        }

        public void Close()
        {
            int ret = Syscall.close(fd);
            if (ret != 0)
                throw new IOException(device);
        }
    }
}