Card = copy_table(Shape)

Card.is_card = true
Card.suit_names = {
    "clubs",
    "diamonds",
    "hearts",
    "spades",
}
Card.suit_colors = { 0, 1, 1, 0 }


-- STATES:
-- deck
-- tray (waste)
-- table
-- home (foundation)

function Card:new(view, suit, value, x, y, angle)
    local o = copy_table(self)
    o:init(view, suit, value, x, y, angle)
    return o
end


function Card:init(view, suit, value, x, y, angle)
    self.view = view
    self.hi_alpha = 0
    self.enabled = true
    local theme = view.theme or "hearts"

    self.suit = suit
    self.value = value
    
    self.bitmap = gfx_load_bitmap("gfx/cards_"..self.suit_names[suit])
    self.sprite = self.suit_names[suit]..value

    self.bitmap_hi = gfx_load_bitmap("themes/"..theme.."/card_highlight")
    self.sprite_hi = "over"
    self.sprite_selected = "selected"
    
    self.bitmap_back = gfx_load_bitmap("themes/"..theme.."/card_back")
    self.sprite_back = self.view.card_back_sprite or "back1"

    self.bitmap_shadow = gfx_load_bitmap("gfx/card_shadow")

    self.x = x
    self.y = y
    self.z = 100
    self.angle = angle

    self.w, self.h, self.cx, self.cy = gfx_sprite_params(self.bitmap, self.sprite)
    self:create_area()
    self.area.x0 = self.area.x0 + 1
    self.area.y0 = self.area.y0 + 1
    self.area.x1 = self.area.x1 - 1
    self.area.y1 = self.area.y1 - 1

    self.highlight_on = true
end


function Card:destroy()
    gfx_release_bitmap(self.bitmap_shadow)
    gfx_release_bitmap(self.bitmap_back)
    gfx_release_bitmap(self.bitmap_hi)
    gfx_release_bitmap(self.bitmap)
end


function Card:update(dt)
    if self.double_click_timer then
        self.double_click_timer = self.double_click_timer - dt
        if self.double_click_timer < 0 then
            self.double_click_timer = nil
        end
    end

    if self.flying_delay then
        self.flying_delay = self.flying_delay - dt
        if self.flying_delay <= 0 then
            self.flying_delay = nil
        end
    elseif self.flying then
        self.flying:update(dt)
        
        if self.state == "shuffle" then
            local t = self.flying.time / self.flying.total_time
            self.x = self.second_x + (1-t) * (self.start_x - self.second_x) + (t*t) * (self.dest_x - self.second_x)
            self.y = self.second_y + (1-t) * (self.start_y - self.second_y) + (t*t) * (self.dest_y - self.second_y)
        else
            self.x = self.flying.val.x
            self.y = self.flying.val.y
        end

        if self.flying:is_finished() then
            self.flying = nil
        end
    end

    if self.revealing then
        self.revealing = self.revealing - dt
        if self.revealing < 0 then
            self.revealing = nil
        end
    end
    

    -- move

    local damp = math.min(1, 25.0 * dt)

    if self.move then
        local dx = self.move.x - self.x
        local dy = self.move.y - self.y
        self.angle = math.min(1, dx / 100) * 65
        self.x = self.x + dx * damp
        self.y = self.y + dy * damp
        if dx * dx + dy * dy < 1 then
            self.x = self.move.x
            self.y = self.move.y
            self.angle = 0
            self.move = nil
        end
    end

    -- move stack
    if (self.state == "float" or self.state == "table") and self.has_stack then
        local c = self.has_stack
        local dx = self.x - c.x
        local dy = self.y + self.view.positions.stack_spacing - c.y
        c.angle = math.min(1, dx / 100) * 65
        c.x = c.x + dx * damp
        c.y = c.y + dy * damp

        if dx * dx + dy * dy < 1 then
            c.x = self.x
            c.y = self.y + self.view.positions.stack_spacing
        end
        
        if math.abs(c.angle) < 1 then
            c.angle = 0
        end
    end
    
    self:highlight_update(dt)
end


function Card:mouse_left_click()
    if self.flying or self.revealing then return end
    
    if self.state == "deck" and not self.has_stack then
        self:send_to_tray()
        snd_play_sound("card_off_deck")
    elseif (self.state == "tray" and not self.has_stack) or (self.state == "table" and not self.covered) then
        if self.double_click_timer and self.view:fast_move_home(self) then
            snd_play_sound("fast_move")
            -- nothing more to do here
        else
            self.view:start_dragging(self)
            self.double_click_timer = 0.3
            snd_play_sound("card_select")
        end
    elseif self.state == "home" and not self.has_stack then
        self.view:start_dragging(self)
        snd_play_sound("card_select")

    elseif self.state == "table" and self.covered and not self.has_stack then
        self:reveal()
    end
end


function Card:mouse_right_click()
    if self.flying or self.revealing then return end

    if (self.state == "tray" and not self.has_stack) or (self.state == "table" and not self.covered) then
        if self.view:fast_move_home(self) then
            snd_play_sound("fast_move")
        end
    end
end


function Card:stack_position()
    if self.state == "table" then
        return self.x, self.y + self.view.positions.stack_spacing, self.z + 1
    elseif self.state == "home" then
        return self.x, self.y, self.z + 1
    elseif self.state == "tray" then
        return self.x, self.y, self.z + 1
    end
end


