local ms=love.mouse
local msIsDown,kbIsDown=ms.isDown,love.keyboard.isDown
local gc=love.graphics
local gc_setColor,gc_rectangle,gc_draw=gc.setColor,gc.rectangle,gc.draw
local setFont,mStr=FONT.set,GC.mStr

local floor,rnd,abs=math.floor,math.random,math.abs
local max,min=math.max,math.min
local ins,rem=table.insert,table.remove

local levels={
    {c=6,r=3,color=3},
    {c=6,r=4,color=3},
    {c=6,r=4,color=4},
    {c=8,r=5,color=4},
    {c=8,r=6,color=5},
    {c=9,r=6,color=5},
    {c=10,r=6,color=6},
    {c=12,r=7,color=6},
    {c=14,r=8,color=7},
    {c=14,r=9,color=7},
    {c=15,r=10,color=7},
    {c=17,r=10,color=8},
    {c=18,r=11,color=8},
    {c=20,r=12,color=9},
    {c=22,r=13,color=9},
    {c=24,r=14,color=9},
}
local colorList={
    COLOR.lR,
    COLOR.lB,
    COLOR.lY,
    COLOR.lG,
    COLOR.lM,
    COLOR.lC,
    COLOR.H,
    COLOR.Z,
    COLOR.lF,
}
gc.setDefaultFilter('nearest','nearest')
local iconList={
    GC.DO{10,10,{'fRect',2,2,6,6}},
    GC.DO{10,10,{'dRect',2.5,2.5,5,5}},
    GC.DO{10,10,{'fCirc',5,5,2}},
    GC.DO{10,10,{'fRect',2,2,2,6},{'fRect',6.5,2,2,6}},
    GC.DO{10,10,{'fRect',2,2,1,1},{'fRect',3,3,1,1},{'fRect',4,4,1,1},{'fRect',5,5,1,1},{'fRect',6,6,1,1},{'fRect',7,7,1,1}},
    GC.DO{10,10,{'fRect',2,2,2,2},{'fRect',2,6,2,2},{'fRect',6,2,2,2},{'fRect',6,6,2,2}},
    GC.DO{1,1},
    GC.DO{10,10,{'fRect',2,2,1,6},{'fRect',3,2,1,5},{'fRect',4,2,1,4},{'fRect',5,2,1,3},{'fRect',6,2,1,2},{'fRect',7,2,1,1}},
    GC.DO{10,10,{'fRect',2,5,3,3},{'fRect',5,2,3,3}},
}
gc.setDefaultFilter('linear','linear')

local invis
local state
local startTime,time
local progress,level
local score,score1
local combo,comboTime,maxCombo,noComboBreak
local field={
    x=160,y=40,
    w=960,h=640,
    c=16,r=10,
    remain=0,
}
local lines={}
local selX,selY

local function resetBoard()
    selX,selY=false,false

    local colors=levels[level].color
    field.c,field.r=levels[level].c,levels[level].r

    local total=field.r*field.c/2-- Total cell count
    local pool=TABLE.new(floor(total/colors),colors)
    for i=1,total%colors do pool[i]=pool[i]+1 end
    for i=1,#pool do pool[i]=pool[i]*2 end
    field.remain=total
    field.full=true
    total=total*2

    TABLE.cut(field)
    for y=1,field.r do
        field[y]={}
        for x=1,field.c do
            local ri=0
            local rn=rnd(total)
            while rn>0 do
                ri=ri+1
                rn=rn-pool[ri]
            end
            total=total-1
            pool[ri]=pool[ri]-1
            field[y][x]=ri
        end
    end

    noComboBreak=true
    comboTime=comboTime+2
    SYSFX.newShade(2,field.x,field.y,field.w,field.h,1,1,1)
end
local function newGame()
    state=0
    level=1
    progress={}
    time=0
    startTime=TIME()
    score,score1,combo,comboTime,maxCombo=0,0,0,0,0
    resetBoard()
end
local function addPoint(list,x,y)
    local l=#list
    if x~=list[l-1] or y~=list[l] then
        list[l+1]=x
        list[l+2]=y
    end
