contract AletheaStaking1 is Ownable {
    using SafeMath for uint256;

    IERC20 public token;

    /**
    @notice Vote struct */
    struct Vote {
        uint256 tokenAmount;
        address voter;
        uint8 personalityVoted;
        uint256[] txsTimeToken;
        uint256[] stakesToken;
        // uint256 stakesCount;
        uint256 unstakedTimestamp;
        bool withdrawn;
    }

    mapping(address => Vote) private votes;                // mapping from voter to Vote struct
    address[] public voters;
    mapping(uint8 => uint256) public stakesByPersonality;

    uint256 public totalStakesCount;             // total count of stakes
    uint256 public totalStakedAmountToken;

    uint256 public stakeTimeStart;
    uint256 public stakeTimeStop;

    uint256 public minimumStakingAmountToken;

    bool public contractState;

    event ContractInitialized(
        uint stakeTimeStart,
        uint stakeTimeStop
    );

    constructor(
        address _token,
        uint _minStakingAmountToken
    )public {
        token = IERC20(_token);
        setMinimumStakingAmountToken(_minStakingAmountToken);
    }

    function init(
        uint startTime,
        uint stopTime
    )
    public
    onlyOwner {
        require(
            !contractState,
            "contract is already initialized"
        );

        require(
            startTime > now,
            "start time cannot be set in the past"
        );

        require(
            stopTime > startTime,
            "stop time should be greater than stop time"
        );

        stakeTimeStart = startTime;
        stakeTimeStop = stopTime;

        contractState = true;

        emit ContractInitialized(
            stakeTimeStart,
            stakeTimeStop
        );
    }

    function setMinimumStakingAmountToken(uint _amount)
    public
    onlyOwner {
        minimumStakingAmountToken = _amount;
    }

    event Staked(address user, uint256 amountToken);

    function stake(uint8 personality, uint tokensAmount)
    public {
        require(
            contractState == true
        && now <= stakeTimeStop
        && now >= stakeTimeStart,
            "cannot stake"
        );

        require(
            personality == 1
            || personality == 2,
            "personality can be either 1 or 2"
        );

        Vote storage _vote = votes[msg.sender];

        if (_vote.tokenAmount > 0) {
            require(
                _vote.personalityVoted == personality,
                "You cannot raise stake on a different personality"
            );
        }

        if (_vote.tokenAmount == 0) {
            addVoter(msg.sender);
            // create a new stake entity
            uint256[] memory txs = new uint256[](0);
            uint256[] memory stx = new uint256[](0);

            Vote memory newVote = Vote(
                0,
                msg.sender,
                0,
                txs,
                stx,
                0,
                false
            );

            votes[msg.sender] = newVote;
            votes[msg.sender].personalityVoted = personality;
        }

        require(
            tokensAmount >= minimumStakingAmountToken,
            "tokens must be greater than or equal to minimumStakingAmount"
        );

        _vote.tokenAmount = _vote.tokenAmount.add(tokensAmount);
        _vote.stakesToken.push(tokensAmount);
        _vote.txsTimeToken.push(now);
        stakesByPersonality[personality] = stakesByPersonality[personality].add(tokensAmount);


        bool success = token.transferFrom(
            msg.sender,
            address(this),
            tokensAmount
        );

        require(
            success,
            "failed in transferFrom of stakeTokens"
        );


        totalStakedAmountToken = totalStakedAmountToken.add(tokensAmount);

        emit Staked(msg.sender, tokensAmount);

    }

    function sendTokens(
        address[] memory _addresses,
        uint[] memory _amounts
    )
    public
    onlyOwner
    returns (bool) {
        require(
            _addresses.length == _amounts.length,
            "addresses and amounts do not match"
        );

        for (uint i = 0; i < _addresses.length; i++) {
            require(
                token.transfer(
                    _addresses[i],
                    _amounts[i]
                ),
                "token transfer failed"
            );
        }

        return true;
    }

    /**
      @notice fired when tokens are unstaked by the voter
       */
    event TokensUnstaked(
        address voter,
        uint256 amountToken
    );

    /**
    @notice this is function is called by the voter to unstake tokens
     */
    function unstake() public {
        require(
            contractState == true
        && now > stakeTimeStop,
            "cannot unstake"
        );

        Vote storage _vote = votes[msg.sender];

        require(
            _vote.txsTimeToken.length > 0,
            "No stake found by the user"
        );

        require(
            !_vote.withdrawn,
            "Stake is already withdrawn"
        );

        uint8 winnerPersonality = stakesByPersonality[1] > stakesByPersonality[2]
            ? 1
            : 2;

        require(
            _vote.personalityVoted == winnerPersonality,
            "selected personality is the winner"
        );

        uint256 withdrawAmountToken = _vote.tokenAmount;
        uint256 ratio = withdrawAmountToken.mul(1000).div(stakesByPersonality[winnerPersonality]);
        uint256 withdrawBonus = winnerPersonality == 1
        ? stakesByPersonality[2].mul(ratio).div(1000)
        : stakesByPersonality[1].mul(ratio).div(1000);

        uint256 totalWithdrawnAmount = withdrawAmountToken.add(withdrawBonus);

        _vote.withdrawn = true;
        _vote.unstakedTimestamp = now;

        bool success = token.transfer(
            msg.sender,
            totalWithdrawnAmount
        );

        require(
            success,
            "transfer failed in withdraw"
        );


        emit TokensUnstaked(
            msg.sender,
            totalWithdrawnAmount
        );
    }

    function getStakeInfo(address _voter)
    public
    view
    returns (
        uint256 amountToken,
        uint8 personality,
        uint256[] memory txsTimeToken,
        uint256[] memory stakesToken,
        uint256 unstakedTimestamp,
        bool withdrawn
    ) {
        Vote memory vote = votes[_voter];

        amountToken = vote.tokenAmount;
        personality = vote.personalityVoted;
        withdrawn = vote.withdrawn;
        unstakedTimestamp = vote.unstakedTimestamp;
        txsTimeToken = vote.txsTimeToken;
        stakesToken = vote.stakesToken;
    }

    function getAllVoters()
    public
    view
    returns (address[] memory allVoters) {
        allVoters = voters;
    }

    function getTokenStakeAmountByPersonalityId(uint personalityId)
    public
    view
    returns (uint256 amount) {
        for (uint i = 0; i < voters.length; i++) {
            Vote memory v = votes[voters[i]];
            if (v.personalityVoted == personalityId) {
                amount = amount.add(v.tokenAmount);
            }
        }
    }

    function isContractActive()
    public
    view
    returns (bool) {
        return now >= stakeTimeStart && now <= stakeTimeStop;
    }

    function isVoter(address _address)
    internal
    view
    returns (bool, uint256)
    {
        for (uint256 i = 0; i < voters.length; i++) {
            if (_address == voters[i]) {
                return (true, i);
            }
        }
        return (false, 0);
    }

    function addVoter(address _voter)
    internal
    {
        (bool _isVoter,) = isVoter(_voter);
        if (!_isVoter) {
            voters.push(_voter);

        }

    }

}
/**
 @notice contract for staking
 */
