Pointshop2View = class( "Pointshop2View" )
Pointshop2View.static.controller = "Pointshop2Controller"
Pointshop2View:include( BaseView )

LibK.InitPostEntityPromise:Done( function( )
    for k, ply in pairs( player.GetAll( ) ) do
        ply.PS2_EquippedItems = ply.PS2_EquippedItems or {}
        ply.PS2_Slots = ply.PS2_Slots or {}
    end
    LocalPlayer().PS2_Inventory = LocalPlayer().PS2_Inventory or {}

    timer.Simple( 0.5, function( )
        Pointshop2View:getInstance( ) --Create the view
    end )
end )

local GLib = LibK.GLib

function Pointshop2View:initialize( )
    self.SlotChanges = {} -- Prevents uncaching of items during slot changes

    --Dynamic Properties
    self.itemMappings = {}
    self.itemCategories = {}
    self.itemProperties = {}

    self.fullyInitialized = false

    self.clPromises = {
        InventoryReceived = Deferred( ),
        OutfitsReceived = Deferred( ),
        SettingsReceived = Deferred( ),
        DynamicsReceived = Deferred( ),
        SlotsReceived = Deferred( )
    }

    local promises = {}
    for k, v in pairs( self.clPromises ) do
        table.insert( promises, v:Promise( ) )
    end

    Pointshop2.ClInitialized = WhenAllFinished( promises )

    Pointshop2.ClInitialized:Done( function( )
        self.fullyInitialized = true
        KLogf( 5, "[Pointshop2] Pointshop2 is now ready for use" )
    end )
end

local function notifyError()
    if not IsValid(LocalPlayer()) then
        timer.Simple(2, notifyError)
        return
    end

    if LocalPlayer():IsAdmin() and ( not pac or not pace ) then
        Pointshop2View:getInstance():displayError( "[CRITICAL][ADMIN ONLY] PAC3 is not installed correctly. Expect errors! Please install PAC3 from GitHub: bit.ly/getpac3", 1000 )
    end

    if LocalPlayer():IsAdmin() and ( not pac.FlashlightDisable or not pace.selectControl ) then
        Pointshop2View:getInstance():displayError( "[CRITICAL][ADMIN ONLY] Your PAC3 version is outdated and will cause errors. Please download the newest PAC version from GitHub: bit.ly/getpac3.", 1000 )
    end
end
hook.Add("InitPostEntity", "ErrorNotifier", function() timer.Simple(2, notifyError) end)

local function resolveIfWaiting( deferred )
    if deferred._promise._state == "pending" then
        deferred:Resolve( )
    end
end

function Pointshop2View:toggleMenu( )
    if self.loaderAttached then
        --In the loading message, handlers are already registered
        return
    end

    if self.fullyInitialized then
        local startTime = SysTime( )
        Pointshop2:ToggleMenu( )
        KLogf( 5, "[Pointshop 2] Menu opened in %s", GLib.FormatDuration( SysTime() - startTime ) )
    else
        self.loaderAttached = true

        local f = vgui.Create("DFrame" )
        f:SetSkin( Pointshop2.Config.DermaSkin )
        f:SetSize( 300, 130 )
        f:SetPos( ScrW() / 2 - f:GetWide( ) / 2, 0 )
        f:SetTitle( "Pointshop 2" )

        local lbl = vgui.Create( "DLabel", f )
        lbl:SetText( "Loading Pointshop2, please wait a second.\nThe menu will open automatically." )
        lbl:Dock( TOP )
        lbl:SizeToContents( )

        local loading = vgui.Create( "DLoadingNotifier", f )
        loading:Dock( TOP )
        loading:Expand( )

        Pointshop2.ClInitialized:Done( function( )
            Pointshop2:ToggleMenu( ) --Open the menu
        end )
        :Fail( function( errid, err )
            self:displayError( "There was an error loading Pointshop 2. Please tell an admin: " .. errid .. ": " .. err )
        end )
        :Always( function( )
            f:Remove( )
            self.loaderAttached = false
        end )
    end