end
local function checkLink(x1,y1,x2,y2)
    -- Y-X-Y Check
    local bestLen,bestLine=1e99,false
    do
        if x1>x2 then x1,y1,x2,y2=x2,y2,x1,y1 end
        local luy,ldy,ruy,rdy=y1,y1,y2,y2
        while luy>1       and not field[luy-1][x1] do luy=luy-1 end
        while ldy<field.r and not field[ldy+1][x1] do ldy=ldy+1 end
        while ruy>1       and not field[ruy-1][x2] do ruy=ruy-1 end
        while rdy<field.r and not field[rdy+1][x2] do rdy=rdy+1 end
        for y=max(luy,ruy),min(ldy,rdy) do
            local nextLine
            for x=x1+1,x2-1 do if field[y][x] then nextLine=true break end end-- goto CONTINUE_nextRow
            if not nextLine then
                local len=abs(x1-x2)+abs(y-y1)+abs(y-y2)
                if len<bestLen then
                    bestLen=len
                    bestLine={x1,y1}
                    addPoint(bestLine,x1,y)
                    addPoint(bestLine,x2,y)
                    addPoint(bestLine,x2,y2)
                end
            end
            -- ::CONTINUE_nextRow::
        end
    end
    -- X-Y-X Check
    do
        if y1>y2 then x1,y1,x2,y2=x2,y2,x1,y1 end
        local ulx,urx,dlx,drx=x1,x1,x2,x2
        while ulx>1       and not field[y1][ulx-1] do ulx=ulx-1 end
        while urx<field.c and not field[y1][urx+1] do urx=urx+1 end
        while dlx>1       and not field[y2][dlx-1] do dlx=dlx-1 end
        while drx<field.c and not field[y2][drx+1] do drx=drx+1 end
        for x=max(ulx,dlx),min(urx,drx) do
            local nextLine
            for y=y1+1,y2-1 do if field[y][x] then nextLine=true break end end-- goto CONTINUE_nextCol
            if not nextLine then
                local len=abs(y1-y2)+abs(x-x1)+abs(x-x2)
                if len<bestLen then
                    bestLen=len
                    bestLine={x1,y1}
                    addPoint(bestLine,x,y1)
                    addPoint(bestLine,x,y2)
                    addPoint(bestLine,x2,y2)
                end
            end
            -- ::CONTINUE_nextCol::
        end
    end
    return bestLine
end
local function tap(x,y)
    if x>=1 and x<=field.c and y>=1 and y<=field.r then
        if state==0 then
            state=1
            startTime=TIME()
        elseif state==1 then
            if selX and (x~=selX or y~=selY) and field[y][x]==field[selY][selX] then
                local line=checkLink(x,y,selX,selY)
                if line then
                    ins(lines,{time=0,line=line})

                    -- Clear
                    field[y][x]=false
                    field[selY][selX]=false
                    field.remain=field.remain-1
                    field.full=false

                    -- Score
                    local s=1000+floor(combo^.9)
                    score=score+s
                    TEXT.show("+"..s,1205,600,20,'score')

                    -- Combo
                    if comboTime==0 then
                        combo=0
                        noComboBreak=false
                    end
                    comboTime=comboTime*max(1-combo*.001,.95)+max(1-combo*.01,.8)
                    combo=combo+1
                    if combo>maxCombo then maxCombo=combo end

                    -- Check win
                    if field.remain==0 then
                        if noComboBreak then
                            SFX.play('emit')
                            SFX.play('clear_4')
                            TEXT.show("FULL COMBO",640,360,100,'beat',.626)
                            comboTime=comboTime+3
                            score=floor(score*1.1)
                        end
                        ins(progress,
                            noComboBreak and
                            ("%s [FC] %.2fs"):format(level,TIME()-startTime) or
                            ("%s - %.2fs"):format(level,TIME()-startTime)
                        )
                        level=level+1
                        if levels[level] then
                            resetBoard()
                            SFX.play('reach')
                        else
                            state=2
                            SFX.play('win')
                        end
                    else
                        SFX.play(
                            combo<50 and 'clear_1' or
                            combo<100 and 'clear_2' or
                            'clear_3',.8
                        )
                    end
                    selX,selY=false,false
                else
                    selX,selY=x,y
                    SFX.play('lock',.9)
                end
            else
                if field[y][x] and (x~=selX or y~=selY) then
                    selX,selY=x,y
                    SFX.play('lock',.8)
                end
            end
        end
    end
end

local scene={}

function scene.enter()
    invis=false
    newGame()
    BGM.play('truth')
    DiscordRPC.update("Playing Link")
end

function scene.keyDown(key,isRep)
    if isRep then return end
    if key=='r' then
        if state~=1 or tryReset() then
            newGame()
        end
    elseif key=='z' or key=='x' then
        love.mousepressed(ms.getPosition())
    elseif key=='escape' then
        if state~=1 or tryBack() then
            SCN.back()
        end
    elseif state==0 then
        if key=='q' then
            invis=not invis
        end
    end
end
local function touch(x,y)
    x=floor((x-field.x)/field.w*field.c+1)
    y=floor((y-field.y)/field.h*field.r+1)
    tap(x,y)
