// File: BaseCommunityFactory.sol



pragma solidity >=0.5.3 < 0.6.0;

/// @author Ryan @ Protea 
/// @title Community Factory interface for later expansion 
contract BaseCommunityFactory {
    struct Community {
        string name;
        address creator;
        address tokenManagerAddress;
        address membershipManagerAddress;
        address[] utilities; 
    }

    mapping(uint256 => Community) internal communities_;
    uint256 internal numberOfCommunities_ = 0;

    uint256 internal publishedBlocknumber_;
    address internal daiAddress_;
    address internal proteaAccount_;
    address internal admin_;
    address internal tokenManagerFactory_;
    address internal membershipManagerFactory_;

    event FactoryRegistered(address oldFactory, address newFactory);

    event CommunityCreated(
        address indexed publisher,
        uint256 index, 
        address indexed tokenManager, 
        address indexed membershipManager, 
        address[] utilities
    );

    /// Constructor of V1 factory
    /// @param _daiTokenAddress         Address of the DAI token account
    /// @param _proteaAccount           Address of the Protea DAI account
    /// @notice                         Also sets a super admin for changing factories at a later stage, unused at present
    /// @author Ryan                
    constructor (address _daiTokenAddress, address _proteaAccount) public {
        admin_ = msg.sender;
        daiAddress_ = _daiTokenAddress;
        proteaAccount_ = _proteaAccount;
        publishedBlocknumber_ = block.number;
    }
    
    modifier onlyAdmin() {
        require(msg.sender == admin_, "Not authorised");
        _;
    }

    /// Allows the creation of a community
    /// @param _communityName           :string Name of the community
    /// @param _communitySymbol         :string Symbol of the community token
    /// @param _communityManager        :address The address of the super admin
    /// @param _gradientDemoninator     :uint256 The gradient modifier in the curve, not required in V1
    /// @param _contributionRate        :uint256 Percentage of incoming DAI to be diverted to the community account, from 0 to 100
    /// @return uint256                 Index of the deployed ecosystem
    /// @dev                            Also sets a super admin for changing factories at a later stage, unused at present
    /// @author Ryan
    function createCommunity(
        string calldata _communityName,
        string calldata _communitySymbol,
        address _communityManager,
        uint256 _gradientDemoninator,
        uint256 _contributionRate
    )
        external
        returns(uint256);

    /// @dev                            By passing through a list, this allows greater flexibility of the interface for different factories
    /// @param _factories               :address[]  List of factories
    /// @notice                         Introspection or interface confirmation should be used at later stages
    function initialize(address[] calldata _factories) external;

    function setTokenManagerFactory(address _newFactory) external;

    function setMembershipManagerFactory(address _newFactory) external;

    /// Fetching community data
    /// @param _index                   :uint256 Index of the community
    /// @dev                            Fetches all data and contract addresses of deployed communities by index
    /// @return Community               Returns a Community struct matching the provided index
    /// @author Ryan
    function getCommunity(uint256 _index)
        external
        view
        returns(
            string memory,
            address,
            address,
            address,
            address[] memory
        );

    function getFactories() external view returns (address[] memory);

    function publishedBlocknumber() external view returns(uint256) {
        return publishedBlocknumber_;
    }
}
// File: CommunityFactoryV1.sol



pragma solidity >=0.5.3 < 0.6.0;

import { ITokenManagerFactory } from "./ITokenManagerFactory.sol";
import { IMembershipFactory } from "./IMembershipFactory.sol";
import { IMembershipManager } from "./IMembershipManager.sol";
import { IEventManagerFactory } from "./IEventManagerFactory.sol";
import { BaseCommunityFactory } from "./BaseCommunityFactory.sol";

