contract WrappedOrigin is ERC20, Pausable {
	using SafeMath for uint256;

	AxieCore public constant AXIE_CORE = AxieCore(0xF5b0A3eFB8e8E4c201e2A935F110eAaF3FFEcb8d);
	AxieExtraData public constant AXIE_EXTRA = AxieExtraData(0x10e304a53351B272dC415Ad049Ad06565eBDFE34);

	uint256[] public axieIds;

	event AxieWrapped(uint256 axieId);
	event AxieUnwrapped(uint256 axieId);

	constructor (string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol, _decimals)
	{}

	function isContract(address _addr) internal view returns (bool) {
		uint32 _size;
		assembly {
			_size:= extcodesize(_addr)
		}
		return (_size > 0);
	}

	function _getSeed(uint256 _seed, address _sender) internal view returns (uint256) {
		if (_seed == 0)
			return uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty, _sender)));
		else
			return uint256(keccak256(abi.encodePacked(_seed)));
	}

	// beast 0000 aqua 0100 plant 0011 bug 0001 bird 0010 reptile 0101
	function isValidCommonOrigin(uint256 _axieId) public view returns(bool) {
		(uint256 _genes,) = AXIE_CORE.getAxie(_axieId);
		uint256 _originGene = (_genes >> 238) & 1;
		if (_originGene != 1)
			return false;
		uint256 _classGenes = (_genes >> 252);
		if (!isCommonClass(_classGenes))
			return false;
		(,,,uint256 _breedCount) = AXIE_EXTRA.getExtra(_axieId);
		if (_breedCount > 2)
			return false;
		return !isMystic(_genes);
	}

	function isCommonClass(uint256 _classGene) pure internal returns (bool) {
		if (_classGene == 0 || _classGene == 3 || _classGene == 4)
			return true;
		return false;
	}

	function isMystic(uint256 _genes) pure internal returns (bool) {
		uint256 _part;
		uint256 _mysticSelector = 0xc0000000;
		for (uint256 i = 0; i < 6 ;i ++) {
			_part = _genes & 0xffffffff;
			if (_part & _mysticSelector == _mysticSelector)
				return true;
			_genes = _genes >> 32;
		}
		return false;
	}

	function wrap(uint256[] calldata _axieIdsToWrap) public notPaused {
		for (uint256 i = 0; i < _axieIdsToWrap.length; i++) {
			require(isValidCommonOrigin(_axieIdsToWrap[i]), "WrappedOrigin: Axie is not an Origin axie.");
			axieIds.push(_axieIdsToWrap[i]);
			AXIE_CORE.safeTransferFrom(msg.sender, address(this), _axieIdsToWrap[i]);
			emit AxieWrapped(_axieIdsToWrap[i]);
		}
		_mint(msg.sender, _axieIdsToWrap.length * 10**decimals);
	}

	function unwrap(uint256 _amount) public notPaused{
		require(!isContract(msg.sender), "WrappedOrigin: Address must not be a contract.");
		unwrapFor(_amount, msg.sender);
	}

	function unwrapFor(uint256 _amount, address _recipient) public notPaused {
		require(!isContract(_recipient), "WrappedOrigin: Recipient must not be a contract.");
		require(_recipient != address(0), "WrappedOrigin: Cannot send to void address.");

		_burn(msg.sender, _amount * 10**decimals);
		uint256 _seed = 0;
		for (uint256 i = 0; i < _amount; i++) {
			_seed = _getSeed(_seed, msg.sender);
			uint256 _index = _seed % axieIds.length;
			uint256 _tokenId = axieIds[_index];

			axieIds[_index] = axieIds[axieIds.length - 1];
			axieIds.pop();
			AXIE_CORE.safeTransferFrom(address(this), _recipient, _tokenId);
			emit AxieUnwrapped(_tokenId);
		}
	}

	function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external view returns (bytes4) {
		require(msg.sender == address(AXIE_CORE), "Not Axie NFT");
		return WrappedOrigin.onERC721Received.selector;
	}
}
function isValidCommonOrigin(uint256 _axieId) public view returns(bool) {
	(uint256 _genes,) = AXIE_CORE.getAxie(_axieId);
	uint256 _originGene = (_genes >> 238) & 1;
	if (_originGene != 1)
		return false;
	uint256 _classGenes = (_genes >> 252);
	if (!isCommonClass(_classGenes))
		return false;
	(,,,uint256 _breedCount) = AXIE_EXTRA.getExtra(_axieId);
	if (_breedCount > 2)
		return false;
	return !isMystic(_genes);
}

// beast 0000 aqua 0100 plant 0011 bug 0001 bird 0010 reptile 0101