contract LTVCreditorProxy is CreditorProxyCore, LTVDecisionEngine {

	mapping (bytes32 => bool) public debtOfferCancelled;
	mapping (bytes32 => bool) public debtOfferFilled;

	bytes32 constant internal NULL_ISSUANCE_HASH = bytes32(0);

	function LTVCreditorProxy(address _contractRegistry) LTVDecisionEngine(_contractRegistry)
		public
	{
		contractRegistry = ContractRegistryInterface(_contractRegistry);
	}

	function fillDebtOffer(LTVDecisionEngineTypes.Params params)
		public returns (bytes32 agreementId)
	{
		OrderLibrary.DebtOrder memory order = params.order;
		CommitmentValues memory commitmentValues = params.creditorCommitment.values;

		bytes32 creditorCommitmentHash = hashCreditorCommitmentForOrder(commitmentValues, order);

		if (!evaluateConsent(params, creditorCommitmentHash)) {
			emit CreditorProxyError(uint8(Errors.DEBT_OFFER_NON_CONSENSUAL), order.creditor, creditorCommitmentHash);
			return NULL_ISSUANCE_HASH;
		}

		if (debtOfferFilled[creditorCommitmentHash]) {
			emit CreditorProxyError(uint8(Errors.DEBT_OFFER_ALREADY_FILLED), order.creditor, creditorCommitmentHash);
			return NULL_ISSUANCE_HASH;
		}

		if (debtOfferCancelled[creditorCommitmentHash]) {
			emit CreditorProxyError(uint8(Errors.DEBT_OFFER_CANCELLED), order.creditor, creditorCommitmentHash);
			return NULL_ISSUANCE_HASH;
		}

		if (!evaluateDecision(params)) {
			emit CreditorProxyError(
				uint8(Errors.DEBT_OFFER_CRITERIA_NOT_MET),
				order.creditor,
				creditorCommitmentHash
			);
			return NULL_ISSUANCE_HASH;
		}

		address principalToken = order.principalToken;

		// The allowance that the token transfer proxy has for this contract's tokens.
		uint tokenTransferAllowance = getAllowance(
			principalToken,
			address(this),
			contractRegistry.tokenTransferProxy()
		);

		uint totalCreditorPayment = order.principalAmount.add(order.creditorFee);

		// Ensure the token transfer proxy can transfer tokens from the creditor proxy
		if (tokenTransferAllowance < totalCreditorPayment) {
			require(setTokenTransferAllowance(principalToken, totalCreditorPayment));
		}

		// Transfer principal from creditor to CreditorProxy
		if (totalCreditorPayment > 0) {
			require(
				transferTokensFrom(
					principalToken,
					order.creditor,
					address(this),
					totalCreditorPayment
				)
			);
		}

		agreementId = sendOrderToKernel(order);

		require(agreementId != NULL_ISSUANCE_HASH);

		debtOfferFilled[creditorCommitmentHash] = true;

		contractRegistry.debtToken().transfer(order.creditor, uint256(agreementId));

		emit DebtOfferFilled(order.creditor, creditorCommitmentHash, agreementId);

		return agreementId;
	}

	function sendOrderToKernel(DebtOrder memory order) internal returns (bytes32 id)
	{
		address[6] memory orderAddresses;
		uint[8] memory orderValues;
		bytes32[1] memory orderBytes32;
		uint8[3] memory signaturesV;
		bytes32[3] memory signaturesR;
		bytes32[3] memory signaturesS;

		(orderAddresses, orderValues, orderBytes32, signaturesV, signaturesR, signaturesS) = unpackDebtOrder(order);

		return contractRegistry.debtKernel().fillDebtOrder(
			address(this),
			orderAddresses,
			orderValues,
			orderBytes32,
			signaturesV,
			signaturesR,
			signaturesS
		);
	}

	function cancelDebtOffer(LTVDecisionEngineTypes.Params params) public returns (bool) {
		LTVDecisionEngineTypes.CommitmentValues memory commitmentValues = params.creditorCommitment.values;
		OrderLibrary.DebtOrder memory order = params.order;

		// sender must be the creditor.
		require(msg.sender == order.creditor);

		bytes32 creditorCommitmentHash = hashCreditorCommitmentForOrder(commitmentValues, order);

		// debt offer must not already be filled.
		require(!debtOfferFilled[creditorCommitmentHash]);

		debtOfferCancelled[creditorCommitmentHash] = true;

		emit DebtOfferCancelled(order.creditor, creditorCommitmentHash);

		return true;
	}

	/**
     * Helper function for approving this address' allowance to Dharma's token transfer proxy.
     */
	function setTokenTransferAllowance(
		address token,
		uint amount
	)
		internal
		returns (bool _success)
	{
		return ERC20(token).approve(
			address(contractRegistry.tokenTransferProxy()),
			amount
		);
	}
}
// Internal interfaces
// Internal mixins
function setTokenTransferAllowance(
	address token,
	uint amount
)
	internal
	returns (bool _success)
{
	return ERC20(token).approve(
		address(contractRegistry.tokenTransferProxy()),
		amount
	);
}

/**
 * Helper function for approving this address' allowance to Dharma's token transfer proxy.
 */