#include "Arduino.h"

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ARRAY_LENGTH(arr) (sizeof(arr) / sizeof(arr[0]))

#ifdef ARDUINO
// work on esp32

#include <U8g2lib.h>

//use i2c
#define I2C_CLK 22
#define I2C_DATA 23
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ I2C_CLK, /* data=*/ I2C_DATA);

// use SPI
// #define SPI_CLK 2
// #define SPI_DATA 3
// #define SPI_CS 7
// #define SPI_DC 6
// #define SPI_RST 10

// class U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI_ESP32 : public U8G2 {
// public:
//   U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI_ESP32(const u8g2_cb_t *rotation, uint8_t clock, uint8_t data, uint8_t cs, uint8_t dc, uint8_t reset = U8X8_PIN_NONE)
//     : U8G2() {
//     u8g2_Setup_ssd1306_128x64_noname_f(&u8g2, rotation, u8x8_byte_arduino_hw_spi, u8x8_gpio_and_delay_arduino);
//     u8x8_SetPin_4Wire_SW_SPI(getU8x8(), clock, data, cs, dc, reset);
//   }
// };
// U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI_ESP32 u8g2(U8G2_R0, SPI_CLK, SPI_DATA, SPI_CS, SPI_DC, SPI_RST);
#else
U8G2_SDL_128X64 u8g2;
#endif


namespace AtomUI {
    class Animation {
    protected:
        static constexpr int HEIGHT = 16;
        static constexpr int SPEED = 1;
        uint8_t position = 0;        // 相当于行数, 实际位置在 position*HEIGHT, 不受渲染位置无关
        int8_t speed = 0;            //当前移动速度 上负 下正
        uint8_t slotSizeScreen = 0;  //屏幕最多显示多少行
        int currentY = 0;            //动画渲染 Y轴绝对坐标

    public:
        Animation() {
            slotSizeScreen = u8g2.getHeight() / HEIGHT;
        }

        [[nodiscard]] uint8_t getPosition() const {
            return position;
        }

        virtual void calculateY() {
            auto targetY = position * HEIGHT;
            if (speed == 0) {  // 有速度才滚动
                currentY = targetY;
            } else {
                currentY = currentY + speed;  // 以特定速度滚动一步
                if (speed > 0) {              // 判断滚动反向 发现滚过头了后撤回来,并清除速度
                    if (currentY >= targetY) {  //滚动多了,超过目标位置
                        currentY = targetY;       //退回到实际位置
                        speed = 0;                // 停止滚动
                    }
                } else if (speed < 0) {
                    if (currentY <= targetY) {
                        currentY = targetY;
                        speed = 0;
                    }
                }
            }
        }

        virtual void up() {
            if (position > 0) {
                position--;
                speed = -SPEED;
            }
        }

        virtual void down() = 0;

        virtual void draw() = 0;
    };

    class Item : public Animation {
    public:
        virtual uint8_t getWidth(int y) = 0;

        virtual uint8_t getSize() = 0;
    };

    class List : public Item {
    public:
        static constexpr int OFFSET_X = 0;
        static constexpr int OFFSET_Y = 13;
        bool allowScroll = false;
        uint8_t listSize;
        char **list;
    public:
        List(char *list[], int size) {
            this->listSize = size;
            this->list = list;

            if (listSize > slotSizeScreen) {
                allowScroll = true;
            }
        }

        uint8_t getSize() override {
            return listSize;
        }

        void draw() override {
            if (allowScroll) {
                calculateY();
            }

            for (int i = 0; i < listSize; ++i) {  //渲染所有列表项
                u8g2.setDrawColor(1);
                u8g2.drawStr(OFFSET_X, i * HEIGHT + OFFSET_Y - currentY, list[i]);
            }
        }

