// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {Army} from "../src/RaidGeldStructs.sol";

library RaidGeldUtils {
    uint256 public constant PRECISION = 10000;
    uint256 constant PRICE_FACTOR = 11500;

    // base price * (0.00666) * 11 per each next unit
    uint256 constant MOLOCH_DENIER_PROFIT = 2533;
    uint256 constant APPRENTICE_PROFIT = 27863;
    uint256 constant ANOINTED_PROFIT = 306493;
    uint256 constant CHAMPION_PROFIT = 3371423;

    // each costs 10 times plus a bit more
    uint256 constant MOLOCH_DENIER_BASE_COST = 380000;
    uint256 constant APPRENTICE_BASE_COST = 3420000;
    uint256 constant ANOINTED_BASE_COST = 30096000;
    uint256 constant CHAMPION_BASE_COST = 255816000;

    uint256 constant RISK_BETA = 15e17;

    function getBossPower(uint8 level) internal pure returns (uint256 power) {
        require(level <= 7, "Bosses only go to level 7");
        if (level == 0) return 9000000;            // 900e4
        else if (level == 1) return 90000000;      // 900e5
        else if (level == 2) return 900000000;     // 900e6
        else if (level == 3) return 9000000000;    // 900e7
        else if (level == 4) return 90000000000;   // 900e8
        else if (level == 5) return 900000000000;  // 900e9
        else if (level == 6) return 9000000000000;  // 900e10
    }

    function getBossCumulativeChance(uint8 level) internal pure returns (uint256 chance) {
        // for boss chances (percent) [99, 89, 80, 70, 62, 51, 40]
        // where cumulative chance is in the form of 18 precision related to 1e18
        require(level <= 7, "Bosses only go to level 7");
        if (level == 0) return 99e16;            
        else if (level == 1) return 8811e14;      
        else if (level == 2) return 7048e14;     
        else if (level == 3) return 493416e12;    
        else if (level == 4) return 30591792e10;   
        else if (level == 5) return 1560181392e8;  
        else if (level == 6) return 6240725568e7;
    }

    function calculateUnitPrice(uint8 unit, uint16 currentLevel, uint16 units) internal pure returns (uint256) {
        require(unit <= 3, "No matching unit found");
        uint256 rollingPriceCalculation = MOLOCH_DENIER_BASE_COST;
        uint256 price = 0;
        if (unit == 1) {
            rollingPriceCalculation = APPRENTICE_BASE_COST;
        } else if (unit == 2) {
            rollingPriceCalculation = ANOINTED_BASE_COST;
        } else if (unit == 3) {
            rollingPriceCalculation = CHAMPION_BASE_COST;
        }

        // Each level costs 15% more than previous
        for (uint256 i = 0; i < currentLevel + units; i++) {
            if (i >= currentLevel) {
                price += rollingPriceCalculation;
            }
            rollingPriceCalculation = rollingPriceCalculation * PRICE_FACTOR / PRECISION;
        }
        return price;
    }

    function calculateProfitsPerSecond(Army memory army) internal pure returns (uint256) {
        // Each next unit scales progressivelly better
        uint256 moloch_denier_profit = army.moloch_denier.level * MOLOCH_DENIER_PROFIT;
        uint256 apprentice_profit = army.apprentice.level * APPRENTICE_PROFIT;
        uint256 anointed_profit = army.anointed.level * ANOINTED_PROFIT;
        uint256 champion_profit = army.champion.level * CHAMPION_PROFIT;
        return moloch_denier_profit + apprentice_profit + anointed_profit + champion_profit;
    }

    // Returns how much Dao Token player is due for winning
    function calculateBossReward(uint8 bossLevel) internal pure returns (uint256) {
        return getBossPower(bossLevel) * (getBossCumulativeChance(bossLevel) * 10 / 5) ** 2;
    }

    // Calculates whether user survives the fight
    function calculateBossFight(uint8 bossLevel, uint256 geldBurnt, uint256 prevrandao)
        internal
        pure
        returns (bool)
    {
        uint256 bossPower = getBossPower(bossLevel);
        require(geldBurnt <= bossPower, "Cant try to defeat boss with more than what boss power is");
        uint256 random_n = random(prevrandao, 1, 100);
        // Relative power as in, you can only put in 800 geld to defeat 900 geld boss,
        // but you will get exponentially worse chances
        uint256 relativePower = ((geldBurnt ** 2) * 100) / bossPower ** 2;
        uint256 roll = (random_n * relativePower * PRECISION) / (100 * PRECISION);
        return roll >= bossPower;
    }

    // TODO: Implement actual randomness
    function random(uint256 prevrandao, uint256 min, uint256 max) internal pure returns (uint256) {
        // returns 0 - 100
        require(max >= min, "Max must be greater than or equal to min");
        uint256 range = max - min + 1;
        return min + (uint256(keccak256(abi.encodePacked(prevrandao))) % range);
    }
}