end
function scene.mouseDown(x,y,k) if k==1 or k==2 or not k then touch(x,y) end end
function scene.mouseMove(x,y) if (msIsDown(1) or kbIsDown('z','x')) then touch(x,y) end end
function scene.touchDown(x,y) touch(x,y) end
function scene.touchMove(x,y) touch(x,y) end

function scene.update(dt)
    if state==1 then
        time=TIME()-startTime
        comboTime=max(comboTime-dt,0)
        score1=score1+MATH.sign(score-score1)+floor((score-score1)*.1+.5)
    end

    for i=#lines,1,-1 do
        local L=lines[i]
        L.time=L.time+dt
        if L.time>=1 then
            rem(lines,i)
        end
    end
end

function scene.draw()
    gc.push('transform')
        -- Camera
        gc.translate(field.x,field.y)

        -- Background
        gc.setColor(COLOR.dX)
        gc.rectangle('fill',0,0,field.w,field.h)

        gc.scale(field.w/field.c,field.h/field.r)

        -- Matrix
        local mono=state==0 or invis and not field.full
        if mono then
            gc_setColor(COLOR.dH)
            for y=1,field.r do
                for x=1,field.c do
                    if field[y][x] then
                        gc_rectangle('fill',x-1,y-1,1,1)
                    end
                end
            end
        else
            for y=1,field.r do
                for x=1,field.c do
                    local t=field[y][x]
                    if t then
                        gc_setColor(colorList[t])
                        gc_rectangle('fill',x-1,y-1,1,1)
                        gc_setColor(0,0,0,.26)
                        gc_draw(iconList[t],x-1,y-1,nil,.1,.1)
                    end
                end
            end
        end

        -- Selecting box
        gc.setLineWidth(.1)
        if selX then
            gc_setColor(1,1,1)
            gc_rectangle('line',selX-1+.05,selY-1+.05,.9,.9)
        end

        -- Clearing lines
        gc.translate(-.5,-.5)
        for i=1,#lines do
            gc_setColor(1,1,1,1-lines[i].time)
            gc.line(lines[i].line)
        end
    gc.pop()
    -- Frame

    if state==2 then
        gc.setColor(.9,.9,0)-- win
    elseif state==1 then
        gc.setColor(.9,.9,.9)-- game
    elseif state==0 then
        gc.setColor(.2,.8,.2)-- ready
    end
    gc.setLineWidth(6)
    gc.rectangle('line',field.x-5,field.y-5,field.w+10,field.h+10)

    -- Draw no-setting area
    if state==2 then
        gc.setColor(1,0,0,.3)
        gc.rectangle('fill',0,100,155,80)
    end

    -- Maxcombo
    setFont(20)gc.setColor(COLOR.dF)
    gc.print(maxCombo,1142,1)

    -- Time
    setFont(30)gc.setColor(COLOR.Z)
    gc.print(("%.3f"):format(time),1140,20)

    -- Progress time list
    setFont(15)gc.setColor(.6,.6,.6)
    for i=1,#progress do gc.print(progress[i],1140,40+20*i) end

    -- Combo Rectangle
    if comboTime>0 then
        local r=32*comboTime^.3
        gc.setColor(1,1,1,min(.6+comboTime,1)*.25)
        gc.rectangle('fill',1205-r,440-r,2*r,2*r,2)
        gc.setColor(1,1,1,min(.6+comboTime,1))
        gc.setLineWidth(2)
        gc.rectangle('line',1205-r,440-r,2*r,2*r,4)
    end

    -- Combo Text
    setFont(60)
    if combo>50 then
        gc.setColor(1,.2,.2,min(.3+comboTime*.5,1)*min(comboTime,1))
        mStr(combo,1205+(rnd()-.5)*combo^.5,398+(rnd()-.5)*combo^.5)
    end
    gc.setColor(1,1,max(1-combo*.001,.5),min(.4+comboTime,1))
    mStr(combo,1205,398)

    -- Score
    setFont(25)gc.setColor(COLOR.Z)
    mStr(score1,1205,560)
end

scene.widgetList={
    WIDGET.newButton{name='reset',x=80,y=60,w=110,h=60,color='lG',fText=CHAR.icon.retry_spin,code=pressKey'r',hideF=function() return state==0 end},
    WIDGET.newSwitch{name='invis',x=100,y=140,lim=80,disp=function() return invis end,code=pressKey'q',hideF=function() return state==1 end},
    WIDGET.newButton{name='back',x=1200,y=660,w=110,font=45,sound='back',fText=CHAR.icon.back,code=pressKey'escape'},
}

return scene
