pragma solidity ^0.4.25;

/**
 * 
 * World War Goo - Competitive Idle Game
 * 
 * https://ethergoo.io
 * 
 */
 
 
interface ERC721 {
    function totalSupply() external view returns (uint256 tokens);
    function balanceOf(address owner) external view returns (uint256 balance);
    function ownerOf(uint256 tokenId) external view returns (address owner);
    function exists(uint256 tokenId) external view returns (bool tokenExists);
    function approve(address to, uint256 tokenId) external;
    function getApproved(uint256 tokenId) external view returns (address approvee);

    function transferFrom(address from, address to, uint256 tokenId) external;
    function tokensOf(address owner) external view returns (uint256[] tokens);
    //function tokenByIndex(uint256 index) external view returns (uint256 token);

    // Events
    event Transfer(address from, address to, uint256 tokenId);
    event Approval(address owner, address approved, uint256 tokenId);
}

interface ApproveAndCallFallBack {
    function receiveApproval(address from, uint256 tokens, address token, bytes data) external;
}


contract Clans is ERC721, ApproveAndCallFallBack {
    using SafeMath for uint256;

    GooToken constant goo = GooToken(0xdf0960778c6e6597f197ed9a25f12f5d971da86c);
    Army constant army = Army(0x98278eb74b388efd4d6fc81dd3f95b642ce53f2b);
    WWGClanCoupons constant clanCoupons = WWGClanCoupons(0xe9fe4e530ebae235877289bd978f207ae0c8bb25); // For minting clans to initial owners (prelaunch buyers)

    string public constant name = "Goo Clan";
    string public constant symbol = "GOOCLAN";
    uint224 numClans;
    address owner; // Minor management

    // ERC721 stuff
    mapping (uint256 => address) public tokenOwner;
    mapping (uint256 => address) public tokenApprovals;
    mapping (address => uint256[]) public ownedTokens;
    mapping(uint256 => uint256) public ownedTokensIndex;

    mapping(address => UserClan) public userClan;
    mapping(uint256 => uint224) public clanFee;
    mapping(uint256 => uint224) public leaderFee;
    mapping(uint256 => uint256) public clanMembers;
    mapping(uint256 => mapping(uint256 => uint224)) public clanUpgradesOwned;
    mapping(uint256 => uint256) public clanGoo;
    mapping(uint256 => address) public clanToken; // i.e. BNB
    mapping(uint256 => uint256) public baseTokenDenomination; // base value for token gains i.e. 0.000001 BNB
    mapping(uint256 => uint256) public clanTotalArmyPower;

    mapping(uint256 => uint224) public referalFee; // If invited to a clan how much % of player's divs go to referer
    mapping(address => mapping(uint256 => address)) public clanReferer; // Address of who invited player to each clan

    mapping(uint256 => Upgrade) public upgradeList;
    mapping(address => bool) operator;

    struct UserClan {
        uint224 clanId;
        uint32 clanJoinTime;
    }

    struct Upgrade {
        uint256 upgradeId;
        uint224 gooCost;
        uint224 upgradeGain;
        uint256 upgradeClass;
        uint256 prerequisiteUpgrade;
    }

    // Events
    event JoinedClan(uint256 clanId, address player, address referer);
    event LeftClan(uint256 clanId, address player);

    constructor() public {
        owner = msg.sender;
    }

    function setOperator(address gameContract, bool isOperator) external {
        require(msg.sender == owner);
        operator[gameContract] = isOperator;
    }

    function totalSupply() external view returns (uint256) {
        return numClans;
    }

    function balanceOf(address player) public view returns (uint256) {
        return ownedTokens[player].length;
    }

    function ownerOf(uint256 clanId) external view returns (address) {
        return tokenOwner[clanId];
    }

    function exists(uint256 clanId) public view returns (bool) {
        return tokenOwner[clanId] != address(0);
    }

    function approve(address to, uint256 clanId) external {
        require(tokenOwner[clanId] == msg.sender);
        tokenApprovals[clanId] = to;
        emit Approval(msg.sender, to, clanId);
    }

    function getApproved(uint256 clanId) external view returns (address) {
        return tokenApprovals[clanId];
    }

    function tokensOf(address player) external view returns (uint256[] tokens) {
         return ownedTokens[player];
    }

    function transferFrom(address from, address to, uint256 tokenId) public {
        require(tokenApprovals[tokenId] == msg.sender || tokenOwner[tokenId] == msg.sender);

        joinClanPlayer(to, uint224(tokenId), 0); // uint224 won't overflow due to tokenOwner check in removeTokenFrom()
        removeTokenFrom(from, tokenId);
        addTokenTo(to, tokenId);

        delete tokenApprovals[tokenId]; // Clear approval
        emit Transfer(from, to, tokenId);
    }

    function safeTransferFrom(address from, address to, uint256 tokenId) public {
        safeTransferFrom(from, to, tokenId, "");
    }

    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public {
        transferFrom(from, to, tokenId);
        checkERC721Recieved(from, to, tokenId, data);
    }

    function checkERC721Recieved(address from, address to, uint256 tokenId, bytes memory data) internal {
        uint256 size;
        assembly { size := extcodesize(to) }
        if (size > 0) { // Recipient is contract so must confirm recipt
            bytes4 successfullyRecieved = ERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data);
            require(successfullyRecieved == bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")));
        }
    }

    function removeTokenFrom(address from, uint256 tokenId) internal {
        require(tokenOwner[tokenId] == from);
        tokenOwner[tokenId] = address(0);

        uint256 tokenIndex = ownedTokensIndex[tokenId];
        uint256 lastTokenIndex = ownedTokens[from].length.sub(1);
        uint256 lastToken = ownedTokens[from][lastTokenIndex];

        ownedTokens[from][tokenIndex] = lastToken;
        ownedTokens[from][lastTokenIndex] = 0;

        ownedTokens[from].length--;
        ownedTokensIndex[tokenId] = 0;
        ownedTokensIndex[lastToken] = tokenIndex;
    }

    function addTokenTo(address to, uint256 tokenId) internal {
        require(ownedTokens[to].length == 0); // Can't own multiple clans
        tokenOwner[tokenId] = to;
        ownedTokensIndex[tokenId] = ownedTokens[to].length;
        ownedTokens[to].push(tokenId);
    }

    function updateClanFees(uint224 newClanFee, uint224 newLeaderFee, uint224 newReferalFee, uint256 clanId) external {
        require(msg.sender == tokenOwner[clanId]);
        require(newClanFee <= 25); // 25% max fee
        require(newReferalFee <= 10); // 10% max refs
        require(newLeaderFee <= newClanFee); // Clan gets fair cut
        clanFee[clanId] = newClanFee;
        leaderFee[clanId] = newLeaderFee;
        referalFee[clanId] = newReferalFee;
    }

    function getPlayerFees(address player) external view returns (uint224 clansFee, uint224 leadersFee, address leader, uint224 referalsFee, address referer) {
        uint256 usersClan = userClan[player].clanId;
        clansFee = clanFee[usersClan];
        leadersFee = leaderFee[usersClan];
        leader = tokenOwner[usersClan];
        referalsFee = referalFee[usersClan];
        referer = clanReferer[player][usersClan];
    }

    function getPlayersClanUpgrade(address player, uint256 upgradeClass) external view returns (uint224 upgradeGain) {
        upgradeGain = upgradeList[clanUpgradesOwned[userClan[player].clanId][upgradeClass]].upgradeGain;
    }

    function getClanUpgrade(uint256 clanId, uint256 upgradeClass) external view returns (uint224 upgradeGain) {
        upgradeGain = upgradeList[clanUpgradesOwned[clanId][upgradeClass]].upgradeGain;
    }

    // Convienence function
    function getClanDetailsForAttack(address player, address target) external view returns (uint256 clanId, uint256 targetClanId, uint224 playerLootingBonus) {
        clanId = userClan[player].clanId;
        targetClanId = userClan[target].clanId;
        playerLootingBonus = upgradeList[clanUpgradesOwned[clanId][3]].upgradeGain; // class 3 = looting bonus
    }

    function joinClan(uint224 clanId, address referer) external {
        require(exists(clanId));
        joinClanPlayer(msg.sender, clanId, referer);
    }

    // Allows smarter invites/referals in future
    function joinClanFromInvite(address player, uint224 clanId, address referer) external {
        require(operator[msg.sender]);
        joinClanPlayer(player, clanId, referer);
    }

    function joinClanPlayer(address player, uint224 clanId, address referer) internal {
        require(ownedTokens[player].length == 0); // Owners can't join

        (uint80 attack, uint80 defense,) = army.getArmyPower(player);

        // Leave old clan
        UserClan memory existingClan = userClan[player];
        if (existingClan.clanId > 0) {
            clanMembers[existingClan.clanId]--;
            clanTotalArmyPower[existingClan.clanId] -= (attack + defense);
            emit LeftClan(existingClan.clanId, player);
        }

        if (referer != address(0) && referer != player) {
            require(userClan[referer].clanId == clanId);
            clanReferer[player][clanId] = referer;
        }

        existingClan.clanId = clanId;
        existingClan.clanJoinTime = uint32(now);

        clanMembers[clanId]++;
        clanTotalArmyPower[clanId] += (attack + defense);
        userClan[player] = existingClan;
        emit JoinedClan(clanId, player, referer);
    }

    function leaveClan() external {
        require(ownedTokens[msg.sender].length == 0); // Owners can't leave

        UserClan memory usersClan = userClan[msg.sender];
        require(usersClan.clanId > 0);

        (uint80 attack, uint80 defense,) = army.getArmyPower(msg.sender);
        clanTotalArmyPower[usersClan.clanId] -= (attack + defense);

        clanMembers[usersClan.clanId]--;
        delete userClan[msg.sender];
        emit LeftClan(usersClan.clanId, msg.sender);

        // Cannot leave if player has unclaimed divs (edge case for clan fee abuse)
        require(attack + defense == 0 || army.lastWarFundClaim(msg.sender) == army.getSnapshotDay());
        require(usersClan.clanJoinTime + 24 hours < now);
    }

    function mintClan(address recipient, uint224 referalPercent, address clanTokenAddress, uint256 baseTokenReward) external {
        require(operator[msg.sender]);
        require(ERC20(clanTokenAddress).totalSupply() > 0);

        numClans++;
        uint224 clanId = numClans; // Starts from clanId 1

        // Add recipient to clan
        joinClanPlayer(recipient, clanId, 0);

        require(tokenOwner[clanId] == address(0));
        addTokenTo(recipient, clanId);
        emit Transfer(address(0), recipient, clanId);

        // Store clan token
        clanToken[clanId] = clanTokenAddress;
        baseTokenDenomination[clanId] = baseTokenReward;
        referalFee[clanId] = referalPercent;

        // Burn clan coupons from owner (prelaunch event)
        if (clanCoupons.totalSupply() > 0) {
            clanCoupons.burnCoupon(recipient, clanId);
        }
    }

    function addUpgrade(uint256 id, uint224 gooCost, uint224 upgradeGain, uint256 upgradeClass, uint256 prereq) external {
        require(operator[msg.sender]);
        upgradeList[id] = Upgrade(id, gooCost, upgradeGain, upgradeClass, prereq);
    }

    // Incase an existing token becomes invalid (i.e. migrates away)
    function updateClanToken(uint256 clanId, address newClanToken, bool shouldRetrieveOldTokens) external {
        require(msg.sender == owner);
        require(ERC20(newClanToken).totalSupply() > 0);

        if (shouldRetrieveOldTokens) {
            ERC20(clanToken[clanId]).transferFrom(this, owner, ERC20(clanToken[clanId]).balanceOf(this));
        }

        clanToken[clanId] = newClanToken;
    }

    // Incase need to tweak/balance attacking rewards (i.e. token moons so not viable to restock at current level)
    function updateClanTokenGain(uint256 clanId, uint256 baseTokenReward) external {
        require(msg.sender == owner);
        baseTokenDenomination[clanId] = baseTokenReward;
    }


    // Clan member goo deposits
    function receiveApproval(address player, uint256 amount, address, bytes) external {
        uint256 clanId = userClan[player].clanId;
        require(exists(clanId));
        require(msg.sender == address(goo));

        ERC20(msg.sender).transferFrom(player, address(0), amount);
        clanGoo[clanId] += amount;
    }

    function buyUpgrade(uint224 upgradeId) external {
        uint256 clanId = userClan[msg.sender].clanId;
        require(msg.sender == tokenOwner[clanId]);

        Upgrade memory upgrade = upgradeList[upgradeId];
        require (upgrade.upgradeId > 0); // Valid upgrade

        uint256 upgradeClass = upgrade.upgradeClass;
        uint256 latestOwned = clanUpgradesOwned[clanId][upgradeClass];
        require(latestOwned < upgradeId); // Haven't already purchased
        require(latestOwned >= upgrade.prerequisiteUpgrade); // Own prequisite

        // Clan discount
        uint224 upgradeDiscount = clanUpgradesOwned[clanId][0]; // class 0 = upgrade discount
        uint224 reducedUpgradeCost = upgrade.gooCost - ((upgrade.gooCost * upgradeDiscount) / 100);

        clanGoo[clanId] = clanGoo[clanId].sub(reducedUpgradeCost);
        army.depositSpentGoo(reducedUpgradeCost); // Transfer to goo bankroll

        clanUpgradesOwned[clanId][upgradeClass] = upgradeId;
    }

    // Goo from divs etc.
    function depositGoo(uint256 amount, uint256 clanId) external {
        require(operator[msg.sender]);
        require(exists(clanId));
        clanGoo[clanId] += amount;
    }


    function increaseClanPower(address player, uint256 amount) external {
        require(operator[msg.sender]);

        uint256 clanId = userClan[player].clanId;
        if (clanId > 0) {
            clanTotalArmyPower[clanId] += amount;
        }
    }

    function decreaseClanPower(address player, uint256 amount) external {
        require(operator[msg.sender]);

        uint256 clanId = userClan[player].clanId;
        if (clanId > 0) {
            clanTotalArmyPower[clanId] -= amount;
        }
    }


    function stealGoo(address attacker, uint256 playerClanId, uint256 enemyClanId, uint80 lootingPower) external returns(uint256) {
        require(operator[msg.sender]);

        uint224 enemyGoo = uint224(clanGoo[enemyClanId]);
        uint224 enemyGooStolen = (lootingPower > enemyGoo) ? enemyGoo : lootingPower;

        clanGoo[enemyClanId] = clanGoo[enemyClanId].sub(enemyGooStolen);

        uint224 clansShare = (enemyGooStolen * clanFee[playerClanId]) / 100;
        uint224 referersFee = referalFee[playerClanId];
        address referer = clanReferer[attacker][playerClanId];

        if (clansShare > 0 || (referersFee > 0 && referer != address(0))) {
            uint224 leaderShare = (enemyGooStolen * leaderFee[playerClanId]) / 100;

            uint224 refsShare;
            if (referer != address(0)) {
                refsShare = (enemyGooStolen * referersFee) / 100;
                goo.mintGoo(refsShare, referer);
            }

            clanGoo[playerClanId] += clansShare;
            goo.mintGoo(leaderShare, tokenOwner[playerClanId]);
            goo.mintGoo(enemyGooStolen - (clansShare + leaderShare + refsShare), attacker);
        } else {
            goo.mintGoo(enemyGooStolen, attacker);
        }
        return enemyGooStolen;
    }


    function rewardTokens(address attacker, uint256 playerClanId, uint80 lootingPower) external returns(uint256) {
        require(operator[msg.sender]);

        uint256 amount = baseTokenDenomination[playerClanId] * lootingPower;
        ERC20(clanToken[playerClanId]).transfer(attacker, amount);
        return amount;

    }

    // Daily clan dividends
    function mintGoo(address player, uint256 amount) external {
        require(operator[msg.sender]);
        clanGoo[userClan[player].clanId] += amount;
    }

}