/// @author Ryan @ Protea 
/// @title V1 Community ecosystem factory
contract CommunityFactoryV1 is BaseCommunityFactory{
    address internal eventManagerFactory_;

    /// Constructor of V1 factory
    /// @param _daiTokenAddress         Address of the DAI token account
    /// @param _proteaAccount           Address of the Protea DAI account
    /// @notice                         Also sets a super admin for changing factories at a later stage, unused at present
    /// @author Ryan                
    constructor (address _daiTokenAddress, address _proteaAccount) public BaseCommunityFactory(_daiTokenAddress, _proteaAccount) {
    }

    /// @dev                            By passing through a list, this allows greater flexibility of the interface for different factories
    /// @param _factories               :address[]  List of factories
    /// @notice                         Introspection or interface confirmation should be used at later stages
    function initialize(address[] calldata _factories) external onlyAdmin(){
        require(tokenManagerFactory_ == address(0), "Already initialised");
        tokenManagerFactory_ = _factories[0];
        membershipManagerFactory_ = _factories[1];
        eventManagerFactory_ = _factories[2];

        emit FactoryRegistered(address(0), tokenManagerFactory_);
        emit FactoryRegistered(address(0), membershipManagerFactory_);
        emit FactoryRegistered(address(0), eventManagerFactory_);
    }

    function setTokenManagerFactory(address _newFactory) external onlyAdmin() {
        address oldFactory = tokenManagerFactory_;
        tokenManagerFactory_ = _newFactory;
        emit FactoryRegistered(oldFactory, tokenManagerFactory_);
    }

    function setMembershipManagerFactory(address _newFactory) external onlyAdmin() {
        address oldFactory = membershipManagerFactory_;
        membershipManagerFactory_ = _newFactory;
        emit FactoryRegistered(oldFactory, membershipManagerFactory_);
    }

    function setEventManagerFactory(address _newFactory) external onlyAdmin() {
        address oldFactory = eventManagerFactory_;
        eventManagerFactory_ = _newFactory;
        emit FactoryRegistered(oldFactory, eventManagerFactory_);
    }

    /// Allows the creation of a community
    /// @param _communityName           :string Name of the community
    /// @param _communitySymbol         :string Symbol of the community token
    /// @param _communityManager        :address The address of the super admin
    /// @param _gradientDemoninator     :uint256 The gradient modifier in the curve, not required in V1
    /// @param _contributionRate        :uint256 Percentage of incoming DAI to be diverted to the community account, from 0 to 100
    /// @return uint256                 Index of the deployed ecosystem
    /// @dev                            Also sets a super admin for changing factories at a later stage, unused at present
    // Rough gas usage 5,169,665 
    /// @author Ryan
    function createCommunity(
        string calldata _communityName,
        string calldata _communitySymbol,
        address _communityManager,
        uint256 _gradientDemoninator,
        uint256 _contributionRate
    )
        external
        returns(uint256)
    {
        address membershipManagerAddress = IMembershipFactory(membershipManagerFactory_).deployMembershipManager(_communityManager);

        address tokenManagerAddress = ITokenManagerFactory(tokenManagerFactory_).deployMarket(
            _communityName,
            _communitySymbol,
            daiAddress_,
            proteaAccount_,
            _communityManager,
            _contributionRate,
            membershipManagerAddress
        );

        IMembershipFactory(membershipManagerFactory_).initialize(tokenManagerAddress, membershipManagerAddress);

        address eventManagerAddress = IEventManagerFactory(eventManagerFactory_).deployEventManager(
            tokenManagerAddress,
            membershipManagerAddress,
            _communityManager
        );

        uint256 index = numberOfCommunities_;
        numberOfCommunities_ = numberOfCommunities_ + 1;
        
        communities_[index].name = _communityName;
        communities_[index].creator = msg.sender;
        communities_[index].tokenManagerAddress = tokenManagerAddress;
        communities_[index].membershipManagerAddress = membershipManagerAddress;
        communities_[index].utilities.push(eventManagerAddress);

        emit CommunityCreated(
            msg.sender,
            index, 
            tokenManagerAddress, 
            membershipManagerAddress, 
            communities_[index].utilities
        );

        return index;
    }

    /// Fetching community data
    /// @param _index                   :uint256 Index of the community
    /// @dev                            Fetches all data and contract addresses of deployed communities by index
    /// @return Community               Returns a Community struct matching the provided index
    /// @author Ryan
    function getCommunity(uint256 _index)
        external
        view
        returns(
            string memory,
            address,
            address,
            address,
            address[] memory
        )
    {
        return (
            communities_[_index].name,
            communities_[_index].creator,
            communities_[_index].membershipManagerAddress,
            communities_[_index].tokenManagerAddress,
            communities_[_index].utilities
        );
    }

    function getFactories() external view returns (address[] memory) {
        address[] memory factories = new address[](3);
        factories[0] = tokenManagerFactory_;
        factories[1] = membershipManagerFactory_;
        factories[2] = eventManagerFactory_;

        return factories;
    }
}
// File: IEventManagerFactory.sol



