contract PublicSaleContract is Ownable {

	using SafeMath for uint256;

	event Whitelist(address indexed _address, bool _isStaking);
	event Deposit(uint256 _timestamp, address indexed _address);
	event Refund(uint256 _timestamp, address indexed _address);
	event TokenReleased(uint256 _timestamp, address indexed _address, uint256 _amount);

	// Token Contract
	IERC20 tokenContract = IERC20(0x03042482d64577A7bdb282260e2eA4c8a89C064B);
	uint256 public noStakeReleaseAmount = 166666.67 ether;
	uint256 public stakeReleaseFirstBatchAmount = 83333.33 ether;
	uint256 public stakeReleaseSecondBatchAmount = 87500 ether;

	// Receiving Address
	address payable receivingAddress = 0x6359EAdBB84C8f7683E26F392A1573Ab6a37B4b4;

	// Contract status
	ContractStatus public status;

	enum ContractStatus {
		INIT, 
		ACCEPT_DEPOSIT, 
		FIRST_BATCH_TOKEN_RELEASED, 
		SECOND_BATCH_TOKEN_RELEASED
	}


	// Whitelist
	mapping(address => WhitelistDetail) whitelist;

	struct WhitelistDetail {
        // Check if address is whitelisted
        bool isWhitelisted;

        // Check if address is staking
        bool isStaking;

        // Check if address has deposited
        bool hasDeposited;
    }

	// Total count of whitelisted address
	uint256 public whitelistCount = 0;

	// Addresses that deposited
	address[] depositAddresses;
	uint256 dIndex = 0;

	// Addresses for second batch release
	address[] secondBatchAddresses;
	uint256 sIndex = 0;

	// Total count of deposits
	uint256 public depositCount = 0;

	// Deposit ticket size
	uint256 public ticketSize = 2.85 ether;

	// Duration of stake
	uint256 constant stakeDuration = 30 days;

	// Time that staking starts
	uint256 public stakeStart;

	constructor() public {
		status = ContractStatus.INIT;
	}

	function updateReceivingAddress(address payable _address) public onlyOwner {
		receivingAddress = _address;
	}

	/**
     * @dev ContractStatus.INIT functions
     */

	function whitelistAddresses(address[] memory _addresses, bool[] memory _isStaking) public onlyOwner {
		require(status == ContractStatus.INIT);

		for (uint256 i = 0; i < _addresses.length; i++) {
			if (!whitelist[_addresses[i]].isWhitelisted) {
				whitelistCount = whitelistCount.add(1);
			}

			whitelist[_addresses[i]].isWhitelisted = true;
			whitelist[_addresses[i]].isStaking = _isStaking[i];

			emit Whitelist(_addresses[i], _isStaking[i]);
		}
	}

	function updateTicketSize(uint256 _amount) public onlyOwner {
		require(status == ContractStatus.INIT);

		ticketSize = _amount;
	}

	function acceptDeposit() public onlyOwner {
		require(status == ContractStatus.INIT);

		status = ContractStatus.ACCEPT_DEPOSIT;
	}

	/**
     * @dev ContractStatus.ACCEPT_DEPOSIT functions
     */

    receive() external payable {
		deposit();
	}

	function deposit() internal {
		require(status == ContractStatus.ACCEPT_DEPOSIT);
		require(whitelist[msg.sender].isWhitelisted && !whitelist[msg.sender].hasDeposited);
		require(msg.value >= ticketSize);

		msg.sender.transfer(msg.value.sub(ticketSize));
		whitelist[msg.sender].hasDeposited = true;
		depositAddresses.push(msg.sender);
		depositCount = depositCount.add(1);

		emit Deposit(block.timestamp, msg.sender);
	}

	function refund(address payable _address) public onlyOwner {
		require(whitelist[_address].hasDeposited);

		delete whitelist[_address];
		_address.transfer(ticketSize);
		depositCount = depositCount.sub(1);

		emit Refund(block.timestamp, _address);
	}

	function refundMultiple(address payable[] memory _addresses) public onlyOwner {
		for (uint256 i = 0; i < _addresses.length; i++) {
			if (whitelist[_addresses[i]].hasDeposited) {
				delete whitelist[_addresses[i]];
				_addresses[i].transfer(ticketSize);
				depositCount = depositCount.sub(1);

				emit Refund(block.timestamp, _addresses[i]);
			}
		}
	}

	function releaseFirstBatchTokens(uint256 _count) public onlyOwner {
		require(status == ContractStatus.ACCEPT_DEPOSIT);

		for (uint256 i = 0; i < _count; i++) {
			if (whitelist[depositAddresses[dIndex]].isWhitelisted) {
				if (whitelist[depositAddresses[dIndex]].isStaking) {
					// Is staking
					tokenContract.transfer(depositAddresses[dIndex], stakeReleaseFirstBatchAmount);
					secondBatchAddresses.push(depositAddresses[dIndex]);

					emit TokenReleased(block.timestamp, depositAddresses[dIndex], stakeReleaseFirstBatchAmount);
				} else {
					// Not staking
					tokenContract.transfer(depositAddresses[dIndex], noStakeReleaseAmount);

					emit TokenReleased(block.timestamp, depositAddresses[dIndex], noStakeReleaseAmount);
				}
			}

			dIndex = dIndex.add(1);

			if (dIndex == depositAddresses.length) {
				receivingAddress.transfer(address(this).balance);
				stakeStart = block.timestamp;
				status = ContractStatus.FIRST_BATCH_TOKEN_RELEASED;
				break;
			}
		}
	}

	/**
     * @dev ContractStatus.FIRST_BATCH_TOKEN_RELEASED functions
     */

    function releaseSecondBatchTokens(uint256 _count) public onlyOwner {
		require(status == ContractStatus.FIRST_BATCH_TOKEN_RELEASED);
		require(block.timestamp > (stakeStart + stakeDuration));

		for (uint256 i = 0; i < _count; i++) {
			tokenContract.transfer(secondBatchAddresses[sIndex], stakeReleaseSecondBatchAmount);
			emit TokenReleased(block.timestamp, secondBatchAddresses[sIndex], stakeReleaseSecondBatchAmount);

			sIndex = sIndex.add(1);

			if (sIndex == secondBatchAddresses.length) {
				status = ContractStatus.SECOND_BATCH_TOKEN_RELEASED;
				break;
			}
		}
	}

	/**
     * @dev ContractStatus.SECOND_BATCH_TOKEN_RELEASED functions
     */

	function withdrawTokens() public onlyOwner {
		require(status == ContractStatus.SECOND_BATCH_TOKEN_RELEASED);

		tokenContract.transfer(receivingAddress, tokenContract.balanceOf(address(this)));
	}

}
// 
/**
 * @dev ContractStatus.ACCEPT_DEPOSIT functions
 */