// Collects information about item counts and minimum prices to SameItemInfo and updates EligibleItems - a list with new items eligible for bot to buy and bid
// Returns count of items in AH that were eligible for being bought or bidded on by ahbot buyer (EligibleItems size)
uint32 AuctionBotBuyer::GetItemInformation(BuyerConfiguration& config)
{
    config.SameItemInfo.clear();
    time_t now = time(nullptr);
    uint32 count = 0;

    AuctionHouseObject* house = sAuctionMgr->GetAuctionsMap(config.GetHouseType());
    for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = house->GetAuctionsBegin(); itr != house->GetAuctionsEnd(); ++itr)
    {
        AuctionEntry* entry = itr->second;
        Item* item = sAuctionMgr->GetAItem(entry->itemGUIDLow);
        if (!item)
            continue;

        BuyerItemInfo& itemInfo = config.SameItemInfo[item->GetEntry()];

        // Update item entry's count and total bid prices
        // This can be used later to determine the prices and chances to bid
        uint32 itemBidPrice = entry->startbid / item->GetCount();
        itemInfo.TotalBidPrice = itemInfo.TotalBidPrice + itemBidPrice;
        itemInfo.BidItemCount++;

        // Set minimum bid price
        if (!itemInfo.MinBidPrice)
            itemInfo.MinBidPrice = itemBidPrice;
        else
            itemBidPrice = std::min(itemInfo.MinBidPrice, itemBidPrice);

        // Set minimum buyout price if item has buyout
        if (entry->buyout)
        {
            // Update item entry's count and total buyout prices
            // This can be used later to determine the prices and chances to buyout
            uint32 itemBuyPrice = entry->buyout / item->GetCount();
            itemInfo.TotalBuyPrice = itemInfo.TotalBuyPrice + itemBuyPrice;
            itemInfo.BuyItemCount++;

            if (!itemInfo.MinBuyPrice)
                itemInfo.MinBuyPrice = itemBuyPrice;
            else
                itemInfo.MinBuyPrice = std::min(itemInfo.MinBuyPrice, itemBuyPrice);
        }

        // Add/update to EligibleItems if:
        // has a bid by player or
        // has no bids and not owned by bot
        if ((entry->bid && entry->bidder) || (entry->owner && !entry->bid))
        {
            config.EligibleItems[entry->Id].LastExist = now;
            config.EligibleItems[entry->Id].AuctionId = entry->Id;
            ++count;
        }
    }

    TC_LOG_DEBUG("ahbot", "AHBot: %u items added to buyable/biddable vector for ah type: %u", count, config.GetHouseType());
    TC_LOG_DEBUG("ahbot", "AHBot: SameItemInfo size = %u", (uint32)config.SameItemInfo.size());
    return count;
}
void AuctionBotBuyer::LoadBuyerValues(BuyerConfiguration& config)
{
    uint32 factionChance;

    switch (config.GetHouseType())
    {
        case AUCTION_HOUSE_ALLIANCE:
            config.BuyerPriceRatio = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ALLIANCE_PRICE_RATIO) + 50;
            factionChance = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_RATIO_ALLIANCE);
            break;
        case AUCTION_HOUSE_HORDE:
            config.BuyerPriceRatio = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_HORDE_PRICE_RATIO) + 50;
            factionChance = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_RATIO_HORDE);
            break;
        default:
            config.BuyerPriceRatio = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_NEUTRAL_PRICE_RATIO) + 50;
            factionChance = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYER_CHANCE_RATIO_NEUTRAL);
            break;
    }

    config.FactionChance = 5000 * factionChance;
}
// Tries to bid and buy items based on their prices and chances set in configs
void AuctionBotBuyer::BuyAndBidItems(BuyerConfiguration& config)
{
    time_t now = time(nullptr);
    AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(config.GetHouseType());
    CheckEntryMap& items = config.EligibleItems;

    // Max amount of items to buy or bid
    uint32 cycles = sAuctionBotConfig->GetItemPerCycleNormal();
    if (items.size() > sAuctionBotConfig->GetItemPerCycleBoost())
    {
        // set more cycles if there is a huge influx of items
        cycles = sAuctionBotConfig->GetItemPerCycleBoost();
        TC_LOG_DEBUG("ahbot", "AHBot: Boost value used for Buyer! (if this happens often adjust both ItemsPerCycle in worldserver.conf)");
    }

    // Process items eligible to be bidded or bought
    CheckEntryMap::iterator itr = items.begin();
    while (cycles && itr != items.end())
    {
        AuctionEntry* auction = auctionHouse->GetAuction(itr->second.AuctionId);
        if (!auction)
        {
            TC_LOG_DEBUG("ahbot", "AHBot: Entry %u doesn't exists, perhaps bought already?", itr->second.AuctionId);
            items.erase(itr++);
            continue;
        }

        // Check if the item has been checked once before
        // If it has been checked and it was recently, skip it
        if (itr->second.LastChecked && (now - itr->second.LastChecked) <= _checkInterval)
        {
            TC_LOG_DEBUG("ahbot", "AHBot: In time interval wait for entry %u!", auction->Id);
            ++itr;
            continue;
        }

        Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow);
        if (!item)
        {
            // auction item not accessible, possible auction in payment pending mode
            items.erase(itr++);
            continue;
        }

        // price to bid if bidding
        uint32 bidPrice;
        if (auction->bid >= auction->startbid)
        {
            // get bid price to outbid previous bidder
            bidPrice = auction->bid + auction->GetAuctionOutBid();
        }
        else
        {
            // no previous bidders - use starting bid
            bidPrice = auction->startbid;
        }

        const BuyerItemInfo* ahInfo = nullptr;
        BuyerItemInfoMap::const_iterator sameItemItr = config.SameItemInfo.find(item->GetEntry());
        if (sameItemItr != config.SameItemInfo.end())
            ahInfo = &sameItemItr->second;

        TC_LOG_DEBUG("ahbot", "AHBot: Rolling for AHentry %u:", auction->Id);

        // Roll buy and bid chances
        bool successBuy = RollBuyChance(ahInfo, item, auction, bidPrice);
        bool successBid = RollBidChance(ahInfo, item, auction, bidPrice);

        // If roll bidding succesfully and bid price is above buyout -> buyout
        // If roll for buying was successful but not for bid, buyout directly
        // If roll bidding was also successful, buy the entry with 20% chance
        // - Better bid than buy since the item is bought by bot if no player bids after
        // Otherwise bid if roll for bid was successful
        if ((auction->buyout && successBid && bidPrice >= auction->buyout) ||
            (successBuy && (!successBid || urand(1, 5) == 1)))
            BuyEntry(auction, auctionHouse); // buyout
        else if (successBid)
            PlaceBidToEntry(auction, bidPrice); // bid

        itr->second.LastChecked = now;
        --cycles;
        ++itr;
    }

    // Clear not needed entries
    config.SameItemInfo.clear();
}
uint32 AuctionBotBuyer::GetBuyableEntry(BuyerConfiguration& config)
{
    config.SameItemInfo.clear();
    uint32 count = 0;
    time_t now = time(nullptr);

    AuctionHouseObject* house = sAuctionMgr->GetAuctionsMap(config.GetHouseType());
    for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = house->GetAuctionsBegin(); itr != house->GetAuctionsEnd(); ++itr)
    {
        AuctionEntry* entry = itr->second;
        Item* item = sAuctionMgr->GetAItem(entry->itemGUIDLow);
        if (item)
        {
            ItemTemplate const * prototype = item->GetTemplate();
            if (prototype)
            {
                ++config.SameItemInfo[item->GetEntry()].ItemCount;    // Structure constructor will make sure Element are correctly initialized if entry is created here.
                config.SameItemInfo[item->GetEntry()].BuyPrice = config.SameItemInfo[item->GetEntry()].BuyPrice + (itr->second->buyout / item->GetCount());
                config.SameItemInfo[item->GetEntry()].BidPrice = config.SameItemInfo[item->GetEntry()].BidPrice + (itr->second->startbid / item->GetCount());
                if (itr->second->buyout != 0)
                {
                    if (itr->second->buyout / item->GetCount() < config.SameItemInfo[item->GetEntry()].MinBuyPrice)
                        config.SameItemInfo[item->GetEntry()].MinBuyPrice = itr->second->buyout / item->GetCount();
                    else if (config.SameItemInfo[item->GetEntry()].MinBuyPrice == 0)
                        config.SameItemInfo[item->GetEntry()].MinBuyPrice = itr->second->buyout / item->GetCount();
                }
                if (itr->second->startbid / item->GetCount() < config.SameItemInfo[item->GetEntry()].MinBidPrice)
                    config.SameItemInfo[item->GetEntry()].MinBidPrice = itr->second->startbid / item->GetCount();
                else if (config.SameItemInfo[item->GetEntry()].MinBidPrice == 0)
                    config.SameItemInfo[item->GetEntry()].MinBidPrice = itr->second->startbid / item->GetCount();

                if (!entry->owner)
                {

                    if (entry->bid != 0 && entry->bidder) // Add bid by player
                    {
                        config.CheckedEntry[entry->Id].LastExist = now;
                        config.CheckedEntry[entry->Id].AuctionId = entry->Id;
                        ++count;
                    }
                }
                else
                {
                    if (entry->bid != 0)
                    {
                        if (entry->bidder)
                        {
                            config.CheckedEntry[entry->Id].LastExist = now;
                            config.CheckedEntry[entry->Id].AuctionId = entry->Id;
                            ++count;
                        }
                    }
                    else
                    {
                        config.CheckedEntry[entry->Id].LastExist = now;
                        config.CheckedEntry[entry->Id].AuctionId = entry->Id;
                        ++count;
                    }
                }
            }
        }
    }

    TC_LOG_DEBUG("ahbot", "AHBot: %u items added to buyable vector for ah type: %u", count, config.GetHouseType());
    TC_LOG_DEBUG("ahbot", "AHBot: SameItemInfo size = %u", (uint32)config.SameItemInfo.size());
    return count;
}
void AuctionBotBuyer::AddNewAuctionBuyerBotBid(BuyerConfiguration& config)
{
    AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(config.GetHouseType());

    PrepareListOfEntry(config);

    time_t now = time(nullptr);
    uint32 buyCycles;
    if (config.CheckedEntry.size() > sAuctionBotConfig->GetItemPerCycleBoost())
    {
        buyCycles = sAuctionBotConfig->GetItemPerCycleBoost();
        TC_LOG_DEBUG("ahbot", "AHBot: Boost value used for Buyer! (if this happens often adjust both ItemsPerCycle in worldserver.conf)");
    }
    else
        buyCycles = sAuctionBotConfig->GetItemPerCycleNormal();

    for (CheckEntryMap::iterator itr = config.CheckedEntry.begin(); itr != config.CheckedEntry.end();)
    {
        AuctionEntry* auction = auctionHouse->GetAuction(itr->second.AuctionId);
        if (!auction) // is auction not active now
        {
            TC_LOG_DEBUG("ahbot", "AHBot: Entry %u doesn't exists, perhaps bought already?",
                itr->second.AuctionId);

            config.CheckedEntry.erase(itr++);
            continue;
        }

        if (itr->second.LastChecked != 0 && (now - itr->second.LastChecked) <= _checkInterval)
        {
            TC_LOG_DEBUG("ahbot", "AHBot: In time interval wait for entry %u!", auction->Id);
            ++itr;
            continue;
        }

        if (buyCycles == 0)
            break;

        uint32 maxChance = 5000;

        Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow);
        if (!item) // auction item not accessible, possible auction in payment pending mode
        {
            config.CheckedEntry.erase(itr++);
            continue;
        }

        ItemTemplate const* prototype = item->GetTemplate();

        uint32 basePrice = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYPRICE_BUYER) ? prototype->BuyPrice : prototype->SellPrice;
        basePrice *= item->GetCount();

        uint32 maxBuyablePrice = (basePrice * config.BuyerPriceRatio) / 100;
        BuyerItemInfoMap::iterator sameItemItr = config.SameItemInfo.find(item->GetEntry());
        uint32 buyoutPrice = auction->buyout / item->GetCount();

        uint32 bidPrice;
        uint32 bidPriceByItem;
        uint32 minBidPrice;
        uint32 minBuyPrice;
        if (auction->bid >= auction->startbid)
        {
            bidPrice = auction->GetAuctionOutBid();
            bidPriceByItem = auction->bid / item->GetCount();
        }
        else
        {
            bidPrice = auction->startbid;
            bidPriceByItem = auction->startbid / item->GetCount();
        }

        double inGameBuyPrice;
        double inGameBidPrice;
        if (sameItemItr == config.SameItemInfo.end())
        {
            inGameBuyPrice = 0;
            inGameBidPrice = 0;
            minBidPrice = 0;
            minBuyPrice = 0;
        }
        else
        {
            if (sameItemItr->second.ItemCount == 1)
                maxBuyablePrice = maxBuyablePrice * 5; // if only one item exist can be bought if the price is high too.
            inGameBuyPrice = sameItemItr->second.BuyPrice / sameItemItr->second.ItemCount;
            inGameBidPrice = sameItemItr->second.BidPrice / sameItemItr->second.ItemCount;
            minBidPrice = sameItemItr->second.MinBidPrice;
            minBuyPrice = sameItemItr->second.MinBuyPrice;
        }

        uint32 maxBidablePrice = maxBuyablePrice - (maxBuyablePrice / 30); // Max Bidable price defined to 70% of max buyable price

        TC_LOG_DEBUG("ahbot", "AHBot: Auction added with data:");
        TC_LOG_DEBUG("ahbot", "AHBot: MaxPrice of Entry %u is %.1fg.", itr->second.AuctionId, double(maxBuyablePrice) / 10000.0);
        TC_LOG_DEBUG("ahbot", "AHBot: GamePrice buy=%.1fg, bid=%.1fg.", inGameBuyPrice / 10000, inGameBidPrice / 10000);
        TC_LOG_DEBUG("ahbot", "AHBot: Minimal price see in AH Buy=%ug, Bid=%ug.",
            minBuyPrice / 10000, minBidPrice / 10000);
        TC_LOG_DEBUG("ahbot", "AHBot: Actual Entry price,  Buy=%ug, Bid=%ug.", buyoutPrice / 10000, bidPrice / 10000);

        if (!auction->owner)                // Original auction owner
            maxChance = maxChance / 5;      // if Owner is AHBot this mean player placed bid on this auction. We divide by 5 chance for AhBuyer to place bid on it. (This make more challenge than ignore entry)
        if (auction->buyout != 0)           // Is the item directly buyable?
        {
            if (IsBuyableEntry(buyoutPrice, inGameBuyPrice, maxBuyablePrice, minBuyPrice, maxChance, config.FactionChance))
            {
                if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance / 2, config.FactionChance))
                {
                    if (urand(0, 5) == 0)
                        PlaceBidToEntry(auction, bidPrice);
                    else
                        BuyEntry(auction);
                }
                else
                    BuyEntry(auction);
            }
            else if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance / 2, config.FactionChance))
                PlaceBidToEntry(auction, bidPrice);
        }
        else if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance, config.FactionChance))
            PlaceBidToEntry(auction, bidPrice);

        itr->second.LastChecked = now;
        --buyCycles;

        ++itr;
    }
}