function Card:send_to_tray()
    self.state = "tray"
    self.view:select_new_deck_top(self)
    self.view:change_tray_top(self)
    self:reveal()

    self.flying = Animator:new()
    self.flying:add_key("x", 0, self.x)
    self.flying:add_key("x", 0.5, self.view.positions.tray_x)
    self.flying:add_key("y", 0, self.y)
    self.flying:add_key("y", 0.5, self.view.positions.tray_y)
    self.flying:add_key("scale", 0, 1)
    self.flying:add_key("scale", 0.25, 0)
    self.flying:add_key("scale", 0.5, 1)
    self.flying:add_key("angle", 0, self.angle)
    self.flying:add_key("angle", 0.5, 0)
    self.flying:add_key("face", 0, 0)
    self.flying:add_key("face", 0.25, 0)
    self.flying:add_key("face", 0.5, 1)

    self.z = self.view:get_top_z()
    self.angle = 0
    self.view:sort_objects()
end


function Card:send_to_deck(x, y, delay)
    self.state = "deck"
    self:cover()

    self.flying = Animator:new()
    self.flying:add_key("x", 0, self.x)
    self.flying:add_key("x", 0 + delay, self.x)
    self.flying:add_key("x", 0.4 + delay, x)
    self.flying:add_key("y", 0, self.y)
    self.flying:add_key("y", 0 + delay, self.y)
    self.flying:add_key("y", 0.4 + delay, y)
    self.flying:add_key("scale", 0, 1)
    self.flying:add_key("scale", 0 + delay, 1)
    self.flying:add_key("scale", 0.2 + delay, 0)
    self.flying:add_key("scale", 0.4 + delay, 1)
    self.flying:add_key("face", 0, 1)
    self.flying:add_key("face", 0 + delay, 1)
    self.flying:add_key("face", 0.2 + delay, 0)
    self.flying:add_key("face", 0.4 + delay, 0)
    self.flying:add_key("z", 0, 0)
    self.flying:add_key("z", 0.4 + delay, 1)

    self.z_change_pending = true
end


function Card:render()
    if self.flying and not self.flying_delay then
        local scale = self.flying.val.scale
        local angle = self.flying.val.angle
        if not self.z_change_pending then
            gfx_render_sprite(self.bitmap_shadow, "shadow", self.x + 6, self.y, 0.6, angle, scale + (1-scale) * 0.4, 1)
        end
        if self.flying.val.face > 0 then
            gfx_render_sprite(self.bitmap, self.sprite, self.x, self.y, 1, angle, scale, 1)
        else
            gfx_render_sprite(self.bitmap_back, self.sprite_back, self.x, self.y, 1, angle, scale, 1)
        end
        
        if self.z_change_pending and self.flying.val.z and self.flying.val.z > 0.9 then
            self.z_change_pending = nil
            self.z = self.view:get_top_z()
            self.view.sort_request = true
        end
    else
        local scale = 1
        if self.revealing then
            scale = math.abs((self.revealing - 0.15) / 0.15)
        end
        if not (self.state == "tray" and self.has_stack and not self.has_stack.flying) then
            gfx_render_sprite(self.bitmap_shadow, "shadow", self.x, self.y, 1, self.angle, scale, 1)
        end
        if self.covered or (self.revealing and self.revealing > 0.15) then
            gfx_render_sprite(self.bitmap_back, self.sprite_back, self.x, self.y, 1, self.angle, scale, 1)
        else
            gfx_render_sprite(self.bitmap, self.sprite, self.x, self.y, 1, self.angle, scale, 1)
        end

        if self.hi_alpha > 0 then
           gfx_render_sprite(self.bitmap_hi, self.sprite_hi, self.x, self.y, self.hi_alpha, self.angle, scale, 1)
        end
    end
end


function Card:cover()
    self.covered = self.covered or 0
    self.covered = self.covered + 1
    self.highlight_on = false
end


function Card:reveal()
    if self.covered and self.covered > 1 then
        self.covered = self.covered - 1
    else
        self.covered = nil
        self.revealing = 0.3
        self.highlight_on = true
    end
end


function Card:accepts_stack(card)
    if self.state == "home" and card.value == self.value + 1 and self.suit == card.suit and not card.has_stack then
        return true
    elseif self.state == "table" and card.value + 1 == self.value and Card.suit_colors[card.suit] ~= Card.suit_colors[self.suit] then
        return true
    end
    return false
end


function Card:move_to(x, y)
    self.move = {
        x = x,
        y = y,
    }
end


function Card:shuffle_away(delay)
    self.state = "shuffle"
    self.flying_delay = delay or (math.random() * 2.0)

    self.start_x = self.x
    self.start_y = self.y
    self.second_x = self.x
    self.second_y = self.y

    self.dest_x = -60
    self.dest_y = 600.0 * math.random()

    local time_scale = 2 * self.x / 800

    self.flying = Animator:new()
    self.flying:key("scale", {{0, 1}, {0.1 * time_scale, 0}, {0.2 * time_scale, 1}, {0.3 * time_scale, 0}, {0.4 * time_scale, 1}, {0.5 * time_scale, 0}, {0.6 * time_scale, 1}, {0.7 * time_scale, 0}, {0.8 * time_scale, 1}} )
    self.flying:key("angle", {{0, self.angle}, {0.8 * time_scale, self.angle + 360 * 2}})
    local face = 1
    if self.covered then
        face = -1
    end
    self.flying:key("face", {{0, 1 * face}, {0.2 * time_scale, -1 * face}, {0.4 * time_scale, 1 * face}, {0.6 * time_scale, -1 * face}, {0.8 * time_scale, 1 * face}})
end