end

function Pointshop2View:showRepairDatabase( message )
    local notification = vgui.Create( "KNotificationPanel" )
    notification:setText( message )
    notification:setIcon( "icon16/exclamation.png" )
    notification.sound = "kreport/electric_deny2.wav"
    notification:SetSkin( Pointshop2.Config.DermaSkin )
    notification.duration = 360

    local btn = vgui.Create( "DButton", notification )
    btn:SetText( "Repair Database" )
    function btn.DoClick( )
        self:controllerAction( "fixDatabase" )
    end
    btn:SizeToContents()
    btn:Dock( TOP )
    btn:DockMargin( 5, 5, 5, 5 )
    btn:SetTall( 30 )
    notification:SetMouseInputEnabled( true )
    btn:SetMouseInputEnabled( true )

    LocalPlayer( ).notificationPanel:addNotification( notification )
end

function Pointshop2View:HandleDecodeError( func, errClass )
    KLogf( 1, "Error decoding call for %s, faulty class %s", func, errClass )
    if not LocalPlayer():IsAdmin() then
        self:displayError( "There was an error loading Pointshop 2 Data. Please tell an admin: Error calling " .. func .. " faulty class: " .. errClass )
    else
        self:showRepairDatabase( "[ADMIN ONLY] There was an error loading Pointshop 2 Data. The recommended step is to try repairing the database. The worst thing that can happen is that some items end up in uncategorized.\nIf you want to be 100% sure please take a database backup before running the repair!\n You need reset permissions to be able to perform this action." )
    end
end

function Pointshop2View:walletChanged( newWallet, tries )
    tries = tries or 0
    local ply
    for k, v in pairs( player.GetAll( ) ) do
        if v:GetNWInt( "KPlayerId" ) == newWallet.ownerId then
            v.PS2_Wallet = newWallet
            KLogf( 5, "[PS2] Received Wallet of %s: %i pts, %i premPts", v:Nick( ), newWallet.points, newWallet.premiumPoints )
            ply = v
        end
    end
    if not ply and tries < 100 then
        timer.Simple( 0.5, function( )
            self:walletChanged( newWallet, tries + 1 )
        end )
    end
    hook.Run( "PS2_WalletChanged", newWallet, ply )
end

function Pointshop2View:adminRemoveItem( ply, itemId )
    return self:controllerTransaction( "adminRemoveItem", itemId )
end