pragma solidity >=0.5.3 < 0.6.0;

interface IEventManagerFactory{
    function deployEventManager(address _tokenManager, address _membershipManager, address _communityCreator) external returns (address);
}
// File: IMembershipFactory.sol



pragma solidity >=0.5.3 < 0.6.0;

interface IMembershipFactory{
    // TODO: comments
    function deployMembershipManager(address _communityManager) external returns (address);

    function initialize(address _tokenManager, address _target) external;

}
// File: IMembershipManager.sol



pragma solidity >=0.5.3 < 0.6.0;

/// @author Ryan @ Protea 
/// @title IMembershipManager
interface IMembershipManager {
    struct RegisteredUtility{
        bool active;
        mapping(uint256 => uint256) lockedStakePool; // Total Stake withheld by the utility
        mapping(uint256 => mapping(address => uint256)) contributions; // Traking individual token values sent in
    }

    struct Membership{
        uint256 currentDate;
        uint256 availableStake;
        uint256 reputation;
    }

    event UtilityAdded(address issuer);
    event UtilityRemoved(address issuer);
    event ReputationRewardSet(address indexed issuer, uint8 id, uint256 amount);

    event StakeLocked(address indexed member, address indexed utility, uint256 tokenAmount);
    event StakeUnlocked(address indexed member, address indexed utility, uint256 tokenAmount);

    event MembershipStaked(address indexed member, uint256 tokensStaked);
   
    function initialize(address _tokenManager) external returns(bool);

    function addUtility(address _utility) external;

    function removeUtility(address _utility) external;

    function addAdmin(address _newAdmin) external;

    function addSystemAdmin(address _newAdmin) external;

    function removeAdmin(address _newAdmin) external;

    function removeSystemAdmin(address _newAdmin) external;

    function setReputationRewardEvent(address _utility, uint8 _id, uint256 _rewardAmount) external;

    function issueReputationReward(address _member, uint8 _rewardId) external returns (bool);
  
    function stakeMembership(uint256 _daiValue, address _member) external returns(bool);

    function manualTransfer(uint256 _tokenAmount, uint256 _index, address _member) external returns (bool);

    function withdrawMembership(uint256 _daiValue, address _member) external returns(bool);

    function lockCommitment(address _member, uint256 _index, uint256 _daiValue) external returns (bool);

    function unlockCommitment(address _member, uint256 _index, uint8 _reputationEvent) external returns (bool);

    function reputationOf(address _account) external view returns(uint256);

    function getMembershipStatus(address _member) external view returns(uint256, uint256, uint256);

    function getUtilityStake(address _utility, uint256 _index) external view returns(uint256);
    
    function getMemberUtilityStake(address _utility, address _member, uint256 _index) external view returns(uint256);

    function getReputationRewardEvent(address _utility, uint8 _id) external view returns(uint256);

    function tokenManager() external view returns(address);
}
// File: ITokenManagerFactory.sol



pragma solidity >=0.5.3 < 0.6.0;

interface ITokenManagerFactory{
    function deployMarket(
        string calldata _name,
        string calldata _symbol,
        address _reserveToken,
        address _proteaAccount,
        address _publisher,
        uint256 _contributionRate,
        address _membershipManager
    ) external returns (address);
}