contract PassCommitteeRoom is PassCommitteeRoomInterface {

// Constant functions

    function ShareManager() constant returns (PassManager) {
        return PassManager(passDao.ActualShareManager());
    }
    
    function TokenManager() constant returns (PassManager) {
        return PassManager(passDao.ActualTokenManager());
    }
    
    function Balance() constant returns (uint) {
        return passDao.ActualShareManager().balance;
    }

    function HasVoted(
        uint _committeeID, 
        address _shareHolder) constant external returns (bool) {

        if (_shareHolder == 0) return hasVoted[_committeeID][msg.sender];
        else return hasVoted[_committeeID][_shareHolder];
    }
    
    function minQuorum() constant returns (uint) {
        return (uint(ShareManager().totalSupply()) / rulesProposals[0].minQuorumDivisor);
    }

    function numberOfCommittees() constant returns (uint) {
        return Committees.length - 1;
    }
    
// Constructor and init functions

    function PassCommitteeRoom(address _passDao) {

        passDao = PassDao(_passDao);
        rulesProposals.length = 1; 
        Committees.length = 1;
        Proposals.length = 1;
        ResolutionProposals.length = 1;
        UpgradeProposals.length = 1;
    }
    
    function init(
        uint _maxInflationRate,
        uint _minMinutesPeriods,
        uint _minQuorumDivisor,
        uint _minCommitteeFees,
        uint _minPercentageOfLikes,
        uint _minutesSetProposalPeriod,
        uint _minMinutesDebatePeriod,
        uint _feesRewardInflationRate,
        uint _tokenPriceInflationRate,
        uint _defaultMinutesFundingPeriod) {

        maxInflationRate = _maxInflationRate;
        minMinutesPeriods = _minMinutesPeriods;
        CommitteeLimits(maxInflationRate, minMinutesPeriods);
        
        if (rulesProposals[0].minQuorumDivisor != 0) throw;
        rulesProposals[0].minQuorumDivisor = _minQuorumDivisor;
        rulesProposals[0].minCommitteeFees = _minCommitteeFees;
        rulesProposals[0].minPercentageOfLikes = _minPercentageOfLikes;
        rulesProposals[0].minutesSetProposalPeriod = _minutesSetProposalPeriod;
        rulesProposals[0].minMinutesDebatePeriod = _minMinutesDebatePeriod;
        rulesProposals[0].feesRewardInflationRate = _feesRewardInflationRate;
        rulesProposals[0].tokenPriceInflationRate = _tokenPriceInflationRate;
        rulesProposals[0].defaultMinutesFundingPeriod = _defaultMinutesFundingPeriod;

    }

// Project manager and contractor management

    function createContractor(
        PassContractorCreator _contractorCreator,
        address _recipient,
        bool _metaProject,
        PassProject _passProject,
        string _projectName, 
        string _projectDescription) returns (PassContractor) {

        PassContractor _contractor = _contractorCreator.createContractor(msg.sender, _recipient, 
            _metaProject, _passProject, _projectName, _projectDescription, false);
        ContractorCreated(_contractorCreator, msg.sender, _contractor, _recipient);
        return _contractor;
    }   

// Proposals Management

    function contractorProposal(
        uint _amount,
        PassContractor _contractor,
        uint _contractorProposalID,
        string _proposalDescription, 
        bytes32 _hashOfTheContractorProposalDocument,        
        address _moderator,
        uint _initialSharePriceMultiplier, 
        uint _minutesFundingPeriod,
        uint _minutesDebatingPeriod
    ) payable returns (uint) {

        if (_minutesFundingPeriod == 0) _minutesFundingPeriod = rulesProposals[0].defaultMinutesFundingPeriod;

        if (address(_contractor) != 0 && _contractorProposalID != 0) {
            if (_hashOfTheContractorProposalDocument != 0 
                ||!_contractor.proposalChecked(msg.sender, _contractorProposalID, _amount)) throw;
            else _proposalDescription = "Proposal checked";
        }

        if ((address(_contractor) != 0 && _contractorProposalID == 0 && _hashOfTheContractorProposalDocument == 0)
            || _amount == 0
            || _minutesFundingPeriod < minMinutesPeriods) throw;

        uint _proposalID = Proposals.length++;
        Proposal p = Proposals[_proposalID];

        p.contractor = _contractor;
        
        if (_contractorProposalID == 0 && _hashOfTheContractorProposalDocument != 0) {
            _contractorProposalID = _contractor.newProposal(msg.sender, _amount, _proposalDescription, _hashOfTheContractorProposalDocument);
        }
        p.contractorProposalID = _contractorProposalID;

        if (address(_contractor) == 0) p.amountForShares = _amount;
        else {
            _contractor.submitProposal(msg.sender, _contractorProposalID, _amount);
            if (_contractor.Project().ProjectManager() == address(_contractor)) p.amountForTokens = _amount;
            else {
                p.amount = Balance();
                if (_amount > p.amount) p.amountForShares = _amount - p.amount;
                else p.amount = _amount;
            }
        }
        
        p.moderator = _moderator;

        p.initialSharePriceMultiplier = _initialSharePriceMultiplier;

        p.minutesFundingPeriod = _minutesFundingPeriod;

        p.committeeID = newCommittee(ProposalTypes.contractor, _proposalID, _minutesDebatingPeriod);   

        p.open = true;
        
        ProposalSubmitted(_proposalID, p.committeeID, p.contractor, p.contractorProposalID, p.amount+p.amountForShares+p.amountForTokens, 
            _proposalDescription, p.moderator, p.initialSharePriceMultiplier, p.minutesFundingPeriod);

        return _proposalID;
    }

    function resolutionProposal(
        string _name,
        string _description,
        PassProject _project,
        uint _minutesDebatingPeriod) payable returns (uint) {
        
        if (address(_project) == 0) _project = PassProject(passDao.MetaProject());
        
        uint _questionID = ResolutionProposals.length++;
        Question q = ResolutionProposals[_questionID];
        
        q.project = _project;
        q.name = _name;
        q.description = _description;
        
        q.committeeID = newCommittee(ProposalTypes.resolution, _questionID, _minutesDebatingPeriod);
        
        ResolutionProposalSubmitted(_questionID, q.committeeID, q.project, q.name, q.description);
        
        return _questionID;
    }

    function rulesProposal(
        uint _minQuorumDivisor, 
        uint _minCommitteeFees,
        uint _minPercentageOfLikes,
        uint _minutesSetProposalPeriod,
        uint _minMinutesDebatePeriod,
        uint _feesRewardInflationRate,
        uint _defaultMinutesFundingPeriod,
        uint _tokenPriceInflationRate) payable returns (uint) {

    
        if (_minQuorumDivisor <= 1
            || _minQuorumDivisor > 10
            || _minutesSetProposalPeriod < minMinutesPeriods
            || _minMinutesDebatePeriod < minMinutesPeriods
            || _feesRewardInflationRate > maxInflationRate
            || _tokenPriceInflationRate > maxInflationRate
            || _defaultMinutesFundingPeriod < minMinutesPeriods) throw; 
        
        uint _rulesProposalID = rulesProposals.length++;
        Rules r = rulesProposals[_rulesProposalID];

        r.minQuorumDivisor = _minQuorumDivisor;
        r.minCommitteeFees = _minCommitteeFees;
        r.minPercentageOfLikes = _minPercentageOfLikes;
        r.minutesSetProposalPeriod = _minutesSetProposalPeriod;
        r.minMinutesDebatePeriod = _minMinutesDebatePeriod;
        r.feesRewardInflationRate = _feesRewardInflationRate;
        r.defaultMinutesFundingPeriod = _defaultMinutesFundingPeriod;
        r.tokenPriceInflationRate = _tokenPriceInflationRate;

        r.committeeID = newCommittee(ProposalTypes.rules, _rulesProposalID, 0);

        RulesProposalSubmitted(_rulesProposalID, r.committeeID, _minQuorumDivisor, _minCommitteeFees, 
            _minPercentageOfLikes, _minutesSetProposalPeriod, _minMinutesDebatePeriod, 
            _feesRewardInflationRate, _defaultMinutesFundingPeriod, _tokenPriceInflationRate);

        return _rulesProposalID;
    }
    
    function upgradeProposal(
        address _newCommitteeRoom,
        address _newShareManager,
        address _newTokenManager,
        uint _minutesDebatingPeriod
    ) payable returns (uint) {
        
        uint _upgradeProposalID = UpgradeProposals.length++;
        Upgrade u = UpgradeProposals[_upgradeProposalID];
        
        u.newCommitteeRoom = _newCommitteeRoom;
        u.newShareManager = _newShareManager;
        u.newTokenManager = _newTokenManager;

        u.committeeID = newCommittee(ProposalTypes.upgrade, _upgradeProposalID, _minutesDebatingPeriod);
        
        UpgradeProposalSubmitted(_upgradeProposalID, u.committeeID, u.newCommitteeRoom, u.newShareManager, u.newTokenManager);
        
        return _upgradeProposalID;
    }
    
// Committees management

    function newCommittee(
        ProposalTypes _proposalType,
        uint _proposalID, 
        uint _minutesDebatingPeriod
    ) internal returns (uint) {

        if (_minutesDebatingPeriod == 0) _minutesDebatingPeriod = rulesProposals[0].minMinutesDebatePeriod;
        
        if (passDao.ActualCommitteeRoom() != address(this)
            || msg.value < rulesProposals[0].minCommitteeFees
            || now + ((rulesProposals[0].minutesSetProposalPeriod + _minutesDebatingPeriod) * 1 minutes) < now
            || _minutesDebatingPeriod < rulesProposals[0].minMinutesDebatePeriod
            || msg.sender == address(this)) throw;

        uint _committeeID = Committees.length++;
        Committee b = Committees[_committeeID];

        b.creator = msg.sender;

        b.proposalType = _proposalType;
        b.proposalID = _proposalID;

        b.fees = msg.value;
        
        b.setDeadline = now + (rulesProposals[0].minutesSetProposalPeriod * 1 minutes);        
        b.votingDeadline = b.setDeadline + (_minutesDebatingPeriod * 1 minutes); 

        b.open = true; 

        return _committeeID;
    }
    
    function vote(
        uint _committeeID, 
        bool _supportsProposal) {
        
        Committee b = Committees[_committeeID];

        if (hasVoted[_committeeID][msg.sender] 
            || now < b.setDeadline
            || now > b.votingDeadline) throw;
            
        PassManager _shareManager = ShareManager();

        uint _balance = uint(_shareManager.balanceOf(msg.sender));
        if (_balance == 0) throw;
        
        hasVoted[_committeeID][msg.sender] = true;

        _shareManager.blockTransfer(msg.sender, b.votingDeadline);

        if (_supportsProposal) b.yea += _balance;
        else b.nay += _balance; 

        uint _a = 100*b.fees;
        if ((_a/100 != b.fees) || ((_a*_balance)/_a != _balance)) throw;
        uint _multiplier = (_a*_balance)/uint(_shareManager.totalSupply());
        uint _divisor = 100 + 100*rulesProposals[0].feesRewardInflationRate*(now - b.setDeadline)/(100*365 days);
        uint _rewardedamount = _multiplier/_divisor;
        if (b.totalRewardedAmount + _rewardedamount > b.fees) _rewardedamount = b.fees - b.totalRewardedAmount;
        b.totalRewardedAmount += _rewardedamount;
        if (!msg.sender.send(_rewardedamount)) throw;

        Voted(_committeeID, _supportsProposal, msg.sender, _rewardedamount);    
}

// Decisions management

    function executeDecision(uint _committeeID) returns (bool) {

        Committee b = Committees[_committeeID];
        
        if (now < b.votingDeadline || !b.open) return;
        
        b.open = false;

        PassManager _shareManager = ShareManager();
        uint _quantityOfShares;
        PassManager _tokenManager = TokenManager();

        if (100*b.yea > rulesProposals[0].minPercentageOfLikes * uint(_shareManager.totalSupply())) {       
            _quantityOfShares = _shareManager.rewardTokensForClient(b.creator, rulesProposals[0].minCommitteeFees);
        }        

        uint _sentToDaoManager = b.fees - b.totalRewardedAmount;
        if (_sentToDaoManager > 0) {
            if (!address(_shareManager).send(_sentToDaoManager)) throw;
        }
        
        if (b.yea + b.nay < minQuorum() || b.yea <= b.nay) {
            if (b.proposalType == ProposalTypes.contractor) Proposals[b.proposalID].open = false;
            ProposalClosed(b.proposalID, b.proposalType, _committeeID, b.totalRewardedAmount, false, _quantityOfShares, _sentToDaoManager);
            return;
        }

        b.dateOfExecution = now;

        if (b.proposalType == ProposalTypes.contractor) {

            Proposal p = Proposals[b.proposalID];
    
            if (p.contractorProposalID == 0) p.open = false;
            
            if (p.amountForShares == 0 && p.amountForTokens == 0) orderToContractor(b.proposalID);
            else {
                if (p.amountForShares != 0) {
                    _shareManager.setFundingRules(p.moderator, p.initialSharePriceMultiplier, p.amountForShares, p.minutesFundingPeriod, 0, b.proposalID);
                }

                if (p.amountForTokens != 0) {
                    _tokenManager.setFundingRules(p.moderator, 0, p.amountForTokens, p.minutesFundingPeriod, rulesProposals[0].tokenPriceInflationRate, b.proposalID);
                }
            }

        } else if (b.proposalType == ProposalTypes.resolution) {
            
            Question q = ResolutionProposals[b.proposalID];
            
            q.project.newResolution(q.name, q.description);
            
        } else if (b.proposalType == ProposalTypes.rules) {

            Rules r = rulesProposals[b.proposalID];
            
            rulesProposals[0].committeeID = r.committeeID;
            rulesProposals[0].minQuorumDivisor = r.minQuorumDivisor;
            rulesProposals[0].minMinutesDebatePeriod = r.minMinutesDebatePeriod; 
            rulesProposals[0].minCommitteeFees = r.minCommitteeFees;
            rulesProposals[0].minPercentageOfLikes = r.minPercentageOfLikes;
            rulesProposals[0].minutesSetProposalPeriod = r.minutesSetProposalPeriod;
            rulesProposals[0].feesRewardInflationRate = r.feesRewardInflationRate;
            rulesProposals[0].tokenPriceInflationRate = r.tokenPriceInflationRate;
            rulesProposals[0].defaultMinutesFundingPeriod = r.defaultMinutesFundingPeriod;

        } else if (b.proposalType == ProposalTypes.upgrade) {

            Upgrade u = UpgradeProposals[b.proposalID];

            if ((u.newShareManager != 0) && (u.newShareManager != address(_shareManager))) {
                _shareManager.disableTransfer();
                if (_shareManager.balance > 0) {
                    if (!_shareManager.sendTo(u.newShareManager, _shareManager.balance)) throw;
                }
            }

            if ((u.newTokenManager != 0) && (u.newTokenManager != address(_tokenManager))) {
                _tokenManager.disableTransfer();
            }

            passDao.upgrade(u.newCommitteeRoom, u.newShareManager, u.newTokenManager);
                
            DappUpgraded(u.newCommitteeRoom, u.newShareManager, u.newTokenManager);
            
        }

        ProposalClosed(b.proposalID, b.proposalType, _committeeID , b.totalRewardedAmount, true, _quantityOfShares, _sentToDaoManager);
            
        return true;
    }
    
    function orderToContractor(uint _proposalID) returns (bool) {
        
        Proposal p = Proposals[_proposalID];
        Committee b = Committees[p.committeeID];

        if (b.open || !p.open) return;
        
        uint _amountForShares;
        uint _amountForTokens;

        if (p.amountForShares != 0) {
            _amountForShares = ShareManager().FundedAmount(_proposalID);
            if (_amountForShares == 0 && now <= b.dateOfExecution + (p.minutesFundingPeriod * 1 minutes)) return;
        }

        if (p.amountForTokens != 0) {
            _amountForTokens = TokenManager().FundedAmount(_proposalID);
            if (_amountForTokens == 0 && now <= b.dateOfExecution + (p.minutesFundingPeriod * 1 minutes)) return;
        }
        
        p.open = false;   

        uint _amount = p.amount + _amountForShares + _amountForTokens;

        PassProject _project = PassProject(p.contractor.Project());

        if (_amount == 0) {
            ContractorProposalClosed(_proposalID, p.contractorProposalID, p.contractor, 0);
            return;
        }    

        if (!p.contractor.order(p.contractorProposalID, _amount)) throw;
        
        if (p.amount + _amountForShares > 0) {
            if (!ShareManager().sendTo(p.contractor, p.amount + _amountForShares)) throw;
        }
        if (_amountForTokens > 0) {
            if (!TokenManager().sendTo(p.contractor, _amountForTokens)) throw;
        }

        ContractorProposalClosed(_proposalID, p.contractorProposalID, p.contractor, _amount);
        
        passDao.addProject(_project);
        _project.newOrder(p.contractor, p.contractorProposalID, _amount);
        
        return true;
    }

// Holder Account management

    function buySharesForProposal(uint _proposalID) payable returns (bool) {
        
        return ShareManager().buyTokensForProposal.value(msg.value)(_proposalID, msg.sender);
    }   

    function sendPendingAmounts(
        uint _from,
        uint _to,
        address _buyer) returns (bool) {
        
        return ShareManager().sendPendingAmounts(_from, _to, _buyer);
    }        
    
    function withdrawPendingAmounts() returns (bool) {
        
        if (!ShareManager().sendPendingAmounts(0, 0, msg.sender)) throw;
    }        
            
}
function buySharesForProposal(uint _proposalID) payable returns (bool) {
        
    return ShareManager().buyTokensForProposal.value(msg.value)(_proposalID, msg.sender);
}   

// Holder Account management