--Class
CardArea = Moveable:extend()

--Class Methods
function CardArea:init(X, Y, W, H, config)
    Moveable.init(self, X, Y, W, H)

    self.states.drag.can = false
    self.states.hover.can = false
    self.states.click.can = false


    self.config = setmetatable({card_limits = {card_limit}}, {
        __index = function(t, key)
            if key == "card_limit" then
                return (t.card_limits.total_slots or 0) - (t.card_limits.extra_slots_used or 0)
            end
        end,
        __newindex = function(t, key, value)
            if key == 'card_limit' then
                if not t.card_limits.base then rawset(t.card_limits, 'base', value) end
                if not t.card_limits.total_slots then rawset(t.card_limits, 'total_slots', value) end
                rawset(t.card_limits, 'mod', value - t.card_limits.base - (t.card_limits.extra_slots or 0) + (t.card_limits.extra_slots_used or 0))
            else
                rawset(t, key, value)
            end
        end
    })
    
    SMODS.merge_defaults(self.config, config)
    self.card_w = config.card_w or G.CARD_W
    self.cards = {}
    self.children = {}
    self.highlighted = {}
    self.config.highlighted_limit = config.highlight_limit or 5
    self.config.card_limit = config.card_limit or 52
    self.config.temp_limit = self.config.card_limit
    self.config.card_count = 0
    self.config.type = config.type or 'deck'
    self.config.sort = config.sort or 'desc'
    self.config.lr_padding = config.lr_padding or 0.1
    self.shuffle_amt = 0

    if getmetatable(self) == CardArea then 
        table.insert(G.I.CARDAREA, self)
    end
end

function CardArea:emplace(card, location, stay_flipped)
for k, v in pairs(self.cards) do
	if v == card then return end
