contract ShotgunClause {

	using SafeMath for uint256;

	IShardGovernor private _shardGovernor;
	IShardRegistry private _shardRegistry;

	enum ClaimWinner { None, Claimant, Counterclaimant }
	ClaimWinner private _claimWinner = ClaimWinner.None;

	uint private _deadlineTimestamp;
	uint private _initialOfferInWei;
	uint private _pricePerShardInWei;
	address payable private _initialClaimantAddress;
	uint private _initialClaimantBalance;
	bool private _shotgunEnacted = false;
	uint private _counterWeiContributed;
	address[] private _counterclaimants;
	mapping(address => uint) private _counterclaimContribs;

	event Countercommit(address indexed committer, uint indexed weiAmount);
	event EtherCollected(address indexed collector, uint indexed weiAmount);

	constructor(
		address payable initialClaimantAddress,
		uint initialClaimantBalance,
		address shardRegistryAddress
	) public payable {
		_shardGovernor = IShardGovernor(msg.sender);
		_shardRegistry = IShardRegistry(shardRegistryAddress);
		_deadlineTimestamp = now.add(1 * 14 days);
		_initialClaimantAddress = initialClaimantAddress;
		_initialClaimantBalance = initialClaimantBalance;
		_initialOfferInWei = msg.value;
		_pricePerShardInWei = (_initialOfferInWei.mul(10**18)).div(_shardRegistry.cap().sub(_initialClaimantBalance));
		_claimWinner = ClaimWinner.Claimant;
	}

	/**
		* @notice Contribute Ether to the counterclaim for this Shotgun.
		* @dev Automatically enacts Shotgun once enough Ether is raised and
		returns initial claimant's Ether offer.
		*/
	function counterCommitEther() external payable {
		require(
			_shardRegistry.balanceOf(msg.sender) > 0,
			"[counterCommitEther] Account does not own Shards"
		);
		require(
			msg.value > 0,
			"[counterCommitEther] Ether is required"
		);
		require(
			_initialClaimantAddress != address(0),
			"[counterCommitEther] Initial claimant does not exist"
		);
		require(
			msg.sender != _initialClaimantAddress,
			"[counterCommitEther] Initial claimant cannot countercommit"
		);
		require(
			!_shotgunEnacted,
			"[counterCommitEther] Shotgun already enacted"
		);
		require(
			now < _deadlineTimestamp,
			"[counterCommitEther] Deadline has expired"
		);
		require(
			msg.value + _counterWeiContributed <= getRequiredWeiForCounterclaim(),
			"[counterCommitEther] Ether exceeds goal"
		);
		if (_counterclaimContribs[msg.sender] == 0) {
			_counterclaimants.push(msg.sender);
		}
		_counterclaimContribs[msg.sender] = _counterclaimContribs[msg.sender].add(msg.value);
		_counterWeiContributed = _counterWeiContributed.add(msg.value);
		emit Countercommit(msg.sender, msg.value);
		if (_counterWeiContributed == getRequiredWeiForCounterclaim()) {
			_claimWinner = ClaimWinner.Counterclaimant;
			enactShotgun();
		}
	}

	/**
		* @notice Collect ether from completed Shotgun.
		* @dev Called by Shard Registry after burning caller's Shards.
		* @dev For counterclaimants, returns both the proportional worth of their
		Shards in Ether AND any counterclaim contributions they have made.
		* @dev alternative: OpenZeppelin PaymentSplitter
		*/
	function collectEtherProceeds(uint balance, address payable caller) external {
		require(
			msg.sender == address(_shardRegistry),
			"[collectEtherProceeds] Caller not authorized"
		);
		if (_claimWinner == ClaimWinner.Claimant && caller != _initialClaimantAddress) {
			uint weiProceeds = (_pricePerShardInWei.mul(balance)).div(10**18);
			weiProceeds = weiProceeds.add(_counterclaimContribs[caller]);
			_counterclaimContribs[caller] = 0;
			(bool success, ) = address(caller).call.value(weiProceeds)("");
			require(success, "[collectEtherProceeds] Transfer failed.");
			emit EtherCollected(caller, weiProceeds);
		} else if (_claimWinner == ClaimWinner.Counterclaimant && caller == _initialClaimantAddress) {
			uint amount = (_pricePerShardInWei.mul(_initialClaimantBalance)).div(10**18);
			amount = amount.add(_initialOfferInWei);
			_initialClaimantBalance = 0;
			(bool success, ) = address(caller).call.value(amount)("");
			require(success, "[collectEtherProceeds] Transfer failed.");
			emit EtherCollected(caller, amount);
		}
	}

	/**
		* @notice Use by successful counterclaimants to collect Shards from initial claimant.
		*/
	function collectShardProceeds() external {
		require(
			_shotgunEnacted && _claimWinner == ClaimWinner.Counterclaimant,
			"[collectShardProceeds] Shotgun has not been enacted or invalid winner"
		);
		require(
			_counterclaimContribs[msg.sender] != 0,
			"[collectShardProceeds] Account has not participated in counterclaim"
		);
		uint proportionContributed = (_counterclaimContribs[msg.sender].mul(10**18)).div(_counterWeiContributed);
		_counterclaimContribs[msg.sender] = 0;
		uint shardsToReceive = (proportionContributed.mul(_initialClaimantBalance)).div(10**18);
		_shardGovernor.transferShards(msg.sender, shardsToReceive);
	}

	function deadlineTimestamp() external view returns (uint256) {
		return _deadlineTimestamp;
	}

	function shotgunEnacted() external view returns (bool) {
		return _shotgunEnacted;
	}

	function initialClaimantAddress() external view returns (address) {
		return _initialClaimantAddress;
	}

	function initialClaimantBalance() external view returns (uint) {
		return _initialClaimantBalance;
	}

	function initialOfferInWei() external view returns (uint256) {
		return _initialOfferInWei;
	}

	function pricePerShardInWei() external view returns (uint256) {
		return _pricePerShardInWei;
	}

	function claimWinner() external view returns (ClaimWinner) {
		return _claimWinner;
	}

	function counterclaimants() external view returns (address[] memory) {
		return _counterclaimants;
	}

	function getCounterclaimantContribution(address counterclaimant) external view returns (uint) {
		return _counterclaimContribs[counterclaimant];
	}

	function counterWeiContributed() external view returns (uint) {
		return _counterWeiContributed;
	}

	function getContractBalance() external view returns (uint) {
		return address(this).balance;
	}

	function shardGovernor() external view returns (address) {
		return address(_shardGovernor);
	}

	function getRequiredWeiForCounterclaim() public view returns (uint) {
		return (_pricePerShardInWei.mul(_initialClaimantBalance)).div(10**18);
	}

	/**
		* @notice Initiate Shotgun enactment.
		* @dev Automatically called if enough Ether is raised by counterclaimants,
		or manually called if deadline expires without successful counterclaim.
		*/
	function enactShotgun() public {
		require(
			!_shotgunEnacted,
			"[enactShotgun] Shotgun already enacted"
		);
		require(
			_claimWinner == ClaimWinner.Counterclaimant ||
			(_claimWinner == ClaimWinner.Claimant && now > _deadlineTimestamp),
			"[enactShotgun] Conditions not met to enact Shotgun Clause"
		);
		_shotgunEnacted = true;
		_shardGovernor.enactShotgun();
	}
}
/**
 	* @title Contract managing Shotgun Clause lifecycle
 	* @author Joel Hubert (Metalith.io)
 	* @dev OpenZeppelin contracts are not ready for 0.6.0 yet, using 0.5.16.
 	* @dev This contract is deployed once a Shotgun is initiated by calling the Registry.
 	*/
function collectShardProceeds() external {
	require(
		_shotgunEnacted && _claimWinner == ClaimWinner.Counterclaimant,
		"[collectShardProceeds] Shotgun has not been enacted or invalid winner"
	);
	require(
		_counterclaimContribs[msg.sender] != 0,
		"[collectShardProceeds] Account has not participated in counterclaim"
	);
	uint proportionContributed = (_counterclaimContribs[msg.sender].mul(10**18)).div(_counterWeiContributed);
	_counterclaimContribs[msg.sender] = 0;
	uint shardsToReceive = (proportionContributed.mul(_initialClaimantBalance)).div(10**18);
	_shardGovernor.transferShards(msg.sender, shardsToReceive);
}

/**
 		* @notice Use by successful counterclaimants to collect Shards from initial claimant.
 		*/