import sensor, image, time
import uos as os
import screen
from screen import touch_exist, get_touch
from text import draw_text
from coprocessor import set_openmv_rtc, restart as coproc_reset, set_boot_manager
from save_locally import save as save_local

# 必须：同步 RTC
set_openmv_rtc()

# === 参数 ===
BROWSE_DIR   = "/Blockly_Program"                 # 目录
ICON_PATH    = "/system/icons/python.bmp"         # 行图标（32x32）
BACK_ICON    = "/system/icons/back.bmp"           # 顶部返回按钮图标（70x35）
TOP_H        = 35                                  # 顶部栏高度
ROW_H        = 40                                  # 每行高度（两行文本）
LINE_H       = 18
SB_W         = 26                                  # 滚动条宽
BTN_H        = 24                                  # 滚动条上下按钮高度
LEFT_PAD     = 4                                   # 列表左侧留白
ASCII_W      = 8
CJK_W        = 16
TITLE_COLOR  = (255,255,255)
SUB_COLOR    = (160,160,160)
BAR_COLOR    = (50,50,50)
BG_ROW       = (20,20,20)
BG_ROW_HL    = (70,70,70)

def ascii_px(s): return len(s) * ASCII_W
def text_px(s):
    w = 0
    for c in s:
        w += ASCII_W if ord(c) < 128 else CJK_W
    return w

def fmt_size(n):
    try: n = int(n)
    except: return "?"
    if n < 1024: return "%d B" % n
    if n < 1024*1024: return "%.1f KB" % (n/1024.0)
    return "%.1f MB" % (n/1024.0/1024.0)

def fmt_time(ts):
    try:
        t = time.localtime(int(ts))
        return "%04d-%02d-%02d %02d:%02d" % (t[0],t[1],t[2],t[3],t[4])
    except:
        return "--"

