contract Investment is IInvestment, Ownable {
    enum State {INACTIVE, ACTIVE}

    struct AllocationToken {
        bool state;
        bool isPremium;
    }

    mapping(address => AllocationToken) public allocationTokens; //addresses of ERC-20, that could be invested through this contract.
    mapping(address => bool) public premiumUsers;
    IProperties public property; // address of Real Estate Properties ERC721 Contract.
    IKYC public kyc;

    State investmentState; //current state of contract to indicate is it allowed to invest AT tokens through this contract.

    /**
    @notice constructor of investment contract
     */
    constructor(IKYC _kyc) public {
        investmentState = State.ACTIVE;
        kyc = _kyc;
        emit StateChanged(uint256(investmentState));
    }

    /**
    @notice call is only allowed to pass when investment contract is in ACTIVE state
     */
    modifier isStateActive() {
        require(
            investmentState == State.ACTIVE,
            "Investment contract's state is INACTIVE."
        );
        _;
    }

    /**
    @notice validates the contract state
     */
    modifier validateContract(address tokenAddress) {
        require(address(property) != address(0), "property is not set.");
        require(
            allocationTokens[tokenAddress].state,
            "token is not a part of allocation tokens."
        );
        _;
    }

    /**
    @notice It changes premium status of a user
    @param user The address of the user
    @param status The premium status of the user */
    function changePremiumStatusOfUser(address user, bool status)
        public
        onlyOwner
    {
        require(user != address(0), "Provide a valid user address.");
        require(
            premiumUsers[user] != status,
            "The provided status is already set."
        );

        premiumUsers[user] = status;
        emit PremiumStatusOfUserChanged(user, status);
    }
    /**
    @notice it invests allocation tokens to buy property
    @param propertyId ID of the property to make investment into
    @param tokenAddress address of the token using which the investment is made
    @param tokens the amount of tokens to invest
     */
    function invest(uint256 propertyId, address tokenAddress, uint256 tokens)
        external
        isStateActive
        validateContract(tokenAddress)
    {
        require(propertyId > 0, "propertyId should be greater than zero");
        require(tokens > 0, "investment tokens should be greater than zero");

        require(
            kyc.getAddressStatus(msg.sender),
            "msg.sender is not whiteliisted in KYC"
        );

        if (allocationTokens[tokenAddress].isPremium) {
            require(
                premiumUsers[msg.sender],
                "Only premium users can invest in the property"
            );
        } else {
            require(
                !premiumUsers[msg.sender],
                "Only basic users can invest in the property"
            );
        }

        IAllocationToken allocationToken = IAllocationToken(tokenAddress);

        allocationToken.burn(msg.sender, tokens);

        (, , , , , , , address ATToken, , uint8 propertyStatus) = property
            .getProperty(propertyId);

        require(ATToken == tokenAddress, "ATTokens do not match");
        require(propertyStatus == 0, "property is not investable");

        // call invest function on the property contract
        property.invest(msg.sender, propertyId, tokens);
        emit Invested(propertyId, msg.sender, tokenAddress, tokens);
    }

    /**
    @notice this function sets/changes state of this smart contract and owner can call it
    @param state it can be either 0 (Inactive) or 1 (Active)
     */
    function setState(uint256 state) external onlyOwner {
        require(state == 0 || state == 1, "Provided state is invalid.");
        require(
            state != uint256(investmentState),
            "Provided state is already set."
        );

        investmentState = State(state);
        emit StateChanged(uint256(investmentState));
    }

    /**
    @notice is called by owner to set property address
    @param _property it is the address of the property
     */
    function setProperty(IProperties _property) external onlyOwner {
        require(
            address(_property) != address(0),
            "property address must be a valid address."
        );
        property = _property;

        emit PropertySet(address(property));
    }

    /**
    @dev fired when allocation token state is changed
     */
    event AllocationTokenStateChanged(address allocationToken, bool state);

    /**
    @notice is called by owner to change the allocation token state
    @param _token it is the address of the allocation token
    @param _state it is the state of the allocation token
    @param _isPremium sets true if the set is premium
     */
    function setAllocationTokenState(
        address _token,
        bool _state,
        bool _isPremium
    ) external onlyOwner {
        require(
            _token != address(0),
            "allocation token address must be a valid address."
        );
        require(
            allocationTokens[_token].state != _state,
            "this state is already set for the provided allocation token."
        );

        allocationTokens[_token].state = _state;
        allocationTokens[_token].isPremium = _isPremium;

        emit AllocationTokenStateChanged(_token, _state);
    }
}
/**
 @notice Investment smart contract for Chelle app
 */
function setState(uint256 state) external onlyOwner {
    require(state == 0 || state == 1, "Provided state is invalid.");
    require(
        state != uint256(investmentState),
        "Provided state is already set."
    );

    investmentState = State(state);
    emit StateChanged(uint256(investmentState));
}

/**
 @notice this function sets/changes state of this smart contract and owner can call it
 @param state it can be either 0 (Inactive) or 1 (Active)
 */