        /**
               * 根据屏幕坐标,获取插值后的宽度
               * @param screenY 屏幕Y轴坐标
               * @return 宽度
               */
        uint8_t getWidth(int screenY) override {
            screenY = screenY + currentY;

            int index = screenY / HEIGHT;  // 绝对坐标转行数

            int remainder = screenY % HEIGHT;
            if (remainder == 0) {  // 如果余数为0,恰好落在了某行上,直接返回当前行的宽度
                return u8g2.getStrWidth(list[index]);
            } else {                                                                  // 落在两行之间
                auto fixedPoint = 1024;                                                 // 避免浮点运算
                int percent = remainder * fixedPoint / HEIGHT;                          // 计算当前位置在两行之间的百分比
                auto fromWidth = u8g2.getStrWidth(list[index]);                         // 获取当前行的宽度
                auto toWidth = u8g2.getStrWidth(list[index + 1]);                       // 获取下一行的宽度
                auto width = fromWidth + (toWidth - fromWidth) * percent / fixedPoint;  // 根据百分比进行插值计算得到宽度
                return width;                                                           // 返回计算得到的宽度
            }
        }

        void down() override {
            if (allowScroll) {
                if (position < listSize - slotSizeScreen) {
                    position++;
                    speed = SPEED;
                }
            }
        }
    };


    class Cursor : public Animation {
    private:
        static constexpr int RADIUS = 1;    // 圆角矩形半径
        static constexpr int OFFSET_X = 0;  // 渲染位置偏移量
        static constexpr int OFFSET_Y = 1;
        static constexpr int OFFSET_WIDTH = 5;    // 游标框向右扩展像素数
        static constexpr int OFFSET_HEIGHT = 14;  // 游标框总高度
        int slotSize = 0;                         // 实际渲染行数  游标可能出现的位置数

        Item *item;

    public:
        explicit Cursor(Item *item) {
            this->item = item;
            slotSize = MIN(this->item->getSize(), slotSizeScreen);
        };

        void up() override {
            if (position > 0) {
                position--;
                speed = -SPEED;
            } else {
                item->up();
            }
        }

        void down() override {
            if (position < slotSize - 1) {
                position++;
                speed = SPEED;
            } else {
                item->down();
            }
        }

        void draw() override {
            u8g2.setDrawColor(2);
            calculateY();
            u8g2.drawRBox(OFFSET_X, currentY + OFFSET_Y,
                          item->getWidth(currentY) + OFFSET_WIDTH,
                          OFFSET_HEIGHT,
                          RADIUS);
        }

        uint8_t getSelectedIndex() {
            return item->getPosition() + position;
        }
    };
}


void setup() {
    Serial.begin(115200);  //没实际意义
    u8g2.begin();
    u8g2.setFont(u8g2_font_wqy12_t_chinese1);
    u8g2.setFontPosBaseline();
}


//主菜单内容
const char *menu_main1[] = {
        "- 1 start",
        "- 2 bbbbbbbb",
        "- 3",
        "- 4 LEGEND",
        "- 5 ",
        "- 6",
        "- 7 cccccccc",
        "- 8",
        "- 9",
        "- 10 end",
};


AtomUI::List list(const_cast<char **>(menu_main1), ARRAY_LENGTH(menu_main1));
AtomUI::Cursor cursor(&list);

void loop() {
    delay(5);
    u8g2.clearBuffer();
    list.draw();
    cursor.draw();
    u8g2.sendBuffer();

#ifdef ARDUINO

    static uint8_t fps = 0;
    static int count = 0;
    static bool flag = true;
    fps++;
    if (fps % 32 == 0) {
        count++;
        if (flag) {
            cursor.down();
        } else {
            cursor.up();
        }
        if (count == 10) {
            count = 0;
            flag = !flag;
        }
    }

#else
    int k = 0;
    k = u8g_sdl_get_key();
    switch (k) {
        case 'w': {
            Serial.println("w");
            cursor.up();
        }
            break;
        case 's': {
            Serial.println("s");
            cursor.down();
        }
            break;
        case 'a': {
            Serial.println("a");
        }
            break;
        case 'd': {
            Serial.println("d");
        }
            break;
        default: {
        }
    }
#endif
}