class ProgramBrowser:
    def __init__(self, path=BROWSE_DIR, x=0, y=0, w=320, h=240):
        self.x,self.y,self.w,self.h = x,y,w,h
        self.path = path

        # 可视区域（顶部栏下方）
        self.list_x = self.x
        self.list_y = self.y + TOP_H
        self.list_w = self.w
        self.list_h = self.h - TOP_H

        # 固定每页 5 行
        self.rows_per_page = 5
        used_h = self.rows_per_page * ROW_H
        self.vpad = max(6, (self.list_h - used_h)//2)  # 垂直居中，上下留白

        # 滚动 & 按压状态
        self.top_idx = 0
        self._dragging_thumb = False
        self._drag_offset_y  = 0
        self._pressed_btn    = None   # 'up' / 'down'
        self._pressed_row    = -1     # 相对当前页的行索引
        self._back_pressed   = False  # 返回按钮按下高亮
        self._just_clicked   = False  # 点击去抖

        # 只加载一次的位图缓存（避免反复读卡）
        self._icon_img = None
        self._back_img = None
        try:
            self._icon_img = image.Image(ICON_PATH)  # 32x32
        except:
            self._icon_img = None
        try:
            self._back_img = image.Image(BACK_ICON)  # 70x35
        except:
            self._back_img = None

        self._scan_files()

        # 渲染快照 & 脏标志：首帧必须重绘
        self._render_snapshot = None
        self._needs_full_redraw = True

    # 目录下 .py 文件，按 mtime 降序
    def _scan_files(self):
        items = []
        try:
            names = os.listdir(self.path)
        except:
            names = []
        for name in names:
            if not name.lower().endswith(".py"): continue
            full = self._join(self.path, name)
            try:
                st = os.stat(full)
                size  = st[6] if len(st) > 6 else 0
                mtime = st[8] if len(st) > 8 else 0
            except:
                size, mtime = 0, 0
            items.append({"name": name, "path": full, "size": size, "mtime": mtime})
        items.sort(key=lambda d: d["mtime"], reverse=True)
        self.items = items
        self.top_idx = min(self.top_idx, max(0, len(self.items)-1))

    def _join(self, a, b):
        if not a: return b
        return a + b if a.endswith("/") else (a + "/" + b)

    # 顶部返回键区域（70x35，位于 (0,0)）
    def _back_rect(self):
        return 0, 0, 70, 35

    # 滚动条度量
    def _scroll_metrics(self):
        used_h = self.rows_per_page * ROW_H
        x0 = self.x + self.w - SB_W
        area_top = self.list_y + self.vpad
        track_top = area_top + BTN_H
        track_h   = used_h - BTN_H - BTN_H
        total = max(1, len(self.items))
        thumb_h = max(24, (track_h * self.rows_per_page) // total) if track_h > 0 else 24
        max_top = max(0, total - self.rows_per_page)
        if max_top == 0 or track_h <= 0:
            thumb_y = track_top
        else:
            travel = track_h - thumb_h
            thumb_y = track_top + (travel * self.top_idx) // max_top
        return x0, area_top, used_h, track_top, track_h, thumb_y, thumb_h

    # 绘制一行
    def _draw_row(self, img, idx, y, highlight=False):
        item = self.items[idx]
        img.draw_rectangle(self.x, y, self.w-SB_W, ROW_H, color=(BG_ROW_HL if highlight else BG_ROW), fill=True)

        # 图标
        ix, iy = self.x + LEFT_PAD, y + (ROW_H - 32)//2
        if self._icon_img is not None:
            try:
                img.draw_image(self._icon_img, ix, iy)  # 使用缓存位图
            except:
                pass

        # 文本区
        tx = ix + 32 + 6
        tw = self.w - SB_W - tx - 8

        # 文件名（超长省略）
        name = item["name"]
        nm = name
        while text_px(nm) > tw and len(nm) > 1:
            nm = nm[:-1]
        if nm != name:
            ell = "..."
            while text_px(nm) + ascii_px(ell) > tw and len(nm) > 1:
                nm = nm[:-1]
            nm = nm + ell
        draw_text(img, tx, y+1, nm, color=TITLE_COLOR)

        # 日期（左）尺寸（右）
        subs_y = y + 22
        date_s = fmt_time(item["mtime"])
        img.draw_string(tx, subs_y, date_s, color=SUB_COLOR)

        size_s = fmt_size(item["size"])
        sx = self.x + self.w - SB_W - 8 - ascii_px(size_s)
        if sx < tx + 2: sx = tx + 2
        img.draw_string(sx, subs_y, size_s, color=SUB_COLOR)

        # 分隔线
        img.draw_rectangle(self.x, y+ROW_H-1, self.w-SB_W, 1, color=(35,35,35))

    # 绘制滚动条
    def _draw_scrollbar(self, img):
        x0, area_top, used_h, track_top, track_h, thumb_y, thumb_h = self._scroll_metrics()
        # 背板
        img.draw_rectangle(x0, self.list_y, SB_W, self.list_h, color=(40,40,40), fill=True)
        # 上/下按钮（单击）
        img.draw_rectangle(x0, area_top, SB_W, BTN_H, color=(90,90,90), fill=True)
        img.draw_string(x0 + (SB_W//2 - 4), area_top + 4, ' ')
        img.draw_rectangle(x0, area_top + used_h - BTN_H, SB_W, BTN_H, color=(90,90,90), fill=True)
        img.draw_string(x0 + (SB_W//2 - 4), area_top + used_h - BTN_H + 4, ' ')
        # 轨道 & 滑块
        if track_h > 0:
            img.draw_rectangle(x0+3, track_top, SB_W-6, track_h, color=(60,60,60), fill=True)
            img.draw_rectangle(x0+3, thumb_y,   SB_W-6, thumb_h, color=(200,200,200), fill=True)

    # 顶部栏（含返回键）
    def _draw_topbar(self, img):
        # 顶部条
        img.draw_rectangle(self.x, self.y, self.w, TOP_H, color=BAR_COLOR, fill=True)
        # 返回图标
        bx, by, bw, bh = self._back_rect()
        if self._back_img is not None:
            try:
                img.draw_image(self._back_img, bx, by)  # 使用缓存位图
            except:
                pass
        else:
            img.draw_string(bx+4, by+8, "<")
        # 标题
        draw_text(img, self.x + self.w//2, self.y + 8, "测试运行程序", center=True)

    # 触摸处理（返回：被点击的行相对索引 or None）
    def _handle_touch(self):
        if not touch_exist():
            # 返回按钮：单击触发
            if self._back_pressed:
                self._back_pressed = False
                coproc_reset()

            # 上/下按钮：单击在抬起时生效
            if self._pressed_btn is not None:
                if self._pressed_btn == 'up':
                    self.top_idx = max(0, self.top_idx - self.rows_per_page)
                elif self._pressed_btn == 'down':
                    max_top = max(0, len(self.items) - self.rows_per_page)
                    self.top_idx = min(max_top, self.top_idx + self.rows_per_page)
            self._pressed_btn  = None

            # 行点击释放：带去抖
            if self._pressed_row >= 0 and not self._just_clicked:
                pr = self._pressed_row
                self._pressed_row = -1
                self._just_clicked = True   # 锁定，直到下一次触摸按下时解锁
                return pr
            self._pressed_row = -1
            return None

        # 有触摸：任何触摸开始都解锁去抖
        self._just_clicked = False

        tx, ty, tz = get_touch()

        # 顶部返回按钮（70x35）
        bx, by, bw, bh = self._back_rect()
        if (bx <= tx < bx+bw) and (by <= ty < by+bh):
            self._back_pressed = True
            return None
        else:
            self._back_pressed = False

        # 只处理列表区域
        if not (self.list_y <= ty < self.list_y + self.list_h):
            return None

        x0, area_top, used_h, track_top, track_h, thumb_y, thumb_h = self._scroll_metrics()

        # 滚动条区域
        if tx >= x0:
            # 上/下按钮（单击）
            if area_top <= ty < area_top + BTN_H:
                self._pressed_btn = 'up';  return None
            if area_top + used_h - BTN_H <= ty < area_top + used_h:
                self._pressed_btn = 'down'; return None
            # 若按了按钮，不处理拖拽/轨道
            if self._pressed_btn is not None: return None

            # 拖动滑块
            if track_h > 0:
                if not self._dragging_thumb and (thumb_y <= ty <= thumb_y + thumb_h):
                    self._dragging_thumb = True
                    self._drag_offset_y = ty - thumb_y
                    return None
                if self._dragging_thumb:
                    max_top = max(0, len(self.items) - self.rows_per_page)
                    travel = track_h - thumb_h
                    if travel <= 0:
                        self.top_idx = 0
                    else:
                        new_thumb_y = ty - self._drag_offset_y
                        rel = (new_thumb_y - track_top) / float(travel)
                        if rel < 0: rel = 0
                        if rel > 1: rel = 1
                        self.top_idx = int(rel * max_top)
                    return None

                # 点击轨道跳转
                max_top = max(0, len(self.items) - self.rows_per_page)
                if track_h > 0 and max_top > 0:
                    rel = (ty - track_top) / float(track_h)
                    if rel < 0: rel = 0
                    if rel > 1: rel = 1
                    self.top_idx = int(rel * max_top)
            return None

        # 列表行区域
        rel = (ty - (self.list_y + self.vpad)) // ROW_H
        if 0 <= rel < self.rows_per_page:
            self._pressed_row = rel
        return None

    # 计算“渲染快照”，用来判定是否需要重绘
    def _snapshot(self):
        # 注意：items 内容变化这里不比较（目录只在 __init__ 扫一次）
        # 若你未来需要“下拉刷新”，可以在刷新后 self._needs_full_redraw = True
        return (
            self.top_idx,
            self._pressed_row,
            self._pressed_btn,
            self._dragging_thumb,
            self._back_pressed,
            len(self.items),
        )

    # 只在需要时绘制
    def _render(self, fb_img):
        # 顶部栏
        self._draw_topbar(fb_img)

        # 空目录
        if not self.items:
            fb_img.draw_rectangle(self.list_x, self.list_y, self.list_w, self.list_h, color=(0,0,0), fill=True)
            fb_img.draw_string(self.x + 10, self.list_y + self.vpad + 10, "（此目录下无 .py 文件）", color=SUB_COLOR)
            self._draw_scrollbar(fb_img)
            return

        # 背景行区域清空（防止局部残影）
        fb_img.draw_rectangle(self.list_x, self.list_y, self.list_w - SB_W, self.list_h, color=(0,0,0), fill=True)

        # 可见行
        start = self.top_idx
        end   = min(len(self.items), start + self.rows_per_page)
        for i, idx in enumerate(range(start, end)):
            y = self.list_y + self.vpad + i * ROW_H
            highlight = (i == self._pressed_row)
            self._draw_row(fb_img, idx, y, highlight=highlight)

        # 滚动条
        self._draw_scrollbar(fb_img)

    # 对外：处理输入，按需重绘
    # 返回 (need_display, clicked_fullpath or None)
    def state(self, fb_img):
        prev_snap = self._render_snapshot

        # 先处理触摸与状态变化
        clicked_rel = self._handle_touch()

        # 拖拽结束时要清理标志（离触控时 _dragging_thumb 可能仍 True）
        if not touch_exist():
            self._dragging_thumb = False

        # 判断是否需要重绘
        new_snap = self._snapshot()
        need_redraw = self._needs_full_redraw or (prev_snap != new_snap)

        selected = None
        if clicked_rel is not None and clicked_rel >= 0:
            idx = self.top_idx + clicked_rel
            if 0 <= idx < len(self.items):
                full = self.items[idx]["path"]
                print(full)  # 单击释放时只打印一次
                # 保存选择并设置为用户启动程序
                try:
                    save_local('user_program', full, '/system/user_boot_manager.py')
                    set_boot_manager(0)
                except Exception as e:
                    # 异常时至少刷新一次，把错误信息显示在外部串口；界面保持
                    pass
                # 立刻软重启
                coproc_reset()
                selected = full
                # 重启去了，此处是否重绘已无意义，但保持一致性：
                need_redraw = True

        # 需要时才绘制
        if need_redraw:
            self._render(fb_img)
            self._render_snapshot = new_snap
            self._needs_full_redraw = False

        return need_redraw, selected


# ==== 运行 ====
screen.init()
fb = sensor.alloc_extra_fb(320, 240, sensor.RGB565)
browser = ProgramBrowser(path=BROWSE_DIR)

while True:
    need_display, selected = browser.state(fb)
    if need_display:
        screen.display(fb)
    # 轻微让出CPU，避免空转占用过高（可按需调小/去掉）
#    time.sleep_ms(10)
