contract SecurityDeposit is CATContract {
    uint public depositorLimit = 100;
    uint public instanceId = 1;
    mapping(uint => SecurityInstance) public instances;
    
    event SecurityDepositCreated(uint indexed id, address indexed instOwner, string ownerNote, string depositPurpose, uint depositAmount);
    event Deposit(uint indexed id, address indexed depositor, uint depositAmount, string note);
    event DepositClaimed(uint indexed id, address indexed fromWho, uint amountClaimed);
    event RefundSent(uint indexed id, address indexed toWho, uint amountRefunded);

    event DepositorLimitChanged(uint oldLimit, uint newLimit);

    enum DepositorState {None, Active, Claimed, Refunded}
    
    struct SecurityInstance {
        uint instId;
        address instOwner;
        string ownerNote;
        string depositPurpose;
        uint depositAmount;
        mapping(address => DepositorState) depositorState;
        mapping(address => string) depositorNote;
        address[] depositors;
    }
    
    modifier onlyInstanceOwner(uint _instId) {
        require(instances[_instId].instOwner == msg.sender);
        _;
    }
    
    modifier instanceExists(uint _instId) {
        require(instances[_instId].instId == _instId);
        _;
    }

    // Chain constructor to pass along CAT payment address, and contract name
    function SecurityDeposit(address _catPaymentCollector) CATContract(_catPaymentCollector, "Security Deposit") {}
    
    function createNewSecurityDeposit(string _ownerNote, string _depositPurpose, uint _depositAmount) external blockCatEntryPoint whenNotPaused returns (uint currentId) {
        // Deposit can't be greater than maximum allowed for each user
        require(_depositAmount <= ethPerTransactionLimit);
        // Cannot have a 0 deposit security deposit
        require(_depositAmount > 0);

        currentId = instanceId;
        address instanceOwner = msg.sender;
        uint depositAmountETH = _depositAmount;
        SecurityInstance storage curInst = instances[currentId];

        curInst.instId = currentId;
        curInst.instOwner = instanceOwner;
        curInst.ownerNote = _ownerNote;
        curInst.depositPurpose = _depositPurpose;
        curInst.depositAmount = depositAmountETH;
        
        SecurityDepositCreated(currentId, instanceOwner, _ownerNote, _depositPurpose, depositAmountETH);
        instanceId++;
    }
    
    function deposit(uint _instId, string _note) external payable instanceExists(_instId) limitTransactionValue whenNotPaused {
        SecurityInstance storage curInst = instances[_instId];
        // Must deposit the right amount
        require(curInst.depositAmount == msg.value);
        // Cannot have more depositors than the limit
        require(curInst.depositors.length < depositorLimit);
        // Cannot double-deposit
        require(curInst.depositorState[msg.sender] == DepositorState.None);

        curInst.depositorState[msg.sender] = DepositorState.Active;
        curInst.depositorNote[msg.sender] = _note;
        curInst.depositors.push(msg.sender);
        
        Deposit(curInst.instId, msg.sender, msg.value, _note);
    }
    
    function claim(uint _instId, address _whoToClaim) public onlyInstanceOwner(_instId) instanceExists(_instId) whenNotPaused returns (bool) {
        SecurityInstance storage curInst = instances[_instId];
        
        // Can only call if the state is active
        if(curInst.depositorState[_whoToClaim] != DepositorState.Active) {
            return false;
        }

        curInst.depositorState[_whoToClaim] = DepositorState.Claimed;
        curInst.instOwner.transfer(curInst.depositAmount);
        DepositClaimed(_instId, _whoToClaim, curInst.depositAmount);
        
        return true;
    }
    
    function refund(uint _instId, address _whoToRefund) public onlyInstanceOwner(_instId) instanceExists(_instId) whenNotPaused returns (bool) {
        SecurityInstance storage curInst = instances[_instId];
        
        // Can only call if state is active
        if(curInst.depositorState[_whoToRefund] != DepositorState.Active) {
            return false;
        }

        curInst.depositorState[_whoToRefund] = DepositorState.Refunded;
        _whoToRefund.transfer(curInst.depositAmount);
        RefundSent(_instId, _whoToRefund, curInst.depositAmount);
        
        return true;
    }
    
    function claimFromSeveral(uint _instId, address[] _toClaim) external onlyInstanceOwner(_instId) instanceExists(_instId) whenNotPaused {
        for(uint i = 0; i < _toClaim.length; i++) {
            claim(_instId, _toClaim[i]);
        }
    }
    
    function refundFromSeveral(uint _instId, address[] _toRefund) external onlyInstanceOwner(_instId) instanceExists(_instId) whenNotPaused {
        for(uint i = 0; i < _toRefund.length; i++) {
            refund(_instId, _toRefund[i]);
        }
    }
    
    function claimAll(uint _instId) external onlyInstanceOwner(_instId) instanceExists(_instId) whenNotPaused {
        SecurityInstance storage curInst = instances[_instId];
        
        for(uint i = 0; i < curInst.depositors.length; i++) {
            claim(_instId, curInst.depositors[i]);
        }
    }
    
    function refundAll(uint _instId) external onlyInstanceOwner(_instId) instanceExists(_instId) whenNotPaused {
        SecurityInstance storage curInst = instances[_instId];
        
        for(uint i = 0; i < curInst.depositors.length; i++) {
            refund(_instId, curInst.depositors[i]);
        }
    }

    function changeDepositorLimit(uint _newLimit) external onlyOwner {
        DepositorLimitChanged(depositorLimit, _newLimit);
        depositorLimit = _newLimit;
    }
    
    // Information functions
    
    function getInstanceMetadata(uint _instId) constant external returns (address instOwner, string ownerNote, string depositPurpose, uint depositAmount) {
        instOwner = instances[_instId].instOwner;
        ownerNote = instances[_instId].ownerNote;
        depositPurpose = instances[_instId].depositPurpose;
        depositAmount = instances[_instId].depositAmount;
    }
    
    function getAllDepositors(uint _instId) constant external returns (address[]) {
        return instances[_instId].depositors;
    }
    
    function checkInfo(uint _instId, address _depositor) constant external returns (DepositorState depositorState, string note) {
        depositorState = instances[_instId].depositorState[_depositor];
        note = instances[_instId].depositorNote[_depositor];
    }

    // Metrics

    function getDepositInstanceCount() constant external returns (uint) {
        return instanceId - 1; // ID is 1-indexed
    }
}
function SecurityDeposit(address _catPaymentCollector) CATContract(_catPaymentCollector, "Security Deposit") {}

// Chain constructor to pass along CAT payment address, and contract name