contract ERC20 {
    function transferFrom(address from, address to, uint tokens) external returns (bool success);
    function transfer(address to, uint tokens) external returns (bool success);
    function totalSupply() external constant returns (uint);
    function balanceOf(address tokenOwner) external constant returns (uint balance);
}

contract GooToken {
    function mintGoo(uint224 amount, address player) external;
    function updatePlayersGooFromPurchase(address player, uint224 purchaseCost) external;
}

contract Army {
    mapping(address => uint256) public lastWarFundClaim; // Days (snapshot number)
    function depositSpentGoo(uint224 amount) external;
    function getArmyPower(address player) external view returns (uint80, uint80, uint80);
    function getSnapshotDay() external view returns (uint256 snapshot);
}

contract WWGClanCoupons {
    function totalSupply() external view returns (uint256);
    function burnCoupon(address clanOwner, uint256 tokenId) external;
}

contract ERC721Receiver {
    function onERC721Received(address operator, address from, uint256 tokenId, bytes data) external returns(bytes4);
}









library SafeMath {

  /**
  * @dev Multiplies two numbers, throws on overflow.
  */
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  /**
  * @dev Integer division of two numbers, truncating the quotient.
  */
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  /**
  * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  /**
  * @dev Adds two numbers, throws on overflow.
  */
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}