gShowBounds = false
gUseBlurInTransitions = false

gPath = "resources/"

function load_file(file)
    local prev_path = package.path
    package.path = package.path .. ";" .. gPath .. file .. ".lua"
    require(file)
    package.path = prev_path
end

load_file("slides_utils")

gSlides = parse_file(io.open("resources/slides_content2.lua", "r"))

function make_rect(l, t, r, b)
    return { left = l, top = t, right = r, bottom = b }
end

function make_paint(typefacename, style, size, color)
    local paint = Sk.newPaint();
    paint:setAntiAlias(true)
    paint:setSubpixelText(true)
    paint:setTypeface(Sk.newTypeface(typefacename, style))
    paint:setTextSize(size)
    paint:setColor(color)
    return paint
end

function draw_bullet(canvas, x, y, paint, indent)
    if 0 == indent then
        return
    end
    local ps = paint:getTextSize()
    local cx = x - ps * .8
    local cy = y - ps * .4
    local radius = ps * .2
    canvas:drawCircle(cx, cy, radius, paint)
end

function stroke_rect(canvas, rect, color)
    local paint = Sk.newPaint()
    paint:setStroke(true);
    paint:setColor(color)
    canvas:drawRect(rect, paint)
end

function drawSlide(canvas, slide, master_template)

    if #slide == 1 then
        template = master_template.title
        canvas:drawText(slide[1].text, 320, 240, template[1])
        return
    end

    template = master_template.slide

    local x = template.margin_x
    local y = template.margin_y
    local scale = 1.25

    if slide.blockstyle == "code" then
        local paint = master_template.codePaint
        local fm = paint:getFontMetrics()
        local height = #slide * (fm.descent - fm.ascent)
        y = (480 - height) / 2
        for i = 1, #slide do
            local node = slide[i]
            y = y - fm.ascent * scale
            canvas:drawText(node.text, x, y, paint)
            y = y + fm.descent * scale
        end
        return
    end

    for i = 1, #slide do
        local node = slide[i]
        local paint = template[node.indent + 1].paint
        local extra_dy = template[node.indent + 1].extra_dy
        local fm = paint:getFontMetrics()
        local x_offset = -fm.ascent * node.indent * 1.25

        local bounds = make_rect(x + x_offset, y, 620, 640)
        local blob, newBottom = Sk.newTextBlob(node.text, bounds, paint)
        draw_bullet(canvas, x + x_offset, y - fm.ascent, paint, node.indent)
        canvas:drawTextBlob(blob, 0, 0, paint)
        y = newBottom + paint:getTextSize() * .5 + extra_dy

        if gShowBounds then
            bounds.bottom = newBottom
            stroke_rect(canvas, bounds, {a=1,r=0,g=1,b=0})
            stroke_rect(canvas, blob:bounds(), {a=1,r=1,g=0,b=0})
        end

    end
end

--------------------------------------------------------------------------------------
function make_tmpl(paint, extra_dy)
    return { paint = paint, extra_dy = extra_dy }
end

function SkiaPoint_make_template()
    normal = Sk.newFontStyle()
    bold = Sk.newFontStyle(700)
    local title = {
        margin_x = 30,
        margin_y = 100,
    }
    title[1] = make_paint("Arial", bold, 45, { a=1, r=1, g=1, b=1 })
    title[1]:setTextAlign("center")
    title[2] = make_paint("Arial", bold, 25, { a=1, r=.75, g=.75, b=.75 })
    title[2]:setTextAlign("center")

    local slide = {
        margin_x = 20,
        margin_y = 25,
    }
    slide[1] = make_tmpl(make_paint("Arial", bold, 35, { a=1, r=1, g=1, b=1 }), 18)
    slide[2] = make_tmpl(make_paint("Arial", normal, 25, { a=1, r=1, g=1, b=1 }), 10)
    slide[3] = make_tmpl(make_paint("Arial", normal, 20, { a=1, r=.9, g=.9, b=.9 }), 5)

    return {
        title = title,
        slide = slide,
        codePaint = make_paint("Courier", normal, 20, { a=1, r=.9, g=.9, b=.9 }),
    }
end

gTemplate = SkiaPoint_make_template()

gRedPaint = Sk.newPaint()
gRedPaint:setAntiAlias(true)
gRedPaint:setColor{a=1, r=1, g=0, b=0 }

-- animation.proc is passed the canvas before drawing.
-- The animation.proc returns itself or another animation (which means keep animating)
-- or it returns nil, which stops the animation.
--
local gCurrAnimation

gSlideIndex = 1

-----------------------------------------------------------------------------

function new_drawable_picture(pic)
    return {
        picture = pic,
        width = pic:width(),
        height = pic:height(),
        draw = function (self, canvas, x, y, paint)
            canvas:drawPicture(self.picture, x, y, paint)
        end
    }
end

function new_drawable_image(img)
    return {
        image = img,
        width = img:width(),
        height = img:height(),
        draw = function (self, canvas, x, y, paint)
            canvas:drawImage(self.image, x, y, paint)
        end
    }
end

function convert_to_picture_drawable(slide)
    local rec = Sk.newPictureRecorder()
    drawSlide(rec:beginRecording(640, 480), slide, gTemplate)
    return new_drawable_picture(rec:endRecording())
