#!/usr/bin/env python3
"""Verify actual TranscriptFont selection/cache with substituted glyph resources."""
from pathlib import Path
import subprocess
import tempfile
repo=Path(__file__).resolve().parents[2]
with tempfile.TemporaryDirectory(prefix="transcript-cache-") as folder:
    temp=Path(folder)
    (temp/"M5GFX.h").write_text(r'''#pragma once
#include <cstdint>
#include <cstddef>
namespace lgfx {
struct LGFXBase {}; struct TextStyle {};
struct FontMetrics { int16_t width=0,x_advance=0,x_offset=0,height=0,baseline=0,y_offset=0; };
struct IFont {
 virtual void getDefaultMetric(FontMetrics*) const=0;
 virtual bool updateFontMetric(FontMetrics*,uint16_t) const=0;
 virtual size_t drawChar(LGFXBase*,int32_t,int32_t,uint16_t,const TextStyle*,FontMetrics*,int32_t&) const=0;
};
struct StubFont:IFont {
 uint16_t code;mutable unsigned calls=0;int16_t width;size_t raster;
 StubFont(uint16_t c,int16_t w,size_t r):code(c),width(w),raster(r){}
 void getDefaultMetric(FontMetrics* m) const override {m->height=16;m->baseline=12;}
 bool updateFontMetric(FontMetrics* m,uint16_t c) const override {
  ++calls;if(c!=code)return false;
  m->width=width;m->x_advance=width+1;m->x_offset=-1;return true;
 }
 size_t drawChar(LGFXBase*,int32_t,int32_t,uint16_t,const TextStyle*,FontMetrics*,int32_t&) const override {return raster;}
};
namespace fonts {inline StubFont efontCN_16{65,8,1},efontTW_16{321,16,2};}
}
''')
    (temp/"esp_heap_caps.h").write_text(r'''#pragma once
#include <cstdlib>
constexpr int MALLOC_CAP_SPIRAM=1,MALLOC_CAP_8BIT=2;
inline bool fail_allocation=false;
inline void* heap_caps_calloc(size_t n,size_t size,int caps) {
 if(caps!=(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT))std::abort();
 return fail_allocation?nullptr:std::calloc(n,size);
}
inline void heap_caps_free(void* p) {std::free(p);}
''')
    (temp/"test.cpp").write_text(r'''#include "transcript_font.h"
#include <cassert>
int main() {
 TranscriptFont font;lgfx::FontMetrics m{};m.height=28;m.baseline=22;m.y_offset=-22;
 assert(font.updateFontMetric(&m,65) && m.width==8 && m.x_advance==9 && m.x_offset==-1);
 const auto calls=lgfx::fonts::efontCN_16.calls;
 for(unsigned i=0;i<10000;++i)assert(font.updateFontMetric(&m,65));
 assert(lgfx::fonts::efontCN_16.calls==calls && m.height==28 && m.baseline==22 && m.y_offset==-22);
 // Traditional fallback collides with A: lookup must validate the code.
 assert(font.updateFontMetric(&m,321) && m.width==16);
 int32_t filled=0;assert(font.drawChar(nullptr,0,0,321,nullptr,&m,filled)==2);
 assert(font.updateFontMetric(&m,65) && m.width==8);
 assert(font.drawChar(nullptr,0,0,65,nullptr,&m,filled)==1);
 assert(!font.updateFontMetric(&m,66));m.width=27;m.x_advance=30;m.x_offset=-3;
 assert(!font.updateFontMetric(&m,66) && m.width==27 && m.x_advance==30 && m.x_offset==-3);
 fail_allocation=true;TranscriptFont uncached;
 assert(uncached.updateFontMetric(&m,321) && m.width==16);
 assert(uncached.updateFontMetric(&m,65) && m.width==8);
}
''')
    subprocess.run(["c++","-std=c++17","-Wall","-Wextra","-Werror","-I",str(temp),
        "-I",str(repo/"boards/common/include"),"-I",str(repo/"boards/m5stack_stopwatch"),
        str(temp/"test.cpp"),"-o",str(temp/"test")],check=True)
    subprocess.run([str(temp/"test")],check=True)
print("Transcript cache: hot path / CN-TW collision / caller metrics / missing / PSRAM failure PASS")
