/*
 * Ten Wong (wangtengoo7@gmail.com) 2017 Seeed technology inc.
 * Refer to: https://github.com/Seeed-Studio/OLED_Display_128X64
 * MIT License
 */
using System;
using Windows.Devices.I2c;

namespace GrovePi.I2CDevices
{
    public interface IOLEDDisplay128X64
    {
        void init();

        void setNormalDisplay();
        void setInverseDisplay();

        void sendCommand(byte command);
        void sendData(byte data);

        void setPageMode();
        void setHorizontalMode();

        void setTextXY(byte row, byte column);
        void clearDisplay();
        void setBrightness(byte brightness);
        void putChar(char c);
        void putString(string str);
        int putNumber(long n);
        int putFloat(float floatNumber, byte deci);
        int putFloat(float floatNumber);
        void drawBitmap(byte[] bitmaparray, int bytes);

        void setHorizontalScrollProperties(byte direction, byte startPage, byte endPage, byte scrollSpeed);
        void activateScroll();
        void deactivateScroll();

    }
    internal sealed class OLEDDisplay128X64: IOLEDDisplay128X64
    {
        private const byte SeeedOLED_Max_X = 127; //128 Pixels
        private const byte SeeedOLED_Max_Y = 63;  //64  Pixels

        private const byte PAGE_MODE = 01;
        private const byte HORIZONTAL_MODE = 02;

        private const byte SeeedOLED_Address = 0x3c;
        private const byte SeeedOLED_Command_Mode = 0x80;
        private const byte SeeedOLED_Data_Mode = 0x40;
        private const byte SeeedOLED_Display_Off_Cmd = 0xAE;
        private const byte SeeedOLED_Display_On_Cmd = 0xAF;
        private const byte SeeedOLED_Normal_Display_Cmd = 0xA6;
        private const byte SeeedOLED_Inverse_Display_Cmd = 0xA7;
        private const byte SeeedOLED_Activate_Scroll_Cmd = 0x2F;
        private const byte SeeedOLED_Dectivate_Scroll_Cmd = 0x2E;
        private const byte SeeedOLED_Set_Brightness_Cmd = 0x81;

        private const byte Scroll_Left = 0x00;
        private const byte Scroll_Right = 0x01;

        private const byte Scroll_2Frames = 0x7;
        private const byte Scroll_3Frames = 0x4;
        private const byte Scroll_4Frames = 0x5;
        private const byte Scroll_5Frames = 0x0;
        private const byte Scroll_25Frames = 0x6;
        private const byte Scroll_64Frames = 0x1;
        private const byte Scroll_128Frames = 0x2;
        private const byte Scroll_256Frames = 0x3;