end

function convert_to_image_drawable(slide)
    local surf = Sk.newRasterSurface(640, 480)
    drawSlide(surf:getCanvas(), slide, gTemplate)
    return new_drawable_image(surf:newImageSnapshot())
end

function new_drawable_slide(slide)
    return {
        slide = slide,
        draw = function (self, canvas, x, y, paint)
            if (nil == paint or ("number" == type(paint) and (1 == paint))) then
                canvas:save()
            else
                canvas:saveLayer(paint)
            end
            canvas:translate(x, y)
            drawSlide(canvas, self.slide, gTemplate)
            canvas:restore()
        end
    }
end

gNewDrawableFactory = {
    default = new_drawable_slide,
    picture = convert_to_picture_drawable,
    image = convert_to_image_drawable,
}

-----------------------------------------------------------------------------

function next_slide()
    local prev = gSlides[gSlideIndex]

    if gSlideIndex < #gSlides then
        gSlideIndex = gSlideIndex + 1
        spawn_transition(prev, gSlides[gSlideIndex], true)
    end
end

function prev_slide()
    local prev = gSlides[gSlideIndex]

    if gSlideIndex > 1 then
        gSlideIndex = gSlideIndex - 1
        spawn_transition(prev, gSlides[gSlideIndex], false)
    end
end

gDrawableType = "default"

load_file("slides_transitions")

function spawn_transition(prevSlide, nextSlide, is_forward)
    local transition
    if is_forward then
        transition = gTransitionTable[nextSlide.transition]
    else
        transition = gTransitionTable[prevSlide.transition]
    end

    if not transition then
        transition = fade_slide_transition
    end

    local prevDrawable = gNewDrawableFactory[gDrawableType](prevSlide)
    local nextDrawable = gNewDrawableFactory[gDrawableType](nextSlide)
    gCurrAnimation = transition(prevDrawable, nextDrawable, is_forward)
end

--------------------------------------------------------------------------------------

function spawn_rotate_animation()
    gCurrAnimation = {
        angle = 0,
        angle_delta = 5,
        pivot_x = 320,
        pivot_y = 240,
        proc = function (self, canvas, drawSlideProc)
            if self.angle >= 360 then
                drawSlideProc(canvas)
                return nil
            end
            canvas:translate(self.pivot_x, self.pivot_y)
            canvas:rotate(self.angle)
            canvas:translate(-self.pivot_x, -self.pivot_y)
            drawSlideProc(canvas)

            self.angle = self.angle + self.angle_delta
            return self
        end
    }
end

function spawn_scale_animation()
    gCurrAnimation = {
        scale = 1,
        scale_delta = .95,
        scale_limit = 0.2,
        pivot_x = 320,
        pivot_y = 240,
        proc = function (self, canvas, drawSlideProc)
            if self.scale < self.scale_limit then
                self.scale = self.scale_limit
                self.scale_delta = 1 / self.scale_delta
            end
            if self.scale > 1 then
                drawSlideProc(canvas)
                return nil
            end
            canvas:translate(self.pivot_x, self.pivot_y)
            canvas:scale(self.scale, self.scale)
            canvas:translate(-self.pivot_x, -self.pivot_y)
            drawSlideProc(canvas)

            self.scale = self.scale * self.scale_delta
            return self
        end
    }
end

local bgPaint = nil

function draw_bg(canvas)
    if not bgPaint then
        bgPaint = Sk.newPaint()
        local grad = Sk.newLinearGradient(  0,   0, { a=1, r=0, g=0, b=.3 },
                                          640, 480, { a=1, r=0, g=0, b=.8 })
        bgPaint:setShader(grad)
        bgPaint:setDither(true)
    end

    canvas:drawPaint(bgPaint)
end

function onDrawContent(canvas, width, height)
    local matrix = Sk.newMatrix()
    matrix:setRectToRect(make_rect(0, 0, 640, 480), make_rect(0, 0, width, height), "center")
    canvas:concat(matrix)

    draw_bg(canvas)

    local drawSlideProc = function(canvas)
        drawSlide(canvas, gSlides[gSlideIndex], gTemplate)
    end

    if gCurrAnimation then
        gCurrAnimation = gCurrAnimation:proc(canvas, drawSlideProc)
        return true
    else
        drawSlideProc(canvas)
        return false
    end
end

function onClickHandler(x, y)
    return false
end

local keyProcs = {
    n = next_slide,
    p = prev_slide,
    r = spawn_rotate_animation,
    s = spawn_scale_animation,
    ["="] = function () scale_text_delta(gTemplate, 1) end,
    ["-"] = function () scale_text_delta(gTemplate, -1) end,

    b = function () gShowBounds = not gShowBounds end,
    B = function () gUseBlurInTransitions = not gUseBlurInTransitions end,

    ["1"] = function () gDrawableType = "default" end,
    ["2"] = function () gDrawableType = "picture" end,
    ["3"] = function () gDrawableType = "image" end,
}

function onCharHandler(uni)
    local proc = keyProcs[uni]
    if proc then
        proc()
        return true
    end
    return false
end