contract SaleManager is SaleBase {

    /// @dev MAPINGS
    mapping (uint256 => uint256[3]) public lastTeamSalePrices;
    mapping (uint256 => uint256) public lastSingleSalePrices;
    mapping (uint256 => uint256) public seedTeamSaleCount;
    uint256 public seedSingleSaleCount = 0;

    /// @dev CONSTANTS
    uint256 public constant SINGLE_SALE_MULTIPLIER = 35;
    uint256 public constant TEAM_SALE_MULTIPLIER = 12;
    uint256 public constant STARTING_PRICE = 10 finney;
    uint256 public constant SALES_DURATION = 1 days;

    bool public isBatchSupported = true;

    /**
     * @dev        Constructor creates a reference to the MLB_NFT Sale Manager contract
     */
    constructor() public {
        require(ownerCut <= 10000); // You can't collect more than 100% silly ;)
        require(msg.sender != address(0));
        paused = true;
        gameManagerPrimary = msg.sender;
        gameManagerSecondary = msg.sender;
        bankManager = msg.sender;
    }

    /// @dev Override unpause so it requires all external contract addresses
    ///  to be set before contract can be unpaused. Also, we can't have
    ///  newContractAddress set either, because then the contract was upgraded.
    /// @notice This is public rather than external so we can call super.unpause
    ///  without using an expensive CALL.
    function unpause() public onlyGameManager whenPaused {
        require(nonFungibleContract != address(0));

        // Actually unpause the contract.
        super.unpause();
    }

    /** @dev Remove all Ether from the contract, which is the owner's cuts
     *  as well as any Ether sent directly to the contract address.
     *  Always transfers to the NFT (ERC721) contract, but can be called either by
     *  the owner or the NFT (ERC721) contract.
     */
    function _withdrawBalance() internal {
        // We are using this boolean method to make sure that even if one fails it will still work
        bankManager.transfer(address(this).balance);
    }


    /** @dev Reject all Ether from being sent here, unless it's from one of the
     *  contracts. (Hopefully, we can prevent user accidents.)
     *  @notice No tipping!
     */
    function() external payable {
        address nftAddress = address(nonFungibleContract);
        require(
            msg.sender == address(this) || 
            msg.sender == gameManagerPrimary ||
            msg.sender == gameManagerSecondary ||
            msg.sender == bankManager ||
            msg.sender == nftAddress
        );
    }

    /**
     * @dev        Creates and begins a new sale.
     * @param      _tokenId        The token identifier
     * @param      _startingPrice  The starting price
     * @param      _endingPrice    The ending price
     * @param      _duration       The duration
     * @param      _seller         The seller
     */
    function _createSale(
        uint256 _tokenId,
        uint256 _startingPrice,
        uint256 _endingPrice,
        uint256 _duration,
        address _seller
    )
        internal
    {
        Sale memory sale = Sale(
            _seller,
            _startingPrice,
            _endingPrice,
            _duration,
            now,
            [_tokenId,0,0,0,0,0,0,0,0]
        );
        _addSale(_tokenId, sale);
    }

    /**
     * @dev        Internal Function, helps in creating team sale
     * @param      _tokenIds       The token identifiers
     * @param      _startingPrice  The starting price
     * @param      _endingPrice    The ending price
     * @param      _duration       The duration
     * @param      _seller         The seller
     */
    function _createTeamSale(
        uint256[9] _tokenIds,
        uint256 _startingPrice,
        uint256 _endingPrice,
        uint256 _duration,
        address _seller)
        internal {

        Sale memory sale = Sale(
            _seller,
            _startingPrice,
            _endingPrice,
            _duration,
            now,
            _tokenIds
        );

        /// Add sale obj to all tokens
        _addTeamSale(_tokenIds, sale);
    }

    /** @dev            Cancels an sale that hasn't been won yet. Returns the MLBNFT (ERC721) to original owner.
     *  @notice         This is a state-modifying function that can be called while the contract is paused.
     */
    function cancelSale(uint256 _tokenId) external whenNotPaused {
        Sale memory sale = tokenIdToSale[_tokenId];
        require(_isOnSale(sale));
        address seller = sale.seller;
        require(msg.sender == seller);
        _cancelSale(_tokenId, seller);
    }

    /** @dev        Cancels an sale that hasn't been won yet. Returns the MLBNFT (ERC721) to original owner.
     *  @notice     This is a state-modifying function that can be called while the contract is paused. Can be only called by the GameManagers
     */
    function cancelSaleWhenPaused(uint256 _tokenId) external whenPaused onlyGameManager {
        Sale memory sale = tokenIdToSale[_tokenId];
        require(_isOnSale(sale));
        address seller = sale.seller;
        _cancelSale(_tokenId, seller);
    }

    /** 
     * @dev    Returns sales info for an CSLCollectibles (ERC721) on sale.
     * @notice Fetches the details related to the Sale
     * @param  _tokenId    ID of the token on sale
     */
    function getSale(uint256 _tokenId) external view returns (address seller, uint256 startingPrice, uint256 endingPrice, uint256 duration, uint256 startedAt, uint256[9] tokenIds) {
        Sale memory sale = tokenIdToSale[_tokenId];
        require(_isOnSale(sale));
        return (
            sale.seller,
            sale.startingPrice,
            sale.endingPrice,
            sale.duration,
            sale.startedAt,
            sale.tokenIds
        );
    }

    /**
     * @dev        Returns the current price of an sale.
     * @param      _tokenId  The token identifier
     */
    function getCurrentPrice(uint256 _tokenId) external view returns (uint256) {
        Sale memory sale = tokenIdToSale[_tokenId];
        require(_isOnSale(sale));
        return _currentPrice(sale);
    }

    /** @dev Calculates the new price for Sale Item
     * @param   _saleType     Sale Type Identifier (0 - Single Sale, 1 - Team Sale)
     * @param   _teamId       Team Identifier
     */
    function _averageSalePrice(uint256 _saleType, uint256 _teamId) internal view returns (uint256) {
        uint256 _price = 0;
        if(_saleType == 0) {
            for(uint256 ii = 0; ii < 10; ii++) {
                _price = _price.add(lastSingleSalePrices[ii]);
            }
            _price = (_price.div(10)).mul(SINGLE_SALE_MULTIPLIER.div(10));
        } else {
            for (uint256 i = 0; i < 3; i++) {
                _price = _price.add(lastTeamSalePrices[_teamId][i]);
            }
        
            _price = (_price.div(3)).mul(TEAM_SALE_MULTIPLIER.div(10));
            _price = _price.mul(9);
        }

        return _price;
    }
    
    /**
     * @dev        Put a Collectible up for sale. Does some ownership trickery to create sale in one tx.
     * @param      _tokenId        The token identifier
     * @param      _startingPrice  The starting price
     * @param      _endingPrice    The ending price
     * @param      _duration       The duration
     * @param      _owner          Owner of the token
     */
    function createSale(uint256 _tokenId, uint256 _startingPrice, uint256 _endingPrice, uint256 _duration, address _owner) external whenNotPaused {
        require(msg.sender == address(nonFungibleContract));

        // Check whether the collectible is inPlay. If inPlay cant put it on Sale
        require(nonFungibleContract.checkIsAttached(_tokenId) == 0);
        
        _escrow(_owner, _tokenId);

        // Sale throws if inputs are invalid and clears
        // transfer and sire approval after escrowing the CSLCollectible.
        _createSale(
            _tokenId,
            _startingPrice,
            _endingPrice,
            _duration,
            _owner
        );
    }

    /**
     * @dev        Put a Collectible up for sale. Only callable, if user approved contract for 1/All Collectibles
     * @param      _tokenId        The token identifier
     * @param      _startingPrice  The starting price
     * @param      _endingPrice    The ending price
     * @param      _duration       The duration
     */
    function userCreateSaleIfApproved (uint256 _tokenId, uint256 _startingPrice, uint256 _endingPrice, uint256 _duration) external whenNotPaused {
        
        require(nonFungibleContract.getApproved(_tokenId) == address(this) || nonFungibleContract.isApprovedForAll(msg.sender, address(this)));
        
        // Check whether the collectible is inPlay. If inPlay cant put it on Sale
        require(nonFungibleContract.checkIsAttached(_tokenId) == 0);
        
        _escrow(msg.sender, _tokenId);

        // Sale throws if inputs are invalid and clears
        // transfer and sire approval after escrowing the CSLCollectible.
        _createSale(
            _tokenId,
            _startingPrice,
            _endingPrice,
            _duration,
            msg.sender
        );
    }

    /** 
     * @dev        Transfers the balance of the sales manager contract to the CSLCollectible contract. We use two-step withdrawal to
     *              prevent two transfer calls in the sale bid function.
     */
    function withdrawSaleManagerBalances() public onlyBanker {
        _withdrawBalance();
    }

    /** 
     *  @dev Function to chnage the OwnerCut only accessible by the Owner of the contract
     *  @param _newCut - Sets the ownerCut to new value
     */
    function setOwnerCut(uint256 _newCut) external onlyBanker {
        require(_newCut <= 10000);
        ownerCut = _newCut;
    }
    
    /**
     * @dev        Facilitates seed collectible auction creation. Enforces strict check on the data being passed
     * @notice     Creates a new Collectible and creates an auction for it.
     * @param      _teamId            The team identifier
     * @param      _posId             The position identifier
     * @param      _attributes        The attributes
     * @param      _playerOverrideId  The player override identifier
     * @param      _mlbPlayerId       The mlb player identifier
     * @param      _startPrice        The start price
     * @param      _endPrice          The end price
     * @param      _saleDuration      The sale duration
     */
    function createSingleSeedAuction(
        uint8 _teamId,
        uint8 _posId,
        uint256 _attributes,
        uint256 _playerOverrideId,
        uint256 _mlbPlayerId,
        uint256 _startPrice,
        uint256 _endPrice,
        uint256 _saleDuration)
        public
        onlyGameManager
        whenNotPaused {
        // Check to see the NFT address is not 0
        require(nonFungibleContract != address(0));
        require(_teamId != 0);

        uint256 nftId = nonFungibleContract.createSeedCollectible(_teamId,_posId,_attributes,address(this),0, _playerOverrideId, _mlbPlayerId);

        uint256 startPrice = 0;
        uint256 endPrice = 0;
        uint256 duration = 0;
        
        if(_startPrice == 0) {
            startPrice = _computeNextSeedPrice(0, _teamId);
        } else {
            startPrice = _startPrice;
        }

        if(_endPrice != 0) {
            endPrice = _endPrice;
        } else {
            endPrice = 0;
        }

        if(_saleDuration == 0) {
            duration = SALES_DURATION;
        } else {
            duration = _saleDuration;
        }

        _createSale(
            nftId,
            startPrice,
            endPrice,
            duration,
            address(this)
        );
    }

    /**
     * @dev        Facilitates promo collectible auction creation. Enforces strict check on the data being passed
     * @notice     Creates a new Collectible and creates an auction for it.
     * @param      _teamId            The team identifier
     * @param      _posId             The position identifier
     * @param      _attributes        The attributes
     * @param      _playerOverrideId  The player override identifier
     * @param      _mlbPlayerId       The mlb player identifier
     * @param      _startPrice        The start price
     * @param      _endPrice          The end price
     * @param      _saleDuration      The sale duration
     */
    function createPromoSeedAuction(
        uint8 _teamId,
        uint8 _posId,
        uint256 _attributes,
        uint256 _playerOverrideId,
        uint256 _mlbPlayerId,
        uint256 _startPrice,
        uint256 _endPrice,
        uint256 _saleDuration)
        public
        onlyGameManager
        whenNotPaused {
        // Check to see the NFT address is not 0
        require(nonFungibleContract != address(0));
        require(_teamId != 0);

        uint256 nftId = nonFungibleContract.createPromoCollectible(_teamId, _posId, _attributes, address(this), 0, _playerOverrideId, _mlbPlayerId);

        uint256 startPrice = 0;
        uint256 endPrice = 0;
        uint256 duration = 0;
        
        if(_startPrice == 0) {
            startPrice = _computeNextSeedPrice(0, _teamId);
        } else {
            startPrice = _startPrice;
        }

        if(_endPrice != 0) {
            endPrice = _endPrice;
        } else {
            endPrice = 0;
        }

        if(_saleDuration == 0) {
            duration = SALES_DURATION;
        } else {
            duration = _saleDuration;
        }

        _createSale(
            nftId,
            startPrice,
            endPrice,
            duration,
            address(this)
        );
    }

    /**
     * @dev        Creates Team Sale Auction
     * @param      _teamId        The team identifier
     * @param      _tokenIds      The token identifiers
     * @param      _startPrice    The start price
     * @param      _endPrice      The end price
     * @param      _saleDuration  The sale duration
     */
    function createTeamSaleAuction(
        uint8 _teamId,
        uint256[9] _tokenIds,
        uint256 _startPrice,
        uint256 _endPrice,
        uint256 _saleDuration)
        public
        onlyGameManager
        whenNotPaused {

        require(_teamId != 0);

        // Helps in not creating sale with wrong team and player combination
        for(uint ii = 0; ii < _tokenIds.length; ii++){
            require(nonFungibleContract.getTeamId(_tokenIds[ii]) == _teamId);
        }
        
        uint256 startPrice = 0;
        uint256 endPrice = 0;
        uint256 duration = 0;
        
        if(_startPrice == 0) {
            startPrice = _computeNextSeedPrice(1, _teamId).mul(9);
        } else {
            startPrice = _startPrice;
        }

        if(_endPrice != 0) {
            endPrice = _endPrice;
        } else {
            endPrice = 0;
        }

        if(_saleDuration == 0) {
            duration = SALES_DURATION;
        } else {
            duration = _saleDuration;
        }

        _createTeamSale(
            _tokenIds,
            startPrice,
            endPrice,
            duration,
            address(this)
        );
    }

    /**
     * @dev        Computes the next auction starting price
     * @param      _saleType     The sale type
     * @param      _teamId       The team identifier
     */
    function _computeNextSeedPrice(uint256 _saleType, uint256 _teamId) internal view returns (uint256) {
        uint256 nextPrice = _averageSalePrice(_saleType, _teamId);

        // Sanity check to ensure we don't overflow arithmetic
        require(nextPrice == nextPrice);

        // We never auction for less than starting price
        if (nextPrice < STARTING_PRICE) {
            nextPrice = STARTING_PRICE;
        }

        return nextPrice;
    }

    /**
     * @dev        Sanity check that allows us to ensure that we are pointing to the right sale call.
     */
    bool public isSalesManager = true;

    /**
     * @dev        works the same as default bid method.
     * @param      _tokenId  The token identifier
     */
    function bid(uint256 _tokenId) public whenNotPaused payable {
        
        Sale memory sale = tokenIdToSale[_tokenId];
        address seller = sale.seller;
        uint256 price = _bid(_tokenId, msg.value);

        //If multi token sale
        if(sale.tokenIds[1] > 0) {
            
            for (uint256 i = 0; i < 9; i++) {
                _transfer(address(this), msg.sender, sale.tokenIds[i]);
            }

            //Gets Avg price
            price = price.div(9);
        } else {
            
            _transfer(address(this), msg.sender, _tokenId);
        }
        
        // If not a seed, exit
        if (seller == address(this)) {
            if(sale.tokenIds[1] > 0){
                uint256 _teamId = nonFungibleContract.getTeamId(_tokenId);

                lastTeamSalePrices[_teamId][seedTeamSaleCount[_teamId] % 3] = price;

                seedTeamSaleCount[_teamId]++;
            } else {
                lastSingleSalePrices[seedSingleSaleCount % 10] = price;
                seedSingleSaleCount++;
            }
        }
    }
    
    /**
     * @dev        Sets the address for the NFT Contract
     * @param      _nftAddress  The nft address
     */
    function setNFTContractAddress(address _nftAddress) public onlyGameManager {
        require (_nftAddress != address(0));        
        nonFungibleContract = MLBNFT(_nftAddress);
    }

    /**
     * @dev        Added this module to allow retrieve of accidental asset transfer to contract
     * @param      _to       { parameter_description }
     * @param      _tokenId  The token identifier
     */
    function assetTransfer(address _to, uint256 _tokenId) public onlyGameManager {
        require(_tokenId != 0);
        nonFungibleContract.transferFrom(address(this), _to, _tokenId);
    }

     /**
     * @dev        Added this module to allow retrieve of accidental asset transfer to contract
     * @param      _to       { parameter_description }
     * @param      _tokenIds  The token identifiers
     */
    function batchAssetTransfer(address _to, uint256[] _tokenIds) public onlyGameManager {
        require(isBatchSupported);
        require (_tokenIds.length > 0);
        
        for(uint i = 0; i < _tokenIds.length; i++){
            require(_tokenIds[i] != 0);
            nonFungibleContract.transferFrom(address(this), _to, _tokenIds[i]);
        }
    }

    /**
     * @dev        Creates new Seed Team Colelctibles
     * @param      _teamId       The team identifier
     * @param      _attributes   The attributes
     * @param      _mlbPlayerId  The mlb player identifier
     */
    function createSeedTeam(uint8 _teamId, uint256[9] _attributes, uint256[9] _mlbPlayerId) public onlyGameManager whenNotPaused {
        require(_teamId != 0);
        
        for(uint ii = 0; ii < 9; ii++) {
            nonFungibleContract.createSeedCollectible(_teamId, uint8(ii.add(1)), _attributes[ii], address(this), 0, 0, _mlbPlayerId[ii]);
        }
    }

    /**
     * @dev            Cancels an sale that hasn't been won yet. Returns the MLBNFT (ERC721) to original owner.
     * @notice         This is a state-modifying function that can be called while the contract is paused.
     */
    function batchCancelSale(uint256[] _tokenIds) external whenNotPaused {
        require(isBatchSupported);
        require(_tokenIds.length > 0);

        for(uint ii = 0; ii < _tokenIds.length; ii++){
            Sale memory sale = tokenIdToSale[_tokenIds[ii]];
            require(_isOnSale(sale));
            
            address seller = sale.seller;
            require(msg.sender == seller);

            _cancelSale(_tokenIds[ii], seller);
        }
    }

    /**
     * @dev        Helps to toggle batch supported flag
     * @param      _flag  The flag
     */
    function updateBatchSupport(bool _flag) public onlyGameManager {
        isBatchSupported = _flag;
    }

    /**
     * @dev        Batching Operation: Creates a new Collectible and creates an auction for it.
     * @param      _teamIds            The team identifier
     * @param      _posIds            The position identifier
     * @param      _attributes        The attributes
     * @param      _playerOverrideIds  The player override identifier
     * @param      _mlbPlayerIds       The mlb player identifier
     * @param      _startPrice         The start price
     */
    function batchCreateSingleSeedAuction(
        uint8[] _teamIds,
        uint8[] _posIds,
        uint256[] _attributes,
        uint256[] _playerOverrideIds,
        uint256[] _mlbPlayerIds,
        uint256 _startPrice)
        public
        onlyGameManager
        whenNotPaused {

        require (isBatchSupported);

        require (_teamIds.length > 0 &&
            _posIds.length > 0 &&
            _attributes.length > 0 &&
            _playerOverrideIds.length > 0 &&
            _mlbPlayerIds.length > 0 );
        
        // Check to see the NFT address is not 0
        require(nonFungibleContract != address(0));
        
        uint256 nftId;

        require (_startPrice != 0);

        for(uint ii = 0; ii < _mlbPlayerIds.length; ii++){
            require(_teamIds[ii] != 0);

            nftId = nonFungibleContract.createSeedCollectible(
                        _teamIds[ii],
                        _posIds[ii],
                        _attributes[ii],
                        address(this),
                        0,
                        _playerOverrideIds[ii],
                        _mlbPlayerIds[ii]);

            _createSale(
                nftId,
                _startPrice,
                0,
                SALES_DURATION,
                address(this)
            );
        }
    }
}
/* Clock sales functions and interfaces
 * @title SaleManager
 * @author Fazri Zubair & Farhan Khwaja (Lucid Sight, Inc.)
 */
function setOwnerCut(uint256 _newCut) external onlyBanker {
    require(_newCut <= 10000);
    ownerCut = _newCut;
}

/** 
 *  @dev Function to chnage the OwnerCut only accessible by the Owner of the contract
 *  @param _newCut - Sets the ownerCut to new value
 */