--!name Reading Log
--!icon book
--!category Tools
local s=120 local Z={"January","February","March","April","May","June","July","August","September","October","November","December"}local U={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}local y,z={},{}local r,C=0,0 local v,V,W,G="m",20,20,1 local H,I,B,w=0,0,0,0 local k,e,m,aq="today",1,0,"week"local function E(q,af,L)if q<af then return af end if q>L then return L end return q end local function am(d)local a=os.date("*t",d)return os.time({year=a.year,month=a.month,day=a.day,hour=12})//86400 end local function M()local d=os.time()if not d or d<1700000000 then return nil end return am(d)end local function N(a)local d=a*86400+43200 return os.date("*t",d+(a-am(d))*86400)end local function A()return M()or r end local function R(a)local c=a-r+s if c>=1 and c<=s then return c end end local function K()return v=="p"and W or V end local function aE()return v=="p"and"pages"or"min"end local function O(a)local c=R(a);return c and y[c]or 0 end local function P(a)local c=R(a);return c and z[c]or 0 end local function ad(a)return v=="p"and P(a)or O(a)end local function J(a)return ad(a)>=K()end local function ac(a)return O(a)>0 or P(a)>0 end local function aB(a)local d=N(a)return U[d.wday]:sub(1,3).." "..d.day.." "..Z[d.month]:sub(1,3)end local function ab(a)return(N(a).wday-1-G)%7 end local function x(f)if f<60 then return f.." min"end if f%60==0 then return f//60 .." h"end return f//60 .." h "..f%60 .." min"end local function o(j,a0)return j.." "..a0..(j==1 and""or"s")end local function aD()local a=A()if not J(a)then a=a-1 end local i=0 while R(a)and J(a)do i,a=i+1,a-1 end if i>0 and not R(a)and a<r then i=i+C end return i end local function aw()local D,i=C,C for a=r-s+1,r do if J(a)then i=i+1 if i>D then D=i end else i=0 end end return D end local function ap(d)local h={}for c=1,s do h[c]=string.char(48+d[c]//64,48+d[c]%64)end return table.concat(h)end local function an(i,d)for c=1,s do local _,D=(i or""):byte(c*2-1,c*2)d[c]=(_ and D)and E((_-48)*64+D-48,0,4095)or 0 end end local function Y()snail.save(table.concat({"R2",r,v,V,W,G,H,I,B,w,C,ap(y),ap(z)},"|"))end local function aZ()local i=snail.load()if type(i)~="string"or i:sub(1,3)~="R2|"then return end local Q={}for q in(i.."|"):gmatch("([^|]*)|")do Q[#Q+1]=q end local j=function(c,af,L,aL)return E(tonumber(Q[c])or aL,af,L)end r=j(2,0,1e7,0)v=Q[3]=="p"and"p"or"m"V,W,G=j(4,5,600,20),j(5,5,500,20),j(6,0,1,1)H,I,B=j(7,0,1e9,0),j(8,0,1e9,0),j(9,0,1e6,0)w,C=j(10,0,1e6,0),j(11,0,1e6,0)an(Q[12],y)an(Q[13],z)end local function aA(a)local b=a-r if b<=0 then return end if r>0 then for c=1,math.min(b,s)do C=((v=="p"and z[c]or y[c])>=K())and C+1 or 0 end if b>s then C=0 end end for c=1,s do local ae=c+b y[c],z[c]=ae<=s and y[ae]or 0,ae<=s and z[ae]or 0 end r=a end local function a2(a,f,l)local c=R(a)if not c then return end f,l=E(f,0,4095),E(l,0,4095)local aF=ac(a)H=math.max(0,H+f-y[c])I=math.max(0,I+l-z[c])y[c],z[c]=f,l if ac(a)~=aF then B=math.max(0,B+(aF and-1 or 1))end w=math.max(w,aw())Y()end local function S(_,D)local f,l,p,t=0,0,0,0 for a=_,D do f,l=f+O(a),l+P(a)if ac(a)then p=p+1 end if J(a)then t=t+1 end end return f,l,p,t end local aJ={"min","pg","week","cal","stats","set"}local aI={"week","cal","stats","set"}local aH={week="This week",cal="Calendar",stats="Totals and averages",set="Goal and settings"}local function aj()return M()and aJ or aI end local function al(a)return aB(a)..":  "..x(O(a))..",  "..o(P(a),"page")..(J(a)and"  - goal met"or"")end local function ao(a)snail.row("Minutes   "..O(a),e==1)snail.row("Pages   "..P(a),e==2)local q,t=ad(a),K()if q>=t then snail.small("Goal met: "..q.." of "..t.." "..aE()..". Nice reading!")else snail.small(t-q.." "..aE().." to go to reach the goal of "..t..".")end local j=math.min(10,q*10//t)snail.board(string.rep("#",j)..string.rep("o",10-j),"small")end local function a3()local i=aD()if i==0 then return"Meet the goal today to start a streak. Best: "..o(w,"day").."."end local d="Streak: "..o(i,"day")..". Best: "..o(math.max(w,i),"day").."."if not J(A())then d=d.." Read today to keep it going."end return d end local function az()snail.status("The clock isn't set")snail.text("Reading Log needs today's date. Connect to Wi-Fi once so the clock gets set, then come back.")snail.small("Nothing you've logged is lost.")end local function aR()snail.title("Reading Log")local n,g=aj(),M()if g then local d=N(g)snail.status(U[d.wday]..", "..d.day.." "..Z[d.month])ao(g)snail.small(a3())local f,l=S(g-ab(g),g)snail.small("This week so far: "..x(f)..", "..o(l,"page")..".")else az()end snail.gap()for b=g and 3 or 1,#n do snail.row(aH[n[b]],e==b)end local p=n[e]if p=="min"then snail.hint("BACK leave OK +15 L-5 R+5 UP/DN move")elseif p=="pg"then snail.hint("BACK leave OK +10 L-1 R+1 UP/DN move")else snail.hint("BACK leave OK open UP/DN move")end end local function aN()snail.title(aB(m))ao(m)snail.gap()snail.small("For a day you forgot to log. Streaks and totals update too.")snail.hint(e==1 and"BACK done OK +15 L-5 R+5 UP/DN move"or"BACK done OK +10 L-1 R+1 UP/DN move")end local function a5(u)local T,ai=K(),10 for a=u,u+6 do T=math.max(T,ad(a))end local ar=(K()*ai+T//2)//T local n={}for ag=ai,1,-1 do local h={}for b=0,6 do local q=ad(u+b)local as=q>0 and math.max(1,(q*ai+T-1)//T)or 0 local d=ag==ar and"g"or"."if ag<=as then local f=q>=K()if ag==as then d=f and"c"or"i"else d=f and"b"or"h"end end h[#h+1]=d if b<6 then h[#h+1]=ag==ar and"g"or"."end end n[#n+1]=table.concat(h)end local h={}for b=0,6 do local at=U[(b+G)%7+1]:sub(1,1)h[#h+1]=u+b==m and"{"..at.."}"or at if b<6 then h[#h+1]="."end end n[#n+1]=table.concat(h)return table.concat(n,"/")end local function aS()local u=m-ab(m)local d=N(u)snail.title("Week of "..d.day.." "..Z[d.month])snail.status(al(m))local f,l,ak,t=S(u,math.min(u+6,A()))snail.small(x(f).." and "..o(l,"page").." this week. Goal met on "..t.." of 7 days.")snail.board(a5(u))snail.hint("BACK back OK fix day L/R day UP/DN week")end local aG={n="u",a="v",f="w"}local function aM()local d=N(m)local F=m-d.day+1 local ay=os.date("*t",os.time({year=d.year,month=d.month+1,day=0,hour=12})).day local au=math.min(F+ay-1,A())snail.title(Z[d.month].." "..d.year)snail.status(al(m))local f,l,p,t=S(F,au)snail.small("Read on "..o(p,"day")..", goal met on "..t..". "..x(f)..", "..o(l,"page")..".")local n,av={},ab(F)local h={}for b=0,6 do h[b+1]=U[(b+G)%7+1]:sub(1,1)end n[1]=table.concat(h)for a4=0,(av+ay-1)//7 do h={}for b=0,6 do local a=F+a4*7+b-av local aa="."if a>=F and a<=au and a>r-s then aa=J(a)and"f"or ac(a)and"a"or"n"if a==m then aa=aG[aa]end end h[b+1]=aa end n[#n+1]=table.concat(h)end snail.board(table.concat(n,"/"))snail.hint("BACK back OK fix day L/R day UP/DN week")end local function aQ()local g=A()snail.title("Totals and averages")local u=g-ab(g)local f,l,p=S(u,g)local j=g-u+1 snail.text("This week: "..x(f)..", "..o(l,"page"))snail.small("Read on "..p.." of "..o(j,"day")..", "..x(f//j).." a day on average")local F=g-N(g).day+1 f,l,p=S(F,g)j=g-F+1 snail.text("This month: "..x(f)..", "..o(l,"page"))snail.small("Read on "..p.." of "..o(j,"day")..", "..x(f//j).." a day on average")local ak,ak,a1,aT=S(g-29,g)snail.text("Last 30 days: read on "..a1..", goal met on "..aT)snail.text("All time: "..x(H)..", "..o(I,"page"))if B>0 then snail.small("Over "..o(B,"reading day")..": "..x(H//B).." and "..o(I//B,"page").." a day")end local i=aD()snail.text("Streak: "..o(i,"day")..". Best: "..o(math.max(w,i),"day")..".")snail.hint("BACK back OK back")end local function aP()snail.title("Goal and settings")snail.row("Daily goal   "..K()..(v=="p"and" pages"or" minutes"),e==1)snail.row("Goal counts   "..(v=="p"and"pages"or"minutes"),e==2)snail.row("Weeks start on   "..U[G+1],e==3)snail.row("Erase the whole log",e==4)snail.gap()snail.small("A streak is the days in a row you meet the goal.")snail.small("Changing the goal rescores the days already logged.")snail.hint(e==4 and"BACK back OK erase UP/DN move"or"BACK back L/R change UP/DN move")end local function aO()snail.title("Erase the log?")snail.text("This clears every day you've logged and the all-time totals. It can't be undone.")snail.gap()snail.row("Keep my log",e==1)snail.row("Erase everything",e==2)snail.hint("BACK back OK choose UP/DN move")end function draw()if(k=="week"or k=="cal"or k=="stats")and A()==0 then snail.title("Reading Log")az()snail.hint("BACK back")elseif k=="day"then aN()elseif k=="week"then aS()elseif k=="cal"then aM()elseif k=="stats"then aQ()elseif k=="set"then aP()elseif k=="erase"then aO()else aR()end end local function aC(a,b)local f,l=O(a),P(a)if e==1 then f=f+(b=="ok"and 15 or b=="right"and 5 or-5)else l=l+(b=="ok"and 10 or b=="right"and 1 or-1)end a2(a,f,l)end local function ah(h,j,b)if b=="up"then return h>1 and h-1 or j end return h<j and h+1 or 1 end local function X(aU)k="today"for b,q in ipairs(aj())do if q==aU then e=b end end end local function ax(aK)local L=A()m=E(m+aK,math.min(r-s+1,L),L)end local function aX(b)local n=aj()e=E(e,1,#n)if b=="up"or b=="down"then e=ah(e,#n,b);return end local p=n[e]if p=="min"or p=="pg"then if b~="top"then aC(M(),b)end elseif b=="ok"then k,e,m=p,1,A()end end local function aW(b)if b=="up"or b=="down"then e=ah(e,4,b);return end if b=="top"then X("set");return end local a=b=="right"and 1 or b=="left"and-1 or 0 if e==1 and a~=0 then if v=="p"then W=E(W+5*a,5,500)else V=E(V+5*a,5,600)end elseif e==2 and a~=0 then v=v=="p"and"m"or"p"elseif e==3 and a~=0 then G=1-G elseif e==4 and b=="ok"then k,e="erase",1;return else return end w=math.max(w,aw())Y()end local function aV(b)if b=="up"or b=="down"then e=ah(e,2,b);return end if b=="ok"and e==2 then for c=1,s do y[c],z[c]=0,0 end H,I,B,w,C=0,0,0,0,0 Y()X("min")elseif b=="ok"or b=="top"then k,e="set",4 end end local function aY(b)if b=="top"then X(k)elseif b=="left"or b=="right"then ax(b=="right"and 1 or-1)elseif b=="up"or b=="down"then ax(b=="down"and 7 or-7)elseif b=="ok"and R(m)and m<=A()then if m==M()then X("min")else aq,k,e=k,"day",1 end end end function key(b)local g=M()if g and g>r then local a_=m==r aA(g)Y()if a_ then m=g end end if k=="today"then aX(b)elseif k=="set"then aW(b)elseif k=="erase"then aV(b)elseif k=="day"then if b=="top"then k,e=aq,1 elseif b=="up"or b=="down"then e=ah(e,2,b)else aC(m,b)end elseif k=="week"or k=="cal"then aY(b)elseif b=="top"or b=="ok"then X("stats")end end function start()for c=1,s do y[c],z[c]=0,0 end aZ()local g=M()if g and g>r then aA(g);Y()end m=A()end