end
    if location == 'front' or self.config.type == 'deck' then 
        table.insert(self.cards, 1, card)
    else
        self.cards[#self.cards+1] = card
    end
    if card.cry_flipped then card.facing = 'back'; card.sprite_facing = 'back' end
    if not (card.cry_flipped and (self == G.shop_jokers or self == G.shop_vouchers or self == G.shop_booster)) and card.facing == 'back' and self.config.type ~= 'discard' and self.config.type ~= 'deck' and not stay_flipped then
        card:flip()
    end
    if self == G.hand and stay_flipped then 
        card.ability.wheel_flipped = true
    end

    if #self.cards > self.config.card_limit then
        if self == G.deck and G.GAME.blind then
            self.config.card_limit = #self.cards
        end
    end
    
    card:set_card_area(self)
    self:set_ranks()
    self:align_cards()

    if self == G.jokers then
        local joker_tally = 0
        for i = 1, #G.jokers.cards do
          if G.jokers.cards[i].ability.set == 'Joker' then joker_tally = joker_tally + 1 end
        end
        if joker_tally > G.GAME.max_jokers then G.GAME.max_jokers = joker_tally end
        check_for_unlock({type = 'modify_jokers'}) 
    end
    if self == G.deck then check_for_unlock({type = 'modify_deck', deck = self}) end
end

function CardArea:remove_card(card, discarded_only)
    if not self.cards then return end
    local _cards = discarded_only and {} or self.cards
    if discarded_only then 
        for k, v in ipairs(self.cards) do
            if v.ability and v.ability.discarded then 
                _cards[#_cards+1] = v
            end
        end
    end
    if self.config.type == 'discard' or self.config.type == 'deck' then
        card = card or _cards[#_cards]
    else
        card = card or _cards[1]
    end
    for i = #self.cards,1,-1 do
        if self.cards[i] == card then
            card:remove_from_area()
            table.remove(self.cards, i)
            self:remove_from_highlighted(card, true)
            break
        end
    end
    self:set_ranks()
    if not G.in_delete_run and self == G.deck then check_for_unlock({type = 'modify_deck', deck = self}) end
    return card
end

function CardArea:change_size(delta)
    if true then
        self.config.card_limits.mod = (self.config.card_limits.mod or 0) + delta
        return
    end
    if delta ~= 0 then 
        G.E_MANAGER:add_event(Event({
            func = function() 
                self.config.real_card_limit = (self.config.real_card_limit or self.config.card_limit) + delta
                self.config.card_limit = math.max(0, self.config.real_card_limit)
                if delta > 0 and self.config.real_card_limit > 1 and self == G.hand and self.cards[1] and (G.STATE == G.STATES.DRAW_TO_HAND or G.STATE == G.STATES.SELECTING_HAND) then 
                    local card_count = math.abs(delta)
                    for i=1, card_count do
                        draw_card(G.deck,G.hand, i*100/card_count,nil, nil , nil, 0.07)
                        G.E_MANAGER:add_event(Event({func = function() self:sort() return true end}))
                    end
                end
                if self == G.hand then check_for_unlock({type = 'min_hand_size'}) end
        return true
        end}))
    end
end

function CardArea:can_highlight(card)
    if G.CONTROLLER.HID.controller then 
        if  self.config.type == 'hand'
        then
                return true
        end
    else
        if  self.config.type == 'hand' or
            self.config.type == 'joker' or
            self.config.type == 'consumeable' or
            (self.config.type == 'shop' and self.config.highlighted_limit > 0)
        then
                return true
        end
    end
    return false
end

function CardArea:add_to_highlighted(card, silent)
    --if self.config.highlighted_limit <= #self.highlighted then return end
    if self.config.type == 'shop' then
        if to_big(#self.highlighted) >= to_big(self.config.highlighted_limit) then
            self:remove_from_highlighted(self.highlighted[1])
        end
        --if not G.FUNCS.check_for_buy_space(card) then return false end
        self.highlighted[#self.highlighted+1] = card
        card:highlight(true)
        if not silent then play_sound('cardSlide1') end
    elseif self.config.type == 'joker' or self.config.type == 'consumeable' then
        if to_big(#self.highlighted) >= to_big(self.config.highlighted_limit) then
            self:remove_from_highlighted(self.highlighted[1])
        end
        self.highlighted[#self.highlighted+1] = card
        card:highlight(true)
        if not silent then play_sound('cardSlide1') end
    else
        if to_big(#self.highlighted) >= to_big(self.config.highlighted_limit) then
            card:highlight(false)
        else
            self.highlighted[#self.highlighted+1] = card
            card:highlight(true)
            if not silent then play_sound('cardSlide1') end
        end
        if self == G.hand and G.STATE == G.STATES.SELECTING_HAND then
            self:parse_highlighted()
        end
    end
end

function CardArea:parse_highlighted()
    G.boss_throw_hand = nil
    local text,disp_text,poker_hands = G.FUNCS.get_poker_hand_info(self.highlighted)
    local text,disp_text,poker_hands
    if self == G.hand then
    	local tbl = {}
    	for i, v in pairs(G.jokers.cards) do
    		if v.base.nominal and v.base.suit then
    			tbl[#tbl+1] = v
    		end
    	end
    	text,disp_text,poker_hands = G.FUNCS.get_poker_hand_info(Cryptid.table_merge(self.highlighted, tbl))
    else
    	text,disp_text,poker_hands = G.FUNCS.get_poker_hand_info(self.highlighted)
    end
    if text == 'NULL' then
        update_hand_text({immediate = true, nopulse = true, delay = 0}, {mult = 0, chips = 0, level = '', handname = ''})
        for name, parameter in pairs(SMODS.Scoring_Parameters) do
            update_hand_text({immediate = true, nopulse = true, delay = 0}, {[name] = parameter.default_value})
        end
    else
        if G.GAME.blind and G.GAME.blind:debuff_hand(self.highlighted, poker_hands, text, true) then
            G.boss_throw_hand = true
        else
            
        end
        local backwards = nil
        for k, v in pairs(self.highlighted) do
            if v.facing == 'back' then
                backwards = true
            end
        end
        if backwards then
            update_hand_text({immediate = true, nopulse = nil, delay = 0}, {handname='????', level='?', mult = '?', chips = '?'})
            for name, parameter in pairs(SMODS.Scoring_Parameters) do
                update_hand_text({immediate = true, nopulse = nil, delay = 0}, {[name] = '?'})
            end
        else
            for name, parameter in pairs(SMODS.Scoring_Parameters) do
                parameter.current = G.GAME.hands[text][name] or parameter.default_value
                update_hand_text({immediate = true, nopulse = nil, delay = 0}, {[name] = parameter.current})
            end
            update_hand_text({immediate = true, nopulse = nil, delay = 0}, {handname=disp_text, level=G.GAME.hands[text].level, mult = Cryptid.ascend(G.GAME.hands[text].mult), chips = Cryptid.ascend(G.GAME.hands[text].chips)})
        end
    end
end

function CardArea:remove_from_highlighted(card, force)
    if (not force) and  card and card.ability.forced_selection and self == G.hand then return end
    for i = #self.highlighted,1,-1 do
        if self.highlighted[i] == card then
            table.remove(self.highlighted, i)
            break
        end
    end
    card:highlight(false)
    if self == G.hand and G.STATE == G.STATES.SELECTING_HAND then
        self:parse_highlighted()
    end
end

function CardArea:unhighlight_all()
    for i = #self.highlighted,1,-1 do
        if self.highlighted[i].ability.forced_selection and self == G.hand then
        else
            self.highlighted[i]:highlight(false)
            table.remove(self.highlighted, i)
        end
    end 
    if self == G.hand and G.STATE == G.STATES.SELECTING_HAND then
        self:parse_highlighted()
    end
end

function CardArea:set_ranks()
    for k, card in ipairs(self.cards) do
        card.rank = k
        card.states.collide.can = true
        if k > 1 and self.config.type == 'deck' then 
            card.states.drag.can = false 
            card.states.collide.can = false 
        elseif self.config.type == 'play' or self.config.type == 'shop' or self.config.type == 'consumeable' then 
            card.states.drag.can = false
        else
            card.states.drag.can = true
        end
    end
end

function CardArea:move(dt)
    --Set sliding up/down for the hand area
    if self == G.hand then 
        local desired_y = G.TILE_H - G.hand.T.h - 1.9*((not G.deck_preview and (G.STATE == G.STATES.SELECTING_HAND or G.STATE == G.STATES.DRAW_TO_HAND)) and 1 or 0)
        G.hand.T.y = 15*G.real_dt*desired_y + (1-15*G.real_dt)*G.hand.T.y
        if math.abs(desired_y - G.hand.T.y) < 0.01 then G.hand.T.y = desired_y end
        if G.STATE == G.STATES.TUTORIAL then 
            G.play.T.y = G.hand.T.y - (3 + 0.6)
        end
    end
    Moveable.move(self, dt)
    self:align_cards()
end

function CardArea:update(dt)
    if self == G.hand then
        if G.GAME.modifiers.minus_hand_size_per_X_dollar then
            self.config.last_poll_size = self.config.last_poll_size or 0
            if math.floor(G.GAME.dollars/G.GAME.modifiers.minus_hand_size_per_X_dollar) ~= self.config.last_poll_size then
                 self:change_size(to_number(self.config.last_poll_size - math.floor(G.GAME.dollars/G.GAME.modifiers.minus_hand_size_per_X_dollar)))
                 self.config.last_poll_size = math.floor(G.GAME.dollars/G.GAME.modifiers.minus_hand_size_per_X_dollar)
            end
        end
        for k, v in pairs(self.cards) do
            if v.ability.forced_selection and not self.highlighted[1] then 
                self:add_to_highlighted(v)
            end
        end
    end
    if self == G.deck then 
        self.states.collide.can = true
        self.states.hover.can = true
        self.states.click.can = true
    end
    --Check and see if controller is being used
    if G.CONTROLLER.HID.controller and self ~= G.hand then self:unhighlight_all() end
    if self == G.deck and self.config.card_limit ~= #G.playing_cards then
        self.config.card_limit = #G.playing_cards
        self.config.card_limits.total_slots = #G.playing_cards
    end
    self:handle_card_limit()
    self.config.temp_limit = math.max(#self.cards, self.config.card_limit or 0)
end

function CardArea:draw()
    if not self.states.visible then return end 
    if G.VIEWING_DECK and (self==G.deck or self==G.hand or self==G.play) then return end

    local state = G.TAROT_INTERRUPT or G.STATE

    self.ARGS.invisible_area_types = self.ARGS.invisible_area_types or {discard=1, voucher=1, play=1, consumeable=1, title = 1, title_2 = 1}
    if self.ARGS.invisible_area_types[self.config.type] or
        (self.config.type == 'hand' and ({[G.STATES.SHOP]=1, [G.STATES.TAROT_PACK]=1, [G.STATES.SPECTRAL_PACK]=1, [G.STATES.STANDARD_PACK]=1,[G.STATES.BUFFOON_PACK]=1,[G.STATES.PLANET_PACK]=1, [G.STATES.ROUND_EVAL]=1, [G.STATES.BLIND_SELECT]=1})[state]) or
        (self.config.type == 'hand' and state == G.STATES.SMODS_BOOSTER_OPENED) or
        (self.config.type == 'deck' and self ~= G.deck) or
        (self.config.type == 'shop' and self ~= G.shop_vouchers) then
    else
        if not self.children.area_uibox then 
                local card_count = not self.config.no_card_count and self ~= G.shop_vouchers and {n=G.UIT.R, config={align = self == G.jokers and 'cl' or self == G.hand and 'cm' or 'cr', padding = 0.03, no_fill = true}, nodes={
                    {n=G.UIT.B, config={w = 0.1,h=0.1}},
                    {n=G.UIT.T, config={ref_table = self.config, ref_value = 'card_count', scale = 0.3, colour = G.C.WHITE}},
                    {n=G.UIT.T, config={text = '/', scale = 0.3, colour = G.C.WHITE}},
                    {n=G.UIT.T, config={ref_table = self.config.card_limits, ref_value = 'total_slots', scale = 0.3, colour = G.C.WHITE}},
                    {n=G.UIT.B, config={w = 0.1,h=0.1}}
                }} or nil

                self.children.area_uibox = UIBox{
                    definition = 
                        {n=G.UIT.ROOT, config = {align = 'cm', colour = G.C.CLEAR}, nodes={
                            {n=G.UIT.R, config={minw = self.T.w,minh = self.T.h,align = "cm", padding = 0.1, mid = true, r = 0.1, colour = self.config.bg_colour or self ~= G.shop_vouchers and {0,0,0,0.1} or nil, ref_table = self}, nodes={
                                self == G.shop_vouchers and 
                                {n=G.UIT.C, config={align = "cm", paddin = 0.1, func = 'shop_voucher_empty', visible = false}, nodes={
                                    {n=G.UIT.R, config={align = "cm"}, nodes={
                                        {n=G.UIT.T, config={text = G.GAME.modifiers.cry_no_vouchers and (very_fair_quip[1] or '') or 'DEFEAT', scale = 0.6, colour = G.C.WHITE}}
                                    }},
                                    {n=G.UIT.R, config={align = "cm"}, nodes={
                                        {n=G.UIT.T, config={text = G.GAME.modifiers.cry_no_vouchers and (very_fair_quip[2] or '') or G.GAME.modifiers.cry_voucher_restock_antes and G.GAME.round_resets.ante % G.GAME.modifiers.cry_voucher_restock_antes == 0 and 'TWO BOSS BLINDS' or 'BOSS BLIND', scale = 0.4, colour = G.C.WHITE}}
                                    }},
                                    {n=G.UIT.R, config={align = "cm"}, nodes={
                                        {n=G.UIT.T, config={text = G.GAME.modifiers.cry_no_vouchers and (very_fair_quip[3] or '') or 'TO RESTOCK', scale = 0.4, colour = G.C.WHITE}}
                                    }},
                                }} or nil,
                            }},
                            card_count
                        }},
                    config = { align = 'cm', offset = {x=0,y=0}, major = self, parent = self}
                }
            end
        self.children.area_uibox:draw()
    end

    self:draw_boundingrect()
    add_to_drawhash(self)

    self.ARGS.draw_layers = self.ARGS.draw_layers or self.config.draw_layers or {'shadow', 'card'}
    for k, v in ipairs(self.ARGS.draw_layers) do
        if self.config.type == 'deck' then 
            for i = #self.cards, 1, -1 do 
                if self.cards[i] ~= G.CONTROLLER.focused.target then
                    if i == 1 or i%(self.config.thin_draw or 9) == 0 or i == #self.cards or math.abs(self.cards[i].VT.x - self.T.x) > 1 or math.abs(self.cards[i].VT.y - self.T.y) > 1  then
                        if G.CONTROLLER.dragging.target ~= self.cards[i] then self.cards[i]:draw(v) end
                    end
                end
            end
        end

        if self.config.type == 'joker' or self.config.type == 'consumeable' or self.config.type == 'shop' or self.config.type == 'title_2' then 
            for i = 1, #self.cards do 
                if self.cards[i] ~= G.CONTROLLER.focused.target then
                    if not self.cards[i].highlighted then
                        if G.CONTROLLER.dragging.target ~= self.cards[i] then self.cards[i]:draw(v) end
                    end
                end
            end
            for i = 1, #self.cards do  
                if self.cards[i] ~= G.CONTROLLER.focused.target then
                    if self.cards[i].highlighted then
                        if G.CONTROLLER.dragging.target ~= self.cards[i] then self.cards[i]:draw(v) end
                    end
                end
            end
        end

        if self.config.type == 'discard' then 
            for i = 1, #self.cards do 
                if self.cards[i] ~= G.CONTROLLER.focused.target then
                    if math.abs(self.cards[i].VT.x - self.T.x) > 1 then 
                        if G.CONTROLLER.dragging.target ~= self.cards[i] then self.cards[i]:draw(v) end
                    end
                end
            end
        end

        if self.config.type == 'hand' or self.config.type == 'play' or self.config.type == 'title' or self.config.type == 'voucher' then 
            for i = 1, #self.cards do 
                if self.cards[i] ~= G.CONTROLLER.focused.target or self == G.hand then
                    if G.CONTROLLER.dragging.target ~= self.cards[i] then self.cards[i]:draw(v) end
                end
            end
        end
    end

    if self == G.deck then
        if G.CONTROLLER.HID.controller and G.STATE == G.STATES.SELECTING_HAND and not self.children.peek_deck then
            self.children.peek_deck = UIBox{
                definition = 
                    {n=G.UIT.ROOT, config = {align = 'cm', padding = 0.1, r =0.1, colour = G.C.CLEAR}, nodes={
                        {n=G.UIT.R, config={align = "cm", r =0.1, colour = adjust_alpha(G.C.L_BLACK, 0.5),func = 'set_button_pip', focus_args = {button = 'triggerleft', orientation = 'bm', scale = 0.6, type = 'none'}}, nodes={
                            {n=G.UIT.R, config={align = "cm"}, nodes={
                                {n=G.UIT.T, config={text = 'PEEK', scale = 0.48, colour = G.C.WHITE, shadow = true}}
                            }},
                            {n=G.UIT.R, config={align = "cm"}, nodes={
                                {n=G.UIT.T, config={text = 'DECK', scale = 0.38, colour = G.C.WHITE, shadow = true}}
                            }},
                        }},
                    }},
                config = { align = 'cl', offset = {x=-0.5,y=0.1}, major = self, parent = self}
            }
            self.children.peek_deck.states.collide.can = false
        elseif (not G.CONTROLLER.HID.controller or G.STATE ~= G.STATES.SELECTING_HAND) and self.children.peek_deck then
            self.children.peek_deck:remove()
            self.children.peek_deck = nil
        end
        if not self.children.view_deck then 
            self.children.view_deck = UIBox{
                definition = 
                    {n=G.UIT.ROOT, config = {align = 'cm', padding = 0.1, r =0.1, colour = G.C.CLEAR}, nodes={
                        {n=G.UIT.R, config={align = "cm", padding = 0.05, r =0.1, colour = adjust_alpha(G.C.BLACK, 0.5),func = 'set_button_pip', focus_args = {button = 'triggerright', orientation = 'bm', scale = 0.6}, button = 'deck_info'}, nodes={
                            {n=G.UIT.R, config={align = "cm", maxw = 2}, nodes={
                                {n=G.UIT.T, config={text = localize('k_view'), scale = 0.48, colour = G.C.WHITE, shadow = true}}
                            }},
                            {n=G.UIT.R, config={align = "cm", maxw = 2}, nodes={
                                {n=G.UIT.T, config={text = localize('k_deck'), scale = 0.38, colour = G.C.WHITE, shadow = true}}
                            }},
                        }},
                    }},
                config = { align = 'cm', offset = {x=0,y=0}, major = self.cards[1] or self, parent = self}
            }
            self.children.view_deck.states.collide.can = false
        end
    if G.deck_preview or self.states.collide.is or (G.buttons and G.buttons.states.collide.is and G.CONTROLLER.HID.controller) then self.children.view_deck:draw() end
    if self.children.peek_deck then self.children.peek_deck:draw() end
    end
end

function CardArea:align_cards()
    if self.config.type == 'hand' then
        self.config.temp_limit = math.min(self.config.card_limit, #G.playing_cards)
    end
    if (self == G.hand or self == G.deck or self == G.discard or self == G.play) and G.view_deck and G.view_deck[1] and G.view_deck[1].cards then return end
    if self.config.type == 'deck' then
            local deck_height = (self.config.deck_height or 0.15)/52
            for k, card in ipairs(self.cards) do
                if card.facing == 'front' then card:flip() end

                if not card.states.drag.is then
                    card.T.x = self.T.x + 0.5*(self.T.w - card.T.w) + self.shadow_parrallax.x*deck_height*(#self.cards/(self == G.deck and 1 or 2) - k) + 0.9*self.shuffle_amt*(1 - k*0.01)*(k%2 == 1 and 1 or -0)
                    card.T.y = self.T.y + 0.5*(self.T.h - card.T.h) + self.shadow_parrallax.y*deck_height*(#self.cards/(self == G.deck and 1 or 2) - k)
                    card.T.r = 0 + 0.3*self.shuffle_amt*(1 + k*0.05)*(k%2 == 1 and 1 or -0)
                    card.T.x = card.T.x + card.shadow_parrallax.x/30
                end
            end
    end
    if self.config.type == 'discard' then
        for k, card in ipairs(self.cards) do
            if card.facing == 'front' then card:flip() end

            if not card.states.drag.is then 
                card.T.x = self.T.x + (self.T.w - card.T.w)*card.discard_pos.x
                card.T.y = self.T.y + (self.T.h - card.T.h)* card.discard_pos.y
                card.T.r = card.discard_pos.r
            end
        end
    end
    if self.config.type == 'hand' and (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED) then
        for k, card in ipairs(self.cards) do
            if not card.states.drag.is then 
                card.T.r = 0.4*(-#self.cards/2 - 0.5 + k)/(#self.cards)+ (G.SETTINGS.reduced_motion and 0 or 1)*0.02*math.sin(2*G.TIMERS.REAL+card.T.x)
                local max_cards = math.max(#self.cards, self.config.temp_limit)
                card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/math.max(max_cards-1, 1) - 0.5*(#self.cards-max_cards)/math.max(max_cards-1, 1)) + 0.5*(self.card_w - card.T.w)
                local highlight_height = G.HIGHLIGHT_H
                if not card.highlighted then highlight_height = 0 end
                card.T.y = G.hand.T.y - 1.8*card.T.h - highlight_height + (G.SETTINGS.reduced_motion and 0 or 1)*0.1*math.sin(0.666*G.TIMERS.REAL+card.T.x) + math.abs(1.3*(-#self.cards/2 + k-0.5)/(#self.cards))^2-0.3
                card.T.x = card.T.x + card.shadow_parrallax.x/30
            end
        end
        table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 - 100*((a.pinned and not a.ignore_pinned) and a.sort_id or 0) < b.T.x + b.T.w/2 - 100*((b.pinned and not b.ignore_pinned) and b.sort_id or 0) end)
    end  
    if self.config.type == 'hand' and not (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED) then

        for k, card in ipairs(self.cards) do
            if not card.states.drag.is then 
                card.T.r = 0.2*(-#self.cards/2 - 0.5 + k)/(#self.cards)+ (G.SETTINGS.reduced_motion and 0 or 1)*0.02*math.sin(2*G.TIMERS.REAL+card.T.x)
                local max_cards = math.max(#self.cards, self.config.temp_limit)
                card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/math.max(max_cards-1, 1) - 0.5*(#self.cards-max_cards)/math.max(max_cards-1, 1)) + 0.5*(self.card_w - card.T.w)

                local highlight_height = G.HIGHLIGHT_H
                if not card.highlighted then highlight_height = 0 end
                card.T.y = self.T.y + self.T.h/2 - card.T.h/2 - highlight_height + (G.SETTINGS.reduced_motion and 0 or 1)*0.03*math.sin(0.666*G.TIMERS.REAL+card.T.x) + math.abs(0.5*(-#self.cards/2 + k-0.5)/(#self.cards))-0.2
                card.T.x = card.T.x + card.shadow_parrallax.x/30
            end
        end
        table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 - 100*((a.pinned and not a.ignore_pinned) and a.sort_id or 0) < b.T.x + b.T.w/2 - 100*((b.pinned and not b.ignore_pinned) and b.sort_id or 0) end)
    end  
    if self.config.type == 'title' or (self.config.type == 'voucher' and #self.cards == 1) then
        for k, card in ipairs(self.cards) do
            if not card.states.drag.is then 
                card.T.r = 0.2*(-#self.cards/2 - 0.5 + k)/(#self.cards)+ (G.SETTINGS.reduced_motion and 0 or 1)*0.02*math.sin(2*G.TIMERS.REAL+card.T.x)
                local max_cards = math.max(#self.cards, self.config.temp_limit)
                card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/math.max(max_cards-1, 1) - 0.5*(#self.cards-max_cards)/math.max(max_cards-1, 1)) + 0.5*(self.card_w - card.T.w)
                local highlight_height = G.HIGHLIGHT_H
                if not card.highlighted then highlight_height = 0 end
                card.T.y = self.T.y + self.T.h/2 - card.T.h/2 - highlight_height + (G.SETTINGS.reduced_motion and 0 or 1)*0.03*math.sin(0.666*G.TIMERS.REAL+card.T.x) + math.abs(0.5*(-#self.cards/2 + k-0.5)/(#self.cards))-(#self.cards>1 and 0.2 or 0)
                card.T.x = card.T.x + card.shadow_parrallax.x/30
            end
        end
        table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 - 100*((a.pinned and not a.ignore_pinned) and a.sort_id or 0) < b.T.x + b.T.w/2 - 100*((b.pinned and not b.ignore_pinned) and b.sort_id or 0) end)
    end  
    if self.config.type == 'voucher' and #self.cards > 1 then
        local self_w = math.max(self.T.w, 3.2)
        for k, card in ipairs(self.cards) do
            if not card.states.drag.is then 
                card.T.r = 0.2*(-#self.cards/2 - 0.5 + k)/(#self.cards)+ (G.SETTINGS.reduced_motion and 0 or 1)*0.02*math.sin(2*G.TIMERS.REAL+card.T.x+card.T.y) + (k%2 == 0 and 1 or -1)*0.08
                local max_cards = math.max(#self.cards, self.config.temp_limit)
                card.T.x = self.T.x + (self_w-self.card_w)*((k-1)/math.max(max_cards-1, 1) - 0.5*(#self.cards-max_cards)/math.max(max_cards-1, 1)) + 0.5*(self.card_w - card.T.w) + (k%2 == 1 and 1 or -1)*0.27 + (self.T.w-self_w)/2
                local highlight_height = G.HIGHLIGHT_H
                if not card.highlighted then highlight_height = 0 end
                card.T.y = self.T.y + self.T.h/2 - card.T.h/2 - highlight_height + (G.SETTINGS.reduced_motion and 0 or 1)*0.03*math.sin(0.666*G.TIMERS.REAL+card.T.x) + math.abs(0.5*(-#self.cards/2 + k-0.5)/(#self.cards))-(#self.cards>1 and 0.2 or 0)
                card.T.x = card.T.x + card.shadow_parrallax.x/30
            end
        end
        table.sort(self.cards, function (a, b) return a.ability.order < b.ability.order end)
    end 
    if self.config.type == 'play' or self.config.type == 'shop' then
        for k, card in ipairs(self.cards) do
            if not card.states.drag.is then 
                card.T.r = 0
                local max_cards = math.max(#self.cards, self.config.temp_limit)
                card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/math.max(max_cards-1, 1) - 0.5*(#self.cards-max_cards)/math.max(max_cards-1, 1)) + 0.5*(self.card_w - card.T.w) + (self.config.card_limit == 1 and 0.5*(self.T.w - card.T.w) or 0)
                local highlight_height = G.HIGHLIGHT_H
                if not card.highlighted then highlight_height = 0 end
                card.T.y = self.T.y + self.T.h/2 - card.T.h/2 - highlight_height
                card.T.x = card.T.x + card.shadow_parrallax.x/30
            end
        end
        table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 - 100*((a.pinned and not a.ignore_pinned) and a.sort_id or 0) < b.T.x + b.T.w/2 - 100*((b.pinned and not b.ignore_pinned) and b.sort_id or 0) end)
    end 
    if self.config.type == 'joker' or self.config.type == 'title_2' then
        for k, card in ipairs(self.cards) do
            if not card.states.drag.is then 
                card.T.r = 0.1*(-#self.cards/2 - 0.5 + k)/(#self.cards)+ (G.SETTINGS.reduced_motion and 0 or 1)*0.02*math.sin(2*G.TIMERS.REAL+card.T.x)
                local max_cards = math.max(#self.cards, self.config.temp_limit)
                card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/math.max(max_cards-1, 1) - 0.5*(#self.cards-max_cards)/math.max(max_cards-1, 1)) + 0.5*(self.card_w - card.T.w)
                if #self.cards > 2 or (#self.cards > 1 and self == G.consumeables) or (#self.cards > 1 and self.config.spread) then
                    card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/(#self.cards-1)) + 0.5*(self.card_w - card.T.w)
                elseif #self.cards > 1 and self ~= G.consumeables then
                    card.T.x = self.T.x + (self.T.w-self.card_w)*((k - 0.5)/(#self.cards)) + 0.5*(self.card_w - card.T.w)
                else
                    card.T.x = self.T.x + self.T.w/2 - self.card_w/2 + 0.5*(self.card_w - card.T.w)
                end
                local highlight_height = G.HIGHLIGHT_H/2
                if not card.highlighted then highlight_height = 0 end
                card.T.y = self.T.y + self.T.h/2 - card.T.h/2 - highlight_height+ (G.SETTINGS.reduced_motion and 0 or 1)*0.03*math.sin(0.666*G.TIMERS.REAL+card.T.x)
                card.T.x = card.T.x + card.shadow_parrallax.x/30
            end
        end
        if not G.GAME.modifiers.cry_conveyor and not G.GAME.cry_fastened then table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 - 100*((a.pinned and not a.ignore_pinned) and a.sort_id or 0) < b.T.x + b.T.w/2 - 100*((b.pinned and not b.ignore_pinned) and b.sort_id or 0) end) end
    end   
    if self.config.type == 'consumeable'then
        for k, card in ipairs(self.cards) do
            if not card.states.drag.is then 
                if #self.cards > 1 then
                    card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/(#self.cards-1)) + 0.5*(self.card_w - card.T.w)
                else
                    card.T.x = self.T.x + self.T.w/2 - self.card_w/2 + 0.5*(self.card_w - card.T.w)
                end
                local highlight_height = G.HIGHLIGHT_H
                if not card.highlighted then highlight_height = 0 end
                card.T.y = self.T.y + self.T.h/2 - card.T.h/2 - highlight_height + (not card.highlighted and (G.SETTINGS.reduced_motion and 0 or 1)*0.05*math.sin(2*1.666*G.TIMERS.REAL+card.T.x) or 0)
                card.T.x = card.T.x + card.shadow_parrallax.x/30
            end
        end
        table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 - 100*((a.pinned and not a.ignore_pinned) and a.sort_id or 0) < b.T.x + b.T.w/2 - 100*((b.pinned and not b.ignore_pinned) and b.sort_id or 0) end)
    end   
    for k, card in ipairs(self.cards) do
        card.rank = k
    end
    if self.children.view_deck then
        self.children.view_deck:set_role{major = self.cards[1] or self}
    end
end

function CardArea:hard_set_T(X, Y, W, H)
    local x = (X or self.T.x)
    local y = (Y or self.T.y)
    local w = (W or self.T.w)
    local h = (H or self.T.h)
    Moveable.hard_set_T(self,x, y, w, h)
    self:calculate_parrallax()
    self:align_cards()
    self:hard_set_cards()
end

function CardArea:hard_set_cards()
    for k, card in pairs(self.cards) do
        card:hard_set_T()
        card:calculate_parrallax()
    end
end

function CardArea:shuffle(_seed)
    pseudoshuffle(self.cards, pseudoseed(_seed or 'shuffle'))
    self:set_ranks()
end

function CardArea:sort(method)
    self.config.sort = method or self.config.sort
    if self.config.sort == 'desc' then
        table.sort(self.cards, function (a, b) return a:get_nominal() > b:get_nominal() end )
    elseif self.config.sort == 'asc' then 
        table.sort(self.cards, function (a, b) return a:get_nominal() < b:get_nominal() end )
    elseif self.config.sort == 'suit desc' then 
        table.sort(self.cards, function (a, b) return a:get_nominal('suit') > b:get_nominal('suit') end )
    elseif self.config.sort == 'suit asc' then 
        table.sort(self.cards, function (a, b) return a:get_nominal('suit') < b:get_nominal('suit') end )
    elseif self.config.sort == 'order' then 
        table.sort(self.cards, function (a, b) return (a.config.card.order or a.config.center.order) < (b.config.card.order or b.config.center.order) end )
    end
end

function CardArea:draw_card_from(area, stay_flipped, discarded_only)
    if area:is(CardArea) then
    local prevent = false
    do
    	local _cards = discarded_only and {} or area.cards
    	local card = nil
    	if discarded_only then 
    		for k, v in ipairs(area.cards) do
    			if v.ability and v.ability.discarded then 
    				_cards[#_cards+1] = v
    			end
    		end
    	end
    	if area.config.type == 'discard' or area.config.type == 'deck' then
    		card = _cards[#_cards]
    	else
    		card = _cards[1]
    	end
    	for k, v in pairs(self.cards) do
    		if v == card then prevent = true; break end
    	end
    end
    if prevent then return end
        if #self.cards < self.config.card_limit or self == G.deck or self == G.hand then
            local card = area:remove_card(nil, discarded_only)
            if card then
                if area == G.discard then
                    card.T.r = 0
                end
                local stay_flipped = G.GAME and G.GAME.blind and G.GAME.blind:stay_flipped(self, card, area)
                if SMODS.to_area then to = SMODS.to_area; SMODS.to_area = nil end
                if (self == G.hand) and G.GAME.modifiers.flipped_cards then
                    if pseudorandom(pseudoseed('flipped_card')) < 1/G.GAME.modifiers.flipped_cards then
                        stay_flipped = true
                    end
                end
                self:emplace(card, nil, stay_flipped)
                return card
            end
        end
    end
end

function CardArea:click() 
    if self == G.deck then 
        G.FUNCS.deck_info()
    end
end

function CardArea:save()
    if not self.cards then return end
    local cardAreaTable = {
        cards = {},
        config = self.config,
    }
    for i = 1, #self.cards do
        cardAreaTable.cards[#cardAreaTable.cards + 1] = self.cards[i]:save()
    end

    return cardAreaTable
end

function CardArea:load(cardAreaTable)

    if self.cards then remove_all(self.cards) end
    self.cards = {}
    if self.children then remove_all(self.children) end
    self.children = {}

    self.config = setmetatable(cardAreaTable.config, {
        __index = function(t, key)
            if key == "card_limit" then
                return (t.card_limits.total_slots or 0) - (t.card_limits.extra_slots_used or 0)
            end
        end,
        __newindex = function(t, key, value)
            if key == 'card_limit' then
                if not t.card_limits.base then rawset(t.card_limits, 'base', value) end
                rawset(t.card_limits, 'mod', value - t.card_limits.base - (t.card_limits.extra_slots or 0) + (t.card_limits.extra_slots_used or 0))
            else
                rawset(t, key, value)
            end
        end
    })
    

    for i = 1, #cardAreaTable.cards do
        loading = true
        local card = Card(0, 0, G.CARD_W, G.CARD_H, G.P_CENTERS.j_joker, G.P_CENTERS.c_base)
        loading = nil
        card:load(cardAreaTable.cards[i])
        self.cards[#self.cards + 1] = card
        if card.highlighted then 
            self.highlighted[#self.highlighted + 1] = card
        end
        card:set_card_area(self)
    end
    self:set_ranks()
    self:align_cards()
    self:hard_set_cards()
end

function CardArea:remove()
    if self.cards then remove_all(self.cards) end
    self.cards = nil
    if self.children then remove_all(self.children) end
    self.children = nil
    for k, v in pairs(G.I.CARDAREA) do
        if v == self then
            table.remove(G.I.CARDAREA, k)
        end
    end
    Moveable.remove(self)
end