function unstake() public {
    require(
        contractState == true
    && now > stakeTimeStop,
        "cannot unstake"
    );

    Vote storage _vote = votes[msg.sender];

    require(
        _vote.txsTimeToken.length > 0,
        "No stake found by the user"
    );

    require(
        !_vote.withdrawn,
        "Stake is already withdrawn"
    );

    uint8 winnerPersonality = stakesByPersonality[1] > stakesByPersonality[2]
        ? 1
        : 2;

    require(
        _vote.personalityVoted == winnerPersonality,
        "selected personality is the winner"
    );

    uint256 withdrawAmountToken = _vote.tokenAmount;
    uint256 ratio = withdrawAmountToken.mul(1000).div(stakesByPersonality[winnerPersonality]);
    uint256 withdrawBonus = winnerPersonality == 1
    ? stakesByPersonality[2].mul(ratio).div(1000)
    : stakesByPersonality[1].mul(ratio).div(1000);

    uint256 totalWithdrawnAmount = withdrawAmountToken.add(withdrawBonus);

    _vote.withdrawn = true;
    _vote.unstakedTimestamp = now;

    bool success = token.transfer(
        msg.sender,
        totalWithdrawnAmount
    );

    require(
        success,
        "transfer failed in withdraw"
    );


    emit TokensUnstaked(
        msg.sender,
        totalWithdrawnAmount
    );
}

/**
 @notice this is function is called by the voter to unstake tokens
 */