#include <epictuner/sdk/read.hpp>
#include <epictuner/sdk/ui.hpp>
#include <epictuner/sdk/jobs.hpp>
#include <algorithm>
#include <cstring>
#include <limits>
#include <memory>
using namespace et::sdk;
namespace {std::string copy(et_string s){return {s.data?s.data:"",s.size};}}
struct Analyzer : Plugin {
    static constexpr auto id="org.epictuner.examples.analyzer";
    static constexpr uint64_t capabilities=ET_CAP_LOG|ET_CAP_EVENTS|ET_CAP_UI|ET_CAP_PROJECT_READ|ET_CAP_DATALOG_READ|ET_CAP_JOBS;
    Ui ui;Reads project,logs;Jobs jobs;std::unique_ptr<Context> host;
    et_handle panel{},job{};uint32_t revision=1;uint64_t generation=0,pending=0;
    bool active=false,patching=false,dirty=false;int logIndex=0,channelIndex=0,pass=0;uint32_t first=0,last=999999,offset=0;
    std::vector<std::string> ids,names,channelIds,channelNames;std::string logOptions,channelOptions,status="Open a datalog, then Refresh.",table,chart,comparisonChannel;
    struct Stats{double sum=0,min=std::numeric_limits<double>::infinity(),max=-std::numeric_limits<double>::infinity();uint64_t count=0;} stats;
    std::vector<double> batch;uint32_t rows=0,total=0;
    ReadCallback events{context,this},listing{list,this},fields{fieldList,this},data{readBatch,this},comparison{compareFields,this};
    et_async_options patched{sizeof(et_async_options),ET_OPERATION_TIMEOUT_MS,0,0,this,patchDone},done{sizeof(et_async_options),ET_OPERATION_TIMEOUT_MS,0,0,this,jobDone};
    static void ET_CALL patchDone(void* p,const et_completion* r)noexcept{try{auto& s=*static_cast<Analyzer*>(p);s.patching=false;if(r->code==ET_OK&&r->value.kind==ET_VALUE_U64)s.revision=std::max(s.revision,uint32_t(r->value.data.u64));if(s.dirty)s.render();}catch(...){}}
    void render(){if(!panel.slot)return;if(patching){dirty=true;return;}dirty=false;patching=ui.patch(panel,{Ui::text("status",ET_UI_TITLE,status),Ui::text("log",ET_UI_OPTIONS,logOptions),Ui::number("log",ET_UI_VALUE,logIndex),
        Ui::text("channel",ET_UI_OPTIONS,channelOptions),Ui::number("channel",ET_UI_VALUE,channelIndex),Ui::text("result",ET_UI_DATA,table),Ui::text("chart",ET_UI_DATA,chart)},revision,0,&patched)==ET_OK;}
    void cancel(){active=false;if(pending)logs.cancel(pending);if(job.slot)jobs.cancel(job);status="Cancelled";render();}
    void refresh(){if(active)cancel();auto r=Reads::make(ET_READ_LOGS,generation);logs.request(r,listing);}
    void channels(){if(ids.empty())return;auto r=Reads::make(ET_READ_LOG_CHANNELS,generation);r.id=string(ids[logIndex]);logs.request(r,fields);}
    static et_result ET_CALL context(void* p,const et_read_reply* r){auto& s=*static_cast<Analyzer*>(p);if(r->event==ET_READ_EVENT_PROJECT){if(s.generation!=r->project_generation){s.cancel();s.generation=r->project_generation;s.refresh();}}return ET_OK;}
    static et_result ET_CALL list(void* p,const et_read_reply* r){auto& s=*static_cast<Analyzer*>(p);if(r->code!=ET_OK||r->project_generation!=s.generation)return ET_OK;s.ids.clear();s.names.clear();s.logOptions.clear();s.logIndex=0;
        for(uint32_t i=0;i<r->item_count;++i){const auto name=copy(r->items[i].label);if(s.logOptions.size()+name.size()+1>3500)break;if(!s.logOptions.empty())s.logOptions+='\n';s.logOptions+=name;s.names.push_back(name);s.ids.push_back(copy(r->items[i].id));}
        s.status=s.ids.empty()?"Open a datalog in EpicTuner, then Refresh.":"Choose a channel and row range. Compare analyzes this and the next open log.";s.channels();s.render();return ET_OK;}
    static et_result ET_CALL fieldList(void* p,const et_read_reply* r){auto& s=*static_cast<Analyzer*>(p);if(r->code!=ET_OK||r->project_generation!=s.generation)return ET_OK;
        const auto selected=s.channelIndex>=0&&s.channelIndex<int(s.channelNames.size())?s.channelNames[s.channelIndex]:std::string{};
        s.channelIds.clear();s.channelNames.clear();s.channelOptions.clear();s.channelIndex=0;
        for(uint32_t i=0;i<r->item_count;++i){const auto name=copy(r->items[i].label);if(s.channelOptions.size()+name.size()+1>3500)break;if(!s.channelOptions.empty())s.channelOptions+='\n';s.channelOptions+=name;s.channelNames.push_back(name);s.channelIds.push_back(copy(r->items[i].id));}
        const auto found=std::find(s.channelNames.begin(),s.channelNames.end(),selected);if(found!=s.channelNames.end())s.channelIndex=int(found-s.channelNames.begin());s.render();return ET_OK;}
    void next(){if(!active)return;auto r=Reads::make(ET_READ_LOG_BATCH,generation);r.id=string(ids[(logIndex+pass)%ids.size()]);r.offset=offset;r.count=std::min<uint32_t>(ET_READ_MAX_ROWS,last-offset+1);auto channel=string(pass?comparisonChannel:channelIds[channelIndex]);r.channels=&channel;r.channel_count=1;
        const auto code=logs.request(r,data,&pending);if(code!=ET_OK){active=false;status="Read rejected: "+std::to_string(code);render();}}
    static et_result ET_CALL readBatch(void* p,const et_read_reply* r){auto& s=*static_cast<Analyzer*>(p);s.pending=0;if(!s.active)return ET_OK;if(r->code!=ET_OK||r->project_generation!=s.generation){s.active=false;s.status="Read cancelled or log changed";s.render();return ET_OK;}
        s.total=r->total;s.rows=r->item_count;s.batch.clear();for(uint32_t i=0;i<r->item_count;++i)if(r->items[i].flags&ET_READ_VALID)s.batch.push_back(r->items[i].value);
        if(s.jobs.submit(work,&s,s.done,s.job)!=ET_OK){s.active=false;s.status="Background analysis unavailable";s.render();}return ET_OK;}
    static et_result ET_CALL work(void* p,const et_job_control* control,et_job_output* out){auto& s=*static_cast<Analyzer*>(p);Stats result;
        for(double v:s.batch){if(control->is_cancelled(control->context))return ET_ERROR_CANCELLED;result.sum+=v;result.min=std::min(result.min,v);result.max=std::max(result.max,v);++result.count;}
        if(out->capacity<sizeof(result))return ET_ERROR_LIMIT;std::memcpy(out->data,&result,sizeof(result));out->size=sizeof(result);return ET_OK;}
    static et_result ET_CALL compareFields(void* p,const et_read_reply* r){auto& s=*static_cast<Analyzer*>(p);s.pending=0;if(!s.active)return ET_OK;
        if(r->code==ET_OK&&r->project_generation==s.generation)for(uint32_t i=0;i<r->item_count;++i)if(copy(r->items[i].label)==s.channelNames[s.channelIndex]){s.comparisonChannel=copy(r->items[i].id);s.next();return ET_OK;}
        s.active=false;s.status="Comparison log does not contain the selected channel";s.render();return ET_OK;}
    bool compare=false;
    static void ET_CALL jobDone(void* p,const et_completion* r)noexcept{try{auto& s=*static_cast<Analyzer*>(p);s.job={};if(!s.active)return;if(r->code!=ET_OK||r->value.kind!=ET_VALUE_BYTES||r->value.data.bytes.size!=sizeof(Stats)){s.active=false;s.status="Analysis cancelled";s.render();return;}
        Stats result;std::memcpy(&result,r->value.data.bytes.data,sizeof(result));s.stats.sum+=result.sum;s.stats.count+=result.count;s.stats.min=std::min(s.stats.min,result.min);s.stats.max=std::max(s.stats.max,result.max);s.offset+=s.rows;
        if(s.rows&&s.offset<s.total&&s.offset<=s.last){s.next();return;}
        const auto mean=s.stats.count?s.stats.sum/s.stats.count:0;const auto name=s.names[(s.logIndex+s.pass)%s.ids.size()];
        const auto row=name+"\t"+std::to_string(s.stats.count)+"\t"+std::to_string(mean)+"\t"+std::to_string(s.stats.count?s.stats.min:0)+"\t"+std::to_string(s.stats.count?s.stats.max:0);
        if(!s.table.empty())s.table+='\n';s.table+=row;s.chart+=std::to_string(mean)+"\n";s.host->log("analysis:"+std::to_string(s.stats.count)+":"+std::to_string(mean));
        if(s.compare&&s.pass==0&&s.ids.size()>1){s.pass=1;s.offset=s.first;s.stats={};auto request=Reads::make(ET_READ_LOG_CHANNELS,s.generation);request.id=string(s.ids[(s.logIndex+1)%s.ids.size()]);s.logs.request(request,s.comparison,&s.pending);return;}
        s.active=false;s.status="Analysis complete";s.render();}catch(...){} }
    void analyze(bool comparing){if(active||pending||job.slot||ids.empty()||channelIds.empty()||last<first)return;active=true;compare=comparing;pass=0;offset=first;stats={};table.clear();chart.clear();status="Analyzing bounded batches…";render();next();}
    static et_result ET_CALL input(void* p,const et_ui_event* e){auto& s=*static_cast<Analyzer*>(p);s.revision=e->revision;const auto node=copy(e->node);
        if(e->type==ET_UI_OPENED){s.panel=e->panel;s.refresh();}if(e->type==ET_UI_CLOSED){s.cancel();s.panel={};}
        if(e->type==ET_UI_EVENT_SELECTED&&e->value.kind==ET_VALUE_F64){const auto value=int(e->value.data.f64);if(s.active)s.cancel();if(node=="log"&&value>=0&&value<int(s.ids.size())){s.logIndex=value;s.channels();}if(node=="channel"&&value>=0&&value<int(s.channelIds.size()))s.channelIndex=value;}
        if(e->type==ET_UI_COMMIT&&e->value.kind==ET_VALUE_F64){const auto value=int(e->value.data.f64);if(s.active)s.cancel();if(node=="first")s.first=uint32_t(value);if(node=="last")s.last=uint32_t(value);}
        if(e->type==ET_UI_CLICK){if(node=="refresh")s.refresh();if(node=="analyze")s.analyze(false);if(node=="compare")s.analyze(true);if(node=="cancel")s.cancel();}return ET_OK;}
    et_result start(Context& c){host=std::make_unique<Context>(c);if(ui.connect(c,input,this)!=ET_OK||project.connect(c,ET_SERVICE_PROJECT)!=ET_OK||logs.connect(c,ET_SERVICE_DATALOG)!=ET_OK||jobs.connect(c)!=ET_OK)return ET_ERROR_STATE;project.listen(events);
        PanelBuilder b("org.epictuner.examples.analyzer.main","Log analyzer");b.definition.flags=0;b.definition.menu=b.keep("Examples/Log analyzer");b.add(ET_UI_COLUMN,"root");b.add(ET_UI_BANNER,"status","root").text(ET_UI_TITLE,status);
        b.add(ET_UI_SELECT,"log","root").text(ET_UI_TITLE,"Open log").text(ET_UI_OPTIONS,"").number(ET_UI_VALUE,0);b.add(ET_UI_SELECT,"channel","root").text(ET_UI_TITLE,"Channel").text(ET_UI_OPTIONS,"").number(ET_UI_VALUE,0);
        b.add(ET_UI_ROW,"range","root");b.add(ET_UI_NUMBER,"first","range").text(ET_UI_TITLE,"First row").number(ET_UI_MINIMUM,0).number(ET_UI_MAXIMUM,999999).number(ET_UI_VALUE,0);b.add(ET_UI_NUMBER,"last","range").text(ET_UI_TITLE,"Last row").number(ET_UI_MINIMUM,0).number(ET_UI_MAXIMUM,999999).number(ET_UI_VALUE,999999);
        b.add(ET_UI_ROW,"actions","root");for(const auto* action:{"refresh","analyze","compare","cancel"})b.add(ET_UI_BUTTON,action,"actions").text(ET_UI_TITLE,action);
        b.add(ET_UI_TABS,"views","root").number(ET_UI_VALUE,0);b.add(ET_UI_SECTION,"summary","views").text(ET_UI_TITLE,"Results");b.add(ET_UI_SECTION,"logview","views").text(ET_UI_TITLE,"Datalog browser");b.prefab(ET_UI_DATALOG_PANEL,"datalog",{},"logview");
        b.add(ET_UI_TABLE,"result","summary").text(ET_UI_OPTIONS,"Log\nCount\nMean\nMin\nMax").text(ET_UI_DATA,"");b.add(ET_UI_CHART,"chart","summary").text(ET_UI_DATA,"");return ui.define(b,nullptr);}
};
ET_PLUGIN(Analyzer)
