local floor=math.floor
local char,byte=string.char,string.byte
local ins=table.insert

local GAME=GAME

local DATA={}
-- Sep symbol: 33 (!)
-- Safe char: 34~126
--[[
    Count: 34~96
    Block: 97~125
    Encode: A[B] sequence, A = block ID, B = repeat times, no B means do not repeat.
    Example: "abcdefg" is [SZJLTOI], "a^aDb)" is [Z*63,Z*37,S*10]
]]
function DATA.copySequence(bag)
    local str=""

    local count=1
    for i=1,#bag+1 do
        if bag[i+1]~=bag[i] or count==64 then
            str=str..char(96+bag[i])
            if count>1 then
                str=str..char(32+count)
                count=1
            end
        else
            count=count+1
        end
    end

    return str
end
function DATA.pasteSequence(str)
    local bag={}
    local b,reg
    for i=1,#str do
        b=byte(str,i)
        if not reg then
            if b>=97 and b<=125 then
                reg=b-96
            else
                return
            end
        else
            if b>=97 and b<=125 then
                ins(bag,reg)
                reg=b-96
            elseif b>=34 and b<=96 then
                for _=1,b-32 do
                    ins(bag,reg)
                end
                reg=false
            end
        end
    end
    if reg then
        ins(bag,reg)
    end
    return true,bag
end

local fieldMeta={__index=function(self,h)
    for i=#self+1,h do
        self[i]={0,0,0,0,0,0,0,0,0,0}
    end
    return self[h]
end}
function DATA.newBoard(f)-- Generate a new board
    return setmetatable(f and TABLE.shift(f) or{},fieldMeta)
end
function DATA.copyBoard(F)-- Copy the [page] board
    local str=""

    -- Encode field
    for y=1,#F do
        local S=""
        local L=F[y]
        for x=1,10 do
            S=S..char(L[x]+1)
        end
        str=str..S
    end
    return STRING.packBin(str)
end
function DATA.copyBoards(field)
    local out={}
    for i=1,#field do
        out[i]=DATA.copyBoard(field[i])
    end
    return table.concat(out,"!")
end
function DATA.pasteBoard(str)-- Paste [str] data to [page] board
    local F=DATA.newBoard()

    -- Decode
    str=STRING.unpackBin(str)
    if not str then return end

    local fX,fY=1,1-- *ptr for Field(r*10+(c-1))
    local p=1
    local lineLimit=126
    while true do
        local b=byte(str,p)-- 1byte

        -- Str end
        if not b then
            if fX~=1 then
                return
            else
                break
            end
        end

        local id=b%32-1-- Block id
        if id>26 then return end-- Illegal blockid
        b=floor(b/32)-- Mode id

        F[fY][fX]=id
        if fX<10 then
            fX=fX+1
        else
            fY=fY+1
            if fY>lineLimit then break end
            fX=1
        end
        p=p+1
    end

    return true,F,#str>lineLimit*10
end

--[[
    Mission: 34~114
    Count: 115~126
    Encode: [A] or [AB] sequence, A = mission ID, B = repeat times, no B means do not repeat.

    _1=01,_2=02,_3=03,_4=04,
    A1=05,A2=06,A3=07,A4=08,
    PC=09,
    Z1=11,Z2=12,Z3=13,
    S1=21,S2=22,S3=23,
    J1=31,J2=32,J3=33,
    L1=41,L2=42,L3=43,
    T1=51,T2=52,T3=53,
    O1=61,O2=62,O3=63,O4=64,
    I1=71,I2=72,I3=73,I4=74,
]]
function DATA.copyMission(mission)
    local _
    local str=""

    local count=1
    for i=1,#mission+1 do
        if mission[i+1]~=mission[i] or count==13 then
            _=33+mission[i]
            str=str..char(_)
            if count>1 then
                str=str..char(113+count)
                count=1
            end
        else
            count=count+1
        end
    end

    return str
end
function DATA.pasteMission(str)
    local b
    local mission={}
    local reg
    for i=1,#str do
        b=byte(str,i)
        if not reg then
            if b>=34 and b<=114 then
                reg=b-33
            else
                return
            end
        else
            if b>=34 and b<=114 then
                if ENUM_MISSION[reg] then
                    ins(mission,reg)
                    reg=b-33
                else
                    TABLE.cut(mission)
                    return
                end
            elseif b>=115 and b<=126 then
                for _=1,b-113 do
                    ins(mission,reg)
                end
                reg=false
            end
        end
    end
    if reg then
        ins(mission,reg)
    end
    return true,mission
end

function DATA.copyQuestArgs(custom_env)
    local ENV=custom_env
    local str=""..
        ENV.holdCount..
        (ENV.ospin and "O" or "Z")..
        (ENV.missionKill and "M" or "Z")..
        ENV.sequence
    return str