function Pointshop2View:receiveInventory( inventory )
    InventoryView:getInstance( ):receiveInventory( inventory ) --Needed for KInventory to work properly
    LocalPlayer().PS2_Inventory = inventory
    KLogf( 5, "[PS2] Received Inventory, %i items", #inventory:getItems( ) )
    for k, v in pairs( inventory:getItems( ) ) do
        KInventory.ITEMS[v.id] = v
    end
    hook.Run( "PS2_InvUpdate" )
    resolveIfWaiting( self.clPromises.InventoryReceived )
end

function Pointshop2View:receiveSlots( slots )
    LocalPlayer().PS2_Slots = LocalPlayer().PS2_Slots or {}

    -- Handle removed slots
    for slotName, item in pairs(LocalPlayer().PS2_Slots) do
        if not slots[slotName] or not slots[slotName].Item then
            LocalPlayer().PS2_Slots[slotName] = nil
        end
    end

    for slotName, item in pairs( slots ) do
        LocalPlayer().PS2_Slots[slotName] = item
        KInventory.ITEMS[item.id] = item
    end

    KLogf( 5, "[PS2] Received slots, %i slots", table.Count( slots ) )
    resolveIfWaiting( self.clPromises.SlotsReceived )
end

function Pointshop2View:itemAddedToSlot( slotName, itemId, item )
    item = item or KInventory.ITEMS[itemId]
    if not item then
        GLib.Error( "Invalid item added to slot message" )
    end

    LocalPlayer().PS2_Slots[slotName] = item

    hook.Run( "PS2_ItemAddedToSlot", slotName, itemId )
end

function Pointshop2View:startBuyItem( itemClass, currencyType )
    return self:controllerTransaction( "buyItem", itemClass.className, currencyType )
    :Fail( function( err )
        self:displayError( "Error buying the item: " .. err )
    end )
end

function Pointshop2View:startSellItems( itemIds )
    local def = Deferred()

    local forceConfirm = hook.Run( "PS2_ShouldForceItemSellConfirmation", itemIds )
    if Pointshop2.ClientSettings.GetSetting( "BasicSettings.AutoconfirmSale" ) and not forceConfirm then
        def:Resolve( true )
    else
        Derma_Query( "Are you sure you want to sell " .. #itemIds .. " items?", "Selling multiple items", "Yes", function()
            def:Resolve( true )
        end, "No", function()
            def:Resolve( false )
        end )
    end

    return def:Promise( ):Then( function( confirmed )
        if confirmed then
            return self:controllerTransaction( "sellItems", itemIds ):Done( function( )
                --Reset selection
                hook.Run( "PS2_InvItemIconSelected" )
                hook.Run( "PS2_AfterItemsSold", itemIds )
            end )
        end
    end ):Fail( function( err )
        self:displayError( "Error selling the item: " .. err )
    end )
end

function Pointshop2View:startSellItem( item )
    local itemIds = { item.id }
    local forceConfirm = hook.Run( "PS2_ShouldForceItemSellConfirmation", itemIds )

    local def = Deferred()
    if Pointshop2.ClientSettings.GetSetting( "BasicSettings.AutoconfirmSale" ) and not forceConfirm then
        def:Resolve( true )
    else
        Derma_Query( "Are you sure you want to sell " .. item:GetPrintName() .. "?", "Selling an item", "Yes", function()
            def:Resolve( true )
        end, "No", function()
            def:Resolve( false )
        end )
    end

    return def:Promise( ):Then( function( confirmed )
        if confirmed then
            return self:controllerTransaction( "sellItem", item.id ):Done( function( )
                hook.Run( "PS2_AfterItemsSold", itemIds )
            end )
        end
    end ):Fail( function( err )
        self:displayError( "Error selling the item: " .. err )
    end )
end

function Pointshop2View:receiveDynamicProperties( itemMappings, itemCategories, itemProperties )
    KLogf( 5, "[PS2] Received Dynamic Properties, %i items in %i categories (%i props)", #itemMappings, #itemCategories, #itemProperties )
    self.itemMappings = itemMappings
    self.itemCategories = itemCategories
    self.itemProperties = itemProperties

    return Pointshop2View:getInstance( ).clPromises.SettingsReceived:Then( function()
        --Load persistent items
        local startTime = SysTime( )
        for _, v in pairs( self.itemProperties ) do
            Pointshop2.LoadPersistentItem( v )
        end
        KLogf( 5, "[Pointshop 2] Persistent items loaded in %s", GLib.FormatDuration( SysTime() - startTime ) )

        startTime = SysTime( )
        local tree = Pointshop2.BuildTree( self.itemCategories, self.itemMappings )
        KLogf( 5, "[Pointshop 2] Tree created in %s", GLib.FormatDuration( SysTime() - startTime ) )

        self.categoryItemsTable = tree or {}

        --Hacky, dunno why this is needed
        --hook.Call( "PS2_DynamicItemsUpdated" )
        timer.Simple( 0.1, function( )
            local startTimeDynamics = SysTime( )
            hook.Call( "PS2_DynamicItemsUpdated" )
            KLogf( 5, "[Pointshop 2] Hook run in %s", GLib.FormatDuration( SysTime() - startTimeDynamics ) )
        end )

        resolveIfWaiting( self.clPromises.DynamicsReceived )
    end )
end

/*
    Called when a shop item (item persistence) has been updated
*/
function Pointshop2View:updateItemPersistences( itemPersistences )
    if not self.itemProperties then
        KLogf(3, "[WARN] Player received item update but items weren't loaded yet")
        return
    end

    for _, itemPersistence in pairs( itemPersistences ) do
        -- Update persistence object cache
        for k, v in pairs( self.itemProperties ) do
            if v.itemPersistenceId == itemPersistence.itemPersistenceId then
                self.itemProperties[k] = itemPersistence
            end
        end

        -- Update class (KInventory.Items[id])
        local itemClass = Pointshop2.LoadPersistentItem( itemPersistence )

        -- Regenerate item
        if LibK.DermaInherits( itemClass:GetPointshopIconControl( ), "DCsgoItemIcon" ) then
            Pointshop2.RequestIcon( itemClass, true )
        end

        KLogf( 5, "[Pointshop 2] Reloaded item %s", itemPersistence.ItemPersistence.name )
    end

    -- Update Shop
    timer.Simple( 0.1, function( )
        local startTime = SysTime( )
        hook.Call( "PS2_DynamicItemsUpdated" )
        KLogf( 5, "[Pointshop 2] Hook run in %s", GLib.FormatDuration( SysTime() - startTime ) )
    end )
end

function Pointshop2View:loadDynamics( versionHash )
    GLib.Resources.Resources["Pointshop2/dynamics"] = nil --Force resource reset
    GLib.Resources.Get( "Pointshop2", "dynamics", versionHash, function( success, data )
        if not success then
            KLogf( 2, "[PS2][ERROR] Couldn't load dynamics resouce!" )
            return
        end

        local startTime = SysTime( )
        local dynamicsDecoded = util.JSONToTable( data )[1]
        KLogf( 5, "[PS2] Decoded dynamic info from resource (version %s) %s", versionHash, GLib.FormatDuration( SysTime() - startTime ) )

        self.dynamicsDecoded = dynamicsDecoded

        --Back to classes
        LibK.processNetTable( dynamicsDecoded[1] )
        LibK.processNetTable( dynamicsDecoded[2] )
        LibK.processNetTable( dynamicsDecoded[3] )

        --Pass to view
        local dynamicsHandled = self:receiveDynamicProperties( dynamicsDecoded[1], dynamicsDecoded[2], dynamicsDecoded[3] )

        --Inform server
        local kPlayerIdValid = Deferred()
        timer.Create( "CheckValidPlayerId", 0.1, 0, function()
            local loaded = true
            for _, v in pairs( player.GetAll( ) ) do
                if v == LocalPlayer( ) and v:GetNWInt( "KPlayerId", -1 ) == -1 then
                    loaded = false
                end
            end
            if loaded then
                kPlayerIdValid:Resolve( )
                timer.Remove( "CheckValidPlayerId" )
            end
        end )
        WhenAllFinished{
            kPlayerIdValid:Promise(),
            dynamicsHandled
        }:Then(function()
            self:controllerAction( "dynamicsReceived" )
            KLogf(5, "[Pointshop 2] Dynamics loaded, ready to receive server calls")
        end)
    end )
end

function Pointshop2View:getPersistenceForClass( itemClass )
    if itemClass._persistenceId == "STATIC" then
        return "STATIC"
    end
    local persistenceClass = Pointshop2.GetPersistenceClassForItemClass( itemClass )
    for _, v in pairs( self.itemProperties ) do
        if v.id == itemClass._persistenceId and instanceOf( persistenceClass, v ) then
            return v
        end
    end
    return nil
end

function Pointshop2View:saveCategoryOrganization( categoryItemsTable )
    GLib.Transfers.Send( 0, "Pointshop2.CategoryOrganization", util.TableToJSON( categoryItemsTable ) )
end

function Pointshop2View:equipItem( item, slotName )
    if item then
        if LocalPlayer().PS2_Slots[slotName] and LocalPlayer().PS2_Slots[slotName].id == item.id then
            return
        end
        self:controllerAction( "equipItem", item.id, slotName )
    else
        self:controllerAction( "unequipItem", slotName )
    end
end

function Pointshop2View:displayItemAddedNotify( item, text )
    local notification = vgui.Create( "DItemReceivedNotification" )
    notification:SetItem( item )
    if text and isstring( text ) then
        notification.lbl:SetText( text )
    end
    LocalPlayer( ).notificationPanel:addNotification( notification )
end

function Pointshop2View:getCategoryOrganization( )
    if not self.categoryItemsTable then
        return KLogf( 2, "[PS2] Couldn't create items table: nothing received from server yet!" )
    end

    return self.categoryItemsTable
end

function Pointshop2View:getShopCategory( )
    if not self.categoryItemsTable then
        return KLogf( 2, "[PS2] Couldn't create items table: nothing received from server yet!" )
    end

    for _, v in pairs( self.categoryItemsTable.subcategories or {} ) do
        if v.self.label == "Shop Categories" then
            return v
        end
    end

    return {
        items = {},
        subcategories = {}
    }
end

function Pointshop2View:getNoSaleCategory( )
    if not self.categoryItemsTable then
        return KLogf( 2, "[PS2] Couldn't create items table: nothing received from server yet!" )
    end

    for _, v in pairs( self.categoryItemsTable.subcategories or { } ) do
        if v.self.label == "Not for sale Items" then
            return v
        end
    end

    return {
        items = { },
        subcategories = { }
    }
end

function Pointshop2View:RegenerateIcons( )
    for _, itemClass in pairs( Pointshop2:GetRegisteredItems( ) ) do
        if not derma.Controls[itemClass:GetPointshopIconControl()] then
            print(itemClass:GetPointshopIconControl())
            continue
        end

         if LibK.DermaInherits( itemClass:GetPointshopIconControl(), "DCsgoItemIcon" ) then
            Pointshop2.RequestIcon( itemClass, true )
        end
    end
end

function Pointshop2View:getUncategorizedItems( )
    local uncategorized = {}
    for _, itemClass in pairs( Pointshop2:GetRegisteredItems( ) ) do
        local found = false
        for _, itemMapping in pairs( self.itemMappings ) do
            if itemMapping.itemClass == itemClass.className then
                found = true
            end
        end
        if not found then
            table.insert( uncategorized, itemClass )
        end
    end
    return uncategorized
end

function Pointshop2View:createPointshopItem( saveTable )
    hook.Run( "PS2_PreReload" )
    self:controllerAction( "saveModuleItem", saveTable )
end

local function handleEquip( ply, item )
    ply.PS2_EquippedItems = ply.PS2_EquippedItems or {}
    ply.PS2_EquippedItems[item.id] = item
    item.owner = ply

    if item.class:IsValidForServer( Pointshop2.GetCurrentServerId( ) ) then
        Pointshop2.ActivateItemHooks( item )
        item:OnEquip( )
    end

    hook.Run( "PS2_ItemEquipped", ply, item )
end

-- Here only the itemId and slot name is sent since we already have the item cached
function Pointshop2View:localPlayerEquipItem( itemId, slotName )
    if getPromiseState( self.clPromises.SlotsReceived ) != "done" then
        KLogf( 4, "Got equip information before Slots were received, deferring %s", getPromiseState( self.clPromises.SlotsReceived ) )
    end

    self.clPromises.SlotsReceived:Done( function( )
        local item = KInventory.ITEMS[itemId]
        if not item then
            GLib.Error( "Got equip for uncached item" )
        end

        LocalPlayer( ).PS2_Slots[slotName] = item
        handleEquip( LocalPlayer(), item )
    end )
end

-- Here the full item is sent since we dont have it yet.
function Pointshop2View:playerEquipItem( kPlayerId, item, isRetry )
    isRetry = isRetry or 0

    -- Invalid
    if not item then
        GLib.Error( "Invalid item in playerEquipItem" )
    end

    local ply
    for _, v in pairs( player.GetAll( ) ) do
        if tonumber( v:GetNWInt( "KPlayerId" ) ) == tonumber( kPlayerId ) then
            ply = v
        end
    end

    if not IsValid( ply ) then
        if isRetry >= 10 then
            KLogf( 4, "[PS2] Couldn't get it on retry D:" )
            return
        end
        if isRetry < 10 then
            KLogf( 4, "[PS2] Player equip on player that is not valid, trying again in 3s" )
            timer.Simple( 3, function( )
                self:playerEquipItem( kPlayerId, item, isRetry + 1 )
            end )
        end
        return
    end

    KInventory.ITEMS[item.id] = item
    handleEquip( ply, item )
end

-- Called to update slots when player moves an item from one slot to another.
-- We don't need to update equip hooks in this case. slotName is the slot
-- the item was moved out of.
function Pointshop2View:itemSlotSwapped( slotName )
    LocalPlayer( ).PS2_Slots[slotName] = nil
end

function Pointshop2View:playerUnequipItem( ply, itemId )
    local item = KInventory.ITEMS[itemId]
    if not item then
        GLib.Error( "Invalid itemId in playerUnequipItem" )
    end

    if ply == LocalPlayer() then
        for slotName, itemInSlot in pairs( ply.PS2_Slots ) do
            if itemInSlot.id == itemId then
                if itemInSlot != item then
                    dpt(itemInSlot)
                    dpt(item)
                    MsgC( Color(255, 0, 0), "Assertion Failure: Cached item does not match slot item\n" )
                    MsgC( Color(255, 0, 0), GLib.StackTrace (nil, 1) )
                end
                ply.PS2_Slots[slotName] = nil
                hook.Run( "PS2_ItemRemovedFromSlot", slotName, item )
            end
        end

    end

    item:OnHolster( ply )
    Pointshop2.DeactivateItemHooks( item )

    if ply.PS2_EquippedItems then
        ply.PS2_EquippedItems[itemId] = nil
    end

    -- Hook
    hook.Run( "PS2_ItemUnequipped", ply, item )
end

function Pointshop2View:loadOutfits( versionHash )
    GLib.Resources.Resources["Pointshop2/outfits"] = nil --Force resource reset
    GLib.Resources.Get( "Pointshop2", "outfits", versionHash, function( success, data )
        if not success then
            KLogf( 2, "[PS2][ERROR] Couldn't load outfits resouce!" )
            return
        end
        Pointshop2.Outfits = util.JSONToTable( data )[1]
        KLogf( 5, "[PS2] Decoded %i outfits from resource (version %s)", #Pointshop2.Outfits, versionHash )
        self:controllerAction( "outfitsReceived" )

        resolveIfWaiting( Pointshop2View:getInstance( ).clPromises.OutfitsReceived )
    end )
end

function Pointshop2View:searchPlayers( subject, attribute )
    return self:controllerTransaction( "searchPlayers", subject, attribute )
end

function Pointshop2View:getUserDetails( kPlayerId )
    if not kPlayerId or type( kPlayerId ) != "number" then
        error( "Invalid Call" )
        debug.Trace( )
        return
    end
    return self:controllerTransaction( "getUserDetails", kPlayerId )
end

function Pointshop2View:adminChangeWallet( kPlayerId, currencyType, newValue )
    return self:controllerTransaction( "adminChangeWallet", kPlayerId, currencyType, newValue )
end

function Pointshop2View:addToPointFeed( message, points, small )
    Pointshop2.PointFeed:AddPointNotification( message, points, small )
end

function Pointshop2View:loadSettings( versionHash )
    GLib.Resources.Resources["Pointshop2/settings"] = nil --Force resource reset
    GLib.Resources.Get( "Pointshop2", "settings", versionHash, function( success, data )
        if not success then
            KLogf( 2, "[PS2][ERROR] Couldn't load settings resouce!" )
            return
        end
        Pointshop2.Settings.Shared = util.JSONToTable( data )[1]
        KLogf( 5, "[PS2] Decoded settings from resource (version %s)", versionHash )

        resolveIfWaiting( Pointshop2View:getInstance( ).clPromises.SettingsReceived )
        hook.Run( "PS2_OnSettingsUpdate" )
    end )
end

function Pointshop2View:saveSettings( mod, realm, settingsTbl )
    self.settingsPromises = self.settingsPromises or {}
    self.settingsPromises[mod.Name] = Deferred( )

    local outBuffer = GLib.StringOutBuffer( )
    outBuffer:String( mod.Name )
    outBuffer:String( realm )
    outBuffer:LongString( util.TableToJSON( settingsTbl ) )

    GLib.Transfers.Send( GLib.GetServerId( ), "Pointshop2.SettingsUpdate", outBuffer:GetString( ) )
    hook.Run( "PS2_SettingsSavingStart" )
    return self.settingsPromises[mod.Name]:Promise( )
end

-- This is called when a client saves serverside settings. Used to create visual indicator
-- of server settings saving.
function Pointshop2View:serverSettingsSaved( modName, err )
    if self.settingsPromises then
        if err then
            self.settingsPromises[modName]:Reject( err )
        else
            self.settingsPromises[modName]:Resolve( )
        end
    end
    hook.Run("PS2_ServerSettingsSaved")
end

function Pointshop2View:sendPoints( ply, points )
    return self:controllerTransaction( "sendPoints", ply, points )
end

function Pointshop2View:resetToDefaults( )
    self:controllerAction( "resetToDefaults" )
end

function Pointshop2View:fixDatabase( )
    self:controllerAction( "fixDatabase" )
end

function Pointshop2View:removeItem( itemClass, refund )
    hook.Run( "PS2_PreReload" )

    self:controllerTransaction( "removeItem", itemClass.className, refund )
    :Done( function( )
        KInventory.Items[itemClass.className] = nil
    end )
end

function Pointshop2View:removeItems( itemClasses, refund )
    hook.Run( "PS2_PreReload" )

    local classNames = {}
    for _, v in pairs( itemClasses ) do
        table.insert( classNames, v.className )
    end

    self:controllerTransaction( "removeItems", classNames, refund )
    :Done( function( removedNames )
        for _, className in pairs( removedNames ) do
            KInventory.Items[className] = nil
        end
    end )
end

function Pointshop2View:installDefaults( )
    self:controllerAction( "installDefaults" )
end

function Pointshop2View:installDlcPack( name )
    self:controllerAction( "installDlcPack", name )
end

function Pointshop2View:getServers( )
    return self:controllerTransaction( "adminGetServers" )
end

function Pointshop2View:migrateServer( server )
    return self:controllerTransaction( "migrateServer", server.id )
end

function Pointshop2View:removeServer( server )
    return self:controllerTransaction( "removeServer", server.id )
end

function Pointshop2View:updateServerRestrictions( itemClassNames, serverIds )
    hook.Run( "PS2_PreReload" )
    self:controllerAction( "updateServerRestrictions", itemClassNames, serverIds )
end

function Pointshop2View:requestMaterials( directory )
    return self:controllerTransaction( "requestMaterials", directory )
end

function Pointshop2View:adminGiveItem( kPlayerId, itemClass )
    return self:controllerTransaction( "adminGiveItem", kPlayerId, itemClass )
end

function Pointshop2View:displayInformation( infoStr, duration )
    local notification = vgui.Create( "KNotificationPanel" )
    notification:setText( infoStr )
    notification:setIcon( "icon16/information.png" )
    notification:SetSkin( "KReport" )
    notification.sound = "kreport/retro_deny.wav"
    if duration then
        notification.duration = duration
    end
    LocalPlayer( ).notificationPanel:addNotification( notification )

    return notification
end

function Pointshop2View:displayError( infoStr, duration )
    local notification = vgui.Create( "KNotificationPanel" )
    notification:setText( infoStr or "<nil>" )
    notification:setIcon( "icon16/exclamation.png" )
    notification.sound = "kreport/electric_deny2.wav"
    notification:SetSkin( "KReport" )
    if duration then
        notification.duration = duration
    end
    LocalPlayer( ).notificationPanel:addNotification( notification )

    return notification
end

function Pointshop2View:updateRankRestrictions( itemClassNames, validRanks )
    hook.Run( "PS2_PreReload" )
    self:controllerAction( "updateRankRestrictions", itemClassNames, validRanks )
end