        // This font be freely used without any restriction(It is placed in public domain)
        private byte[,] BasicFont = new byte[,]
        {
            {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
            {0x00,0x00,0x5F,0x00,0x00,0x00,0x00,0x00},
            {0x00,0x00,0x07,0x00,0x07,0x00,0x00,0x00},
            {0x00,0x14,0x7F,0x14,0x7F,0x14,0x00,0x00},
            {0x00,0x24,0x2A,0x7F,0x2A,0x12,0x00,0x00},
            {0x00,0x23,0x13,0x08,0x64,0x62,0x00,0x00},
            {0x00,0x36,0x49,0x55,0x22,0x50,0x00,0x00},
            {0x00,0x00,0x05,0x03,0x00,0x00,0x00,0x00},
            {0x00,0x1C,0x22,0x41,0x00,0x00,0x00,0x00},
            {0x00,0x41,0x22,0x1C,0x00,0x00,0x00,0x00},
            {0x00,0x08,0x2A,0x1C,0x2A,0x08,0x00,0x00},
            {0x00,0x08,0x08,0x3E,0x08,0x08,0x00,0x00},
            {0x00,0xA0,0x60,0x00,0x00,0x00,0x00,0x00},
            {0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x00},
            {0x00,0x60,0x60,0x00,0x00,0x00,0x00,0x00},
            {0x00,0x20,0x10,0x08,0x04,0x02,0x00,0x00},
            {0x00,0x3E,0x51,0x49,0x45,0x3E,0x00,0x00},
            {0x00,0x00,0x42,0x7F,0x40,0x00,0x00,0x00},
            {0x00,0x62,0x51,0x49,0x49,0x46,0x00,0x00},
            {0x00,0x22,0x41,0x49,0x49,0x36,0x00,0x00},
            {0x00,0x18,0x14,0x12,0x7F,0x10,0x00,0x00},
            {0x00,0x27,0x45,0x45,0x45,0x39,0x00,0x00},
            {0x00,0x3C,0x4A,0x49,0x49,0x30,0x00,0x00},
            {0x00,0x01,0x71,0x09,0x05,0x03,0x00,0x00},
            {0x00,0x36,0x49,0x49,0x49,0x36,0x00,0x00},
            {0x00,0x06,0x49,0x49,0x29,0x1E,0x00,0x00},
            {0x00,0x00,0x36,0x36,0x00,0x00,0x00,0x00},
            {0x00,0x00,0xAC,0x6C,0x00,0x00,0x00,0x00},
            {0x00,0x08,0x14,0x22,0x41,0x00,0x00,0x00},
            {0x00,0x14,0x14,0x14,0x14,0x14,0x00,0x00},
            {0x00,0x41,0x22,0x14,0x08,0x00,0x00,0x00},
            {0x00,0x02,0x01,0x51,0x09,0x06,0x00,0x00},
            {0x00,0x32,0x49,0x79,0x41,0x3E,0x00,0x00},
            {0x00,0x7E,0x09,0x09,0x09,0x7E,0x00,0x00},
            {0x00,0x7F,0x49,0x49,0x49,0x36,0x00,0x00},
            {0x00,0x3E,0x41,0x41,0x41,0x22,0x00,0x00},
            {0x00,0x7F,0x41,0x41,0x22,0x1C,0x00,0x00},
            {0x00,0x7F,0x49,0x49,0x49,0x41,0x00,0x00},
            {0x00,0x7F,0x09,0x09,0x09,0x01,0x00,0x00},
            {0x00,0x3E,0x41,0x41,0x51,0x72,0x00,0x00},
            {0x00,0x7F,0x08,0x08,0x08,0x7F,0x00,0x00},
            {0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00},
            {0x00,0x20,0x40,0x41,0x3F,0x01,0x00,0x00},
            {0x00,0x7F,0x08,0x14,0x22,0x41,0x00,0x00},
            {0x00,0x7F,0x40,0x40,0x40,0x40,0x00,0x00},
            {0x00,0x7F,0x02,0x0C,0x02,0x7F,0x00,0x00},
            {0x00,0x7F,0x04,0x08,0x10,0x7F,0x00,0x00},
            {0x00,0x3E,0x41,0x41,0x41,0x3E,0x00,0x00},
            {0x00,0x7F,0x09,0x09,0x09,0x06,0x00,0x00},
            {0x00,0x3E,0x41,0x51,0x21,0x5E,0x00,0x00},
            {0x00,0x7F,0x09,0x19,0x29,0x46,0x00,0x00},
            {0x00,0x26,0x49,0x49,0x49,0x32,0x00,0x00},
            {0x00,0x01,0x01,0x7F,0x01,0x01,0x00,0x00},
            {0x00,0x3F,0x40,0x40,0x40,0x3F,0x00,0x00},
            {0x00,0x1F,0x20,0x40,0x20,0x1F,0x00,0x00},
            {0x00,0x3F,0x40,0x38,0x40,0x3F,0x00,0x00},
            {0x00,0x63,0x14,0x08,0x14,0x63,0x00,0x00},
            {0x00,0x03,0x04,0x78,0x04,0x03,0x00,0x00},
            {0x00,0x61,0x51,0x49,0x45,0x43,0x00,0x00},
            {0x00,0x7F,0x41,0x41,0x00,0x00,0x00,0x00},
            {0x00,0x02,0x04,0x08,0x10,0x20,0x00,0x00},
            {0x00,0x41,0x41,0x7F,0x00,0x00,0x00,0x00},
            {0x00,0x04,0x02,0x01,0x02,0x04,0x00,0x00},
            {0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00},
            {0x00,0x01,0x02,0x04,0x00,0x00,0x00,0x00},
            {0x00,0x20,0x54,0x54,0x54,0x78,0x00,0x00},
            {0x00,0x7F,0x48,0x44,0x44,0x38,0x00,0x00},
            {0x00,0x38,0x44,0x44,0x28,0x00,0x00,0x00},
            {0x00,0x38,0x44,0x44,0x48,0x7F,0x00,0x00},
            {0x00,0x38,0x54,0x54,0x54,0x18,0x00,0x00},
            {0x00,0x08,0x7E,0x09,0x02,0x00,0x00,0x00},
            {0x00,0x18,0xA4,0xA4,0xA4,0x7C,0x00,0x00},
            {0x00,0x7F,0x08,0x04,0x04,0x78,0x00,0x00},
            {0x00,0x00,0x7D,0x00,0x00,0x00,0x00,0x00},
            {0x00,0x80,0x84,0x7D,0x00,0x00,0x00,0x00},
            {0x00,0x7F,0x10,0x28,0x44,0x00,0x00,0x00},
            {0x00,0x41,0x7F,0x40,0x00,0x00,0x00,0x00},
            {0x00,0x7C,0x04,0x18,0x04,0x78,0x00,0x00},
            {0x00,0x7C,0x08,0x04,0x7C,0x00,0x00,0x00},
            {0x00,0x38,0x44,0x44,0x38,0x00,0x00,0x00},
            {0x00,0xFC,0x24,0x24,0x18,0x00,0x00,0x00},
            {0x00,0x18,0x24,0x24,0xFC,0x00,0x00,0x00},
            {0x00,0x00,0x7C,0x08,0x04,0x00,0x00,0x00},
            {0x00,0x48,0x54,0x54,0x24,0x00,0x00,0x00},
            {0x00,0x04,0x7F,0x44,0x00,0x00,0x00,0x00},
            {0x00,0x3C,0x40,0x40,0x7C,0x00,0x00,0x00},
            {0x00,0x1C,0x20,0x40,0x20,0x1C,0x00,0x00},
            {0x00,0x3C,0x40,0x30,0x40,0x3C,0x00,0x00},
            {0x00,0x44,0x28,0x10,0x28,0x44,0x00,0x00},
            {0x00,0x1C,0xA0,0xA0,0x7C,0x00,0x00,0x00},
            {0x00,0x44,0x64,0x54,0x4C,0x44,0x00,0x00},
            {0x00,0x08,0x36,0x41,0x00,0x00,0x00,0x00},
            {0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00},
            {0x00,0x41,0x36,0x08,0x00,0x00,0x00,0x00},
            {0x00,0x02,0x01,0x01,0x02,0x01,0x00,0x00},
            {0x00,0x02,0x05,0x05,0x02,0x00,0x00,0x00}
        };

        internal I2cDevice DirectAccess { get; }

        internal byte addressingMode;

        internal OLEDDisplay128X64(I2cDevice Device)
        {
            if (Device == null) throw new ArgumentNullException(nameof(Device));

            DirectAccess = Device;
        }

        public void init()
        {
            sendCommand(SeeedOLED_Display_Off_Cmd);     //display off
            //delay(5);
            sendCommand(SeeedOLED_Display_On_Cmd);  //display on
            //delay(5);
            sendCommand(SeeedOLED_Normal_Display_Cmd);  //Set Normal Display (default)
        }

        public void setNormalDisplay()
        {
            sendCommand(SeeedOLED_Normal_Display_Cmd);
        }

        public void setInverseDisplay()
        {
            sendCommand(SeeedOLED_Inverse_Display_Cmd);
        }

        public void sendCommand(byte command)
        {
            DirectAccess.Write(new byte[] { SeeedOLED_Command_Mode, command });
        }

        public void sendData(byte data)
        {
            DirectAccess.Write(new byte[] { SeeedOLED_Data_Mode, data });
        }

        public void setPageMode()
        {
            addressingMode = PAGE_MODE;
            sendCommand(0x20);          //set addressing mode
            sendCommand(0x02);          //set page addressing mode
        }

        public void setHorizontalMode()
        {
            addressingMode = HORIZONTAL_MODE;
            sendCommand(0x20);          //set addressing mode
            sendCommand(0x00);          //set horizontal addressing mode
        }

        public void setTextXY(byte row, byte column)
        {
            sendCommand((byte)(0xB0 + row));            //set page address
            sendCommand((byte)(0x00 + (8 * column & 0x0F)));  //set column lower address
            sendCommand((byte)(0x10 + ((8 * column >> 4) & 0x0F)));   //set column higher address
        }

        public void clearDisplay()
        {
            byte i, j;
            sendCommand(SeeedOLED_Display_Off_Cmd);   //display off
            for (j = 0; j < 8; j++)
            {
                setTextXY(j, 0);
                {
                    for (i = 0; i < 16; i++)  //clear all columns
                    {
                        putChar(' ');
                    }
                }
            }
            sendCommand(SeeedOLED_Display_On_Cmd);    //display on
            setTextXY(0, 0);
        }

        public void setBrightness(byte brightness)
        {
            sendCommand(SeeedOLED_Set_Brightness_Cmd);
            sendCommand(brightness);
        }

        public void putChar(char c)
        {
            if (c < 32 || c > 127) //Ignore non-printable ASCII characters. This can be modified for multilingual font.
            {
                c = ' '; //Space
            }
            byte i = 0;
            for (i = 0; i < 8; i++)
            {
                //read bytes from code memory
                sendData(BasicFont[c-32, i]); //font array starts at 0, ASCII starts at 32. Hence the translation
            }
        }
  

        public void putString(string str)
        {
            foreach (var c in str)
            {
                putChar(c);
            }
        }

        public int putNumber(long long_num)
        {
            char[] char_buffer = new char[10];
            int i = 0;
            int f = 0;

            if (long_num < 0)
            {
                f = 1;
                putChar('-');
                long_num = -long_num;
            }
            else if (long_num == 0)
            {
                f = 1;
                putChar('0');
                return f;
            }

            while (long_num > 0)
            {
                char_buffer[i++] = (char)(long_num % 10);
                long_num /= 10;
            }

            f = f + i;
            for (; i > 0; i--)
            {
                putChar((char)('0' + (int)(char_buffer[i - 1])));
            }
            return f;
        }

        public int putFloat(float floatNumber, byte deci)
        {
            int temp = 0;
            float decy = 0.0F;
            float rounding = 0.5F;
            int f = 0;
            if (floatNumber < 0.0F)
            {
                putString("-");
                floatNumber = -floatNumber;
                f += 1;
            }
            for (int i = 0; i < deci; ++i)
            {
                rounding /= 10.0F;
            }
            floatNumber += rounding;

            temp = (int)floatNumber;
            f += putNumber(temp);
            if (deci > 0)
            {
                putChar('.');
                f += 1;
            }
            decy = floatNumber - temp;//decimal part,
            for (int i = 0; i < deci; i++)//4
            {
                decy *= 10;// for the next decimal
                temp = (int)decy;//get the decimal
                putNumber(temp);
                decy -= temp;
            }
            f += deci;
            return f;
        }

        public int putFloat(float floatNumber)
        {
            int deci= 2;
            int temp = 0;
            float decy = 0.0F;
            float rounding = 0.5F;
            int f = 0;
            if (floatNumber < 0.0)
            {
                putString("-");
                floatNumber = -floatNumber;
                f += 1;
            }
            for (int i = 0; i < deci; ++i)
            {
                rounding /= 10.0F;
            }
            floatNumber += rounding;

            temp = (int)floatNumber;
            f += putNumber(temp);
            if (deci > 0)
            {
                putChar('.');
                f += 1;
            }
            decy = floatNumber - temp;//decimal part,
            for (int i = 0; i < deci; i++)//4
            {
                decy *= 10;// for the next decimal
                temp = (int)decy;//get the decimal
                putNumber(temp);
                decy -= temp;
            }
            f += deci;
            return f;
        }

        public void drawBitmap(byte[] bitmaparray, int bytes)
        {
            byte localAddressMode = addressingMode;
            if (addressingMode != HORIZONTAL_MODE)
            {
                //Bitmap is drawn in horizontal mode
                setHorizontalMode();
            }

            for (int i = 0; i < bytes; i++)
            {
                sendData(bitmaparray[i]);
            }

            if (localAddressMode == PAGE_MODE)
            {
                //If pageMode was used earlier, restore it.
                setPageMode();
            }

        }

        public void setHorizontalScrollProperties(byte direction, byte startPage, byte endPage, byte scrollSpeed)
        {
            if (Scroll_Right == direction)
            {
                //Scroll Right
                sendCommand(0x26);
            }
            else
            {
                //Scroll Left
                sendCommand(0x27);

            }
            sendCommand(0x00);
            sendCommand(startPage);
            sendCommand(scrollSpeed);
            sendCommand(endPage);
            sendCommand(0x00);
            sendCommand(0xFF);
        }

        public void activateScroll()
        {
            sendCommand(SeeedOLED_Activate_Scroll_Cmd);
        }

        public void deactivateScroll()
        {
            sendCommand(SeeedOLED_Dectivate_Scroll_Cmd);
        }
    }
}