end
function DATA.pasteQuestArgs(str)
    if #str<4 then return end
    local ENV={}
    ENV.holdCount=  MATH.clamp(str:byte(1)-48,0,26)
    ENV.ospin=      str:byte(2)~=90
    ENV.missionKill=str:byte(3)~=90
    ENV.sequence=   str:sub(4)
    if select(2,require"parts.player.seqGenerators"(ENV.sequence)) then
        MES.new('warn',text.invalidSequence)
        ENV.sequence='bag'
    end
    return true,ENV
end

local function _encode(t)
    if t<128 then return char(t) end
    local buffer2=char(t%128)
    t=floor(t/128)
    while t>=128 do
        buffer2=char(128+t%128)..buffer2
        t=floor(t/128)
    end
    return char(128+t)..buffer2
end
local function _decode(str,p)
    local ret=0
    repeat
        local b=byte(str,p)
        p=p+1
        ret=ret*128+(b<128 and b or b-128)
    until b<128
    return ret,p
end
--[[
    Replay file:
    a zlib-compressed json table

    Replay data format (table):
        {frame,event, frame,event, ...}

    Replay data format (byte): (1 byte each period)
        dt, event, dt, event, ...
    all data range from 0 to 127
    large value will be encoded as 1xxxxxxx(high)-1xxxxxxx-...-0xxxxxxx(low)

    Example (decoded):
        26,1, 42,-1, ...
    This means:
        Press key1 at 26f
        Release key1 at 42f
        ...
]]
function DATA.dumpRecording(list,ptr)
    local out=""
    local buffer=""
    if not ptr then ptr=1 end
    while list[ptr] do
        if #buffer>26 then
            out=out..buffer
            buffer=""
        end
        buffer=buffer.._encode(list[ptr])
        ptr=ptr+1
    end
    return out..buffer,ptr
end
function DATA.pumpRecording(str,L)
    local len=#str
    local p=1
    local data

    while p<=len do
        data,p=_decode(str,p)
        ins(L,data)
    end
end
do-- function DATA.saveReplay()
    local function _getModList()
        local res={}
        for number,sel in next,GAME.mod do
            if sel>0 then
                ins(res,{MODOPT[number].no,sel})
            end
        end
        return res
    end
    function DATA.saveReplay()
        -- Filtering modes that cannot be saved
        if GAME.initPlayerCount~=1 then
            MES.new('error',"Cannot save recording of more than 1 player now!")
            return
        end

        -- Write file
        local fileName=os.date("replay/%Y_%m_%d_%H%M%S.rep")
        if not love.filesystem.getInfo(fileName) then
            local metadata={
                date=os.date("%Y/%m/%d %H:%M:%S"),
                mode=GAME.curModeName,
                version=VERSION.string,
                player=USERS.getUsername(USER.uid),
                seed=GAME.seed,
                setting=GAME.setting,
                mod=_getModList(),
                tasUsed=GAME.tasUsed,
            }
            if GAME.curMode.savePrivate then
                metadata.private=GAME.curMode.savePrivate()
            end
            love.filesystem.write(fileName,
                love.data.compress('string','zlib',
                    JSON.encode(metadata).."\n"..
                    DATA.dumpRecording(GAME.rep)
                )
            )
            ins(REPLAY,1,DATA.parseReplay(fileName))
            return true
        else
            MES.new('error',"Save failed: File already exists")
        end
    end
end
function DATA.parseReplay(fileName,ifFull)
    local fileData
    -- Read file
    fileData=love.filesystem.read(fileName)
    return DATA.parseReplayData(fileName,fileData,ifFull)
end
function DATA.parseReplayData(fileName,fileData,ifFull)
    local success,metaData
    local rep={-- unavailable replay object
        fileName=fileName,
        available=false,
    }

    if not (fileData and #fileData>0) then return rep end-- goto BREAK_cannotParse

    -- Decompress file
    success,fileData=pcall(love.data.decompress,'string','zlib',fileData)
    if not success then return rep end-- goto BREAK_cannotParse

    -- Load metadata
    metaData,fileData=STRING.readLine(fileData)
    metaData=JSON.decode(metaData)
    if not metaData then return rep end-- goto BREAK_cannotParse

    -- Convert ancient replays
    metaData.mode=MODE_UPDATE_MAP[metaData.mode] or metaData.mode
    if not MODES[metaData.mode] then return rep end-- goto BREAK_cannotParse

    -- Create replay object
    rep={
        fileName=fileName,
        available=true,

        date=metaData.date,
        mode=metaData.mode,
        version=metaData.version,
        player=metaData.player,

        seed=metaData.seed,
        setting=metaData.setting,
        mod=metaData.mod,
        tasUsed=metaData.tasUsed,
    }
    if ifFull then rep.data=fileData end
    if metaData.private then
        rep.private=metaData.private
    end
    return rep
end
return DATA
