// Contract: TACVoting
// Target function: function isMatchValid(uint64 matchId) public view returns (bool)
//SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     *
     * _Available since v2.4.0._
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

abstract contract ITACData {
     function balanceOf(address account) public virtual view returns (uint256);
     function transfer(address recipient, uint256 amount) public virtual returns (bool);
}

abstract contract ICoopData {
    function getUser(address _address) public virtual view returns(address userAddress, string memory displayName, uint16 locationCode,
    uint16 dojangCode, bool isMale, uint16 weight, string memory notes, uint8 numTickets, uint64[] memory matches, string memory imageURL, uint16 role);
    function getMatch(uint64 _id) public virtual view returns(uint64 id, address winner, uint8 winnerPoints,
    address loser, uint8 loserPoints, uint64 time, string memory notes, address referee);
    function getUserMatchNumber(address _address) public virtual view returns (uint256);
}

abstract contract ITACLockup {
     function getTACLocked(address user) public virtual view returns (uint256 lockedAmount);
}

 //Control who can access various functions.
contract AccessControl {
   address payable public creatorAddress;
   
   modifier onlyCREATOR() {
        require(msg.sender == creatorAddress, "You are not the creator of the contract.");
        _;
    }

   // Constructor
    constructor() public {
        creatorAddress = 0x813dd04A76A716634968822f4D30Dfe359641194;
    }
}


contract TACVoting is AccessControl {
    using SafeMath for uint256;

    /////////////////////////////////////////////////DATA STRUCTURES AND GLOBAL VARIABLES ///////////////////////////////////////////////////////////////////////
    uint256 public numElections = 0;

    //How long to open an election for
    uint64 public electionDuration = 604800;

    //The oldest any match can be to still compete. maxMatchAge of 0 means all matches are valid.
    uint64 public maxMatchAge = 0;
    
    uint256 public defaultVotes = 100000000000000000000;

    //Main data structure to hold info about a vote
    struct Vote {
        address voter; //since the address is unique this also serves as their id
        uint64 choice;
    }

    //Data structure that holds inforamtion about one of the options for the election.
    struct Option {
        uint64 matchId;
        string videoURL;
        string description;
        address bluePlayer;
    }

    //Data structure storing the information about each election.
    struct Election {
        uint64 id;
        bool isLive;
        uint64 startTime;
        mapping (address => uint16) voteId; //which choice each address selected.
        uint64 winningMatch;
    }

    mapping(uint64 => Option[]) optionsPerElection;
    mapping(uint64 => Vote[]) votesPerElection;

    //Payout percentages of the contract balance for each winning video. The number is divided 100, so each athlete received 20% of the pot.
    uint8 payoutDivisor = 5;
   
    address public TACContract = 0xABa8ace37f301E7a3A3FaD44682C8Ec8DC2BD18A;
    address public TACLockupContract = 0xbE6492206f460136921308c80D390c3D1D3f1716;
    address public coopDataContract = 0x4E81fc0Eeef51c516773Eb6b6Ec05c452B0c8F5f;
    
    //Main mapping storing an Election record for each election id.
    Election[] Elections;

    //Once you've won, the same match cannot be entered again.
    mapping(uint64 => bool) public allWinners;

    //Total number of votes each match has received. Reset after counting votes
    mapping (uint64 => uint256) public voteTotals;

    //Shows which match is currently in which election.
    mapping(uint64 => uint64) public MatchElections;

  /////////////////////////////////////////////////CONTRACT ADMIN FUNCTIONS ///////////////////////////////////////////////////////////////////////
    function setParameters(uint8 _payoutDivisor, uint64 _electionDuration, uint64 _maxMatchAge, uint256 _defaultVotes) public onlyCREATOR {
        payoutDivisor = _payoutDivisor;
        electionDuration = _electionDuration;
        maxMatchAge = _maxMatchAge;
        defaultVotes = _defaultVotes;
    }

    function setAddresss(address _TACContract, address _TACLockupContract, address _coopDataContract) public onlyCREATOR {
        TACContract = _TACContract;
        TACLockupContract = _TACLockupContract;
        coopDataContract = _coopDataContract;
    }

    //returns how many elections there have been so far.
    function getNumElections() public view returns (uint256) {
        return numElections;
    }

    //Returns the contract's TAC balance.
    function getTACBalance() public view returns (uint256 balance) {
        ITACData TAC = ITACData(TACContract);
        balance = TAC.balanceOf(address(this));
    }

 
     /////////////////////////////////////////////////ELECTION FUNCTIONS ///////////////////////////////////////////////////////////////////////
     
    function init() public {
        //Make sure there currently isn't an open election.
        require(numElections == 0, "The contract has already been initialized");
        Election memory election;
        election.id = uint64(numElections);
        election.startTime = uint64(now);
        election.winningMatch = 0;
        election.isLive = true;
        Elections.push(election);
        numElections ++;
    }     
     
    function openElection() public {
        //Make sure there currently isn't an open election.
        require(Elections[numElections-1].isLive == false, "The current election must finish before you can open another one");
        //Open the new election
        Election memory election;
        election.id = uint64(numElections);
        election.startTime = uint64(now);
        election.winningMatch = 0;
        election.isLive = true;
        Elections.push(election);
        numElections ++;
    }

    //Function called to add a match to an election. 
    function joinElection(uint64 electionId, uint64 matchId, string memory videoURL, string memory description, address bluePlayer) public {
        //Anyone can join - UI limits to match players.
        //Make sure this match is valid
        require(isMatchValid(matchId) == true, "Sorry, but you can't submit this match to an election.");
        //Make sure the election is the proper one to join
        checkElection(electionId);
        //Add the option to the election
        require(MatchElections[matchId] != electionId || electionId == 0, "Sorry, but this match is already in this election");
        Option memory option;
        option.matchId = matchId;
        option.videoURL = videoURL;
        option.description = description;
        option.bluePlayer = bluePlayer;
        optionsPerElection[electionId].push(option);
        MatchElections[matchId] = electionId;
    }


    //Function to return election information. 
    function getElection(uint64 electionId) public view returns (uint64 id, bool isLive, uint64 startTime, Option[] memory options, Vote[] memory votes, uint64 winningMatch) {
        id=Elections[electionId].id;
        isLive = Elections[electionId].isLive;
        startTime = Elections[electionId].startTime;
        options = optionsPerElection[electionId];
        votes = votesPerElection[electionId];
        winningMatch = Elections[electionId].winningMatch;
    }
    
    //Returns information about each choice a voter could make. 
    function getElectionOptions(uint64 electionId, uint16 optionNumber) public view returns (uint64 option, string memory videoURL, string memory description) {
        option = optionsPerElection[electionId][optionNumber].matchId; 
        videoURL = optionsPerElection[electionId][optionNumber].videoURL; 
        description = optionsPerElection[electionId][optionNumber].description;
    }