pragma solidity ^0.4.25;

 /*
  * @title: SafeMath
  * @dev: Helper contract functions to arithmatic operations safely.
  */
contract SafeMath {
    function Sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SafeMath: subtraction overflow");
        uint256 c = a - b;
    
        return c;
    }
    function Add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }
    function Mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }
}

 /*
  * @title: Token
  * @dev: Interface contract for ERC20 tokens
  */
contract Token {
      function totalSupply() public view returns (uint256 supply);
      function balanceOf(address _owner) public view returns (uint256 balance);
      function transfer(address _to, uint256 _value) public returns (bool success);
      function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
      function approve(address _spender, uint256 _value) public returns (bool success);
      function allowance(address _owner, address _spender) public view returns (uint256 remaining);
      event Transfer(address indexed _from, address indexed _to, uint256 _value);
      event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}

 /*
  * @title: Staking
  * @author BlockBank (https://www.blockbank.co.kr)
  */
contract Staking is SafeMath
{
    // _prAddress: ERC20 contract address
    // msg.sender: owner && operator
    constructor(address _prAddress) public
    {
        owner = msg.sender;
        operator = owner;
        prAddress = _prAddress;
        isContractUse = true;
    }

    address public owner;
    // Functions with this modifier can only be executed by the owner
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    address public operator;
    // Functions with this modifier can only be executed by the operator
    modifier onlyOperator() {
        require(msg.sender == operator);
        _;
    }
    function transferOperator(address _operator) onlyOwner public {
        operator = _operator;
    }
    
    bool public isContractUse;
    // Functions with this modifier can only be executed when this contract is not abandoned
    modifier onlyContractUse {
        require(isContractUse == true);
        _;
    }
    
    function SetContractUse(bool _isContractUse) onlyOperator public{
        isContractUse = _isContractUse;
    }

    uint32 public lastAcccountId;
    mapping (uint32 => address) id_account;
    mapping (uint32 => bool) accountId_freeze;
    mapping (address => uint32) account_id;
    // Find or add account
    function FindOrAddAccount(address findAddress) private returns (uint32)
    {
        if (account_id[findAddress] == 0)
        {
            account_id[findAddress] = ++lastAcccountId;
            id_account[lastAcccountId] = findAddress;
        }
        return account_id[findAddress];
    }
    // Find or revert account
    function FindOrRevertAccount() private view returns (uint32)
    {
        uint32 accountId = account_id[msg.sender];
        require(accountId != 0);
        return accountId;
    }
    // Get account id of msg sender
    function GetMyAccountId() view public returns (uint32)
    {
        return account_id[msg.sender];
    }
    // Get account id of any users
    function GetAccountId(address account) view public returns (uint32)
    {
        return account_id[account];
    }
    // Freeze or unfreez of account
    function SetFreezeByAddress(bool isFreeze, address account) onlyOperator public
    {
        uint32 accountId = account_id[account];

        if (accountId != 0)
        {
            accountId_freeze[accountId] = isFreeze;
        }
    }
    function IsFreezeByAddress(address account) public view returns (bool)
    {
        uint32 accountId = account_id[account];
        
        if (accountId != 0)
        {
            return accountId_freeze[accountId];
        }
        return false;
    }

    // reserved: Balance held up in orderBook
    // available: Balance available for trade
    struct Balance
    {
        uint256 available;
        uint256 maturity;
    }

    struct ListItem
    {
        uint32 prev;
        uint32 next;
    }

    mapping (uint32 => Balance) AccountId_Balance;
    
    uint256 public totalBonus;
    address public prAddress;
    
    uint256 public interest6weeks; //bp
    uint256 public interest12weeks; //bp
    
    // set interst for each holding period: 6 / 12 weeks
    function SetInterest(uint256 _interest6weeks, uint256 _interest12weeks) onlyOperator public
    {
        interest6weeks = _interest6weeks;    
        interest12weeks = _interest12weeks;
    }
    
    // deposit bonus to pay interest
    function depositBonus(uint256 amount) onlyOwner public
    {
        require(Token(prAddress).transferFrom(msg.sender, this, amount));
        
        totalBonus = Add(totalBonus, amount);
    }
    
    // withdraw bonus to owner account
    function WithdrawBonus(uint256 amount) onlyOwner public
    {
        require(Token(prAddress).transfer(msg.sender, amount));
        totalBonus = Sub(totalBonus, amount);
    }

    // Deposit ERC20's for saving
    function storeToken6Weeks(uint256 amount) onlyContractUse public
    {
        uint32 accountId = FindOrAddAccount(msg.sender);
        require(accountId_freeze[accountId] == false);
        require(AccountId_Balance[accountId].available == 0);
        
        require(Token(prAddress).transferFrom(msg.sender, this, amount));
        
        uint256 interst = Mul(amount, interest6weeks) / 10000;
        
        totalBonus = Sub(totalBonus, interst);
        AccountId_Balance[accountId].available = Add(AccountId_Balance[accountId].available, amount + interst);
        AccountId_Balance[accountId].maturity = now + 6 weeks;
    }
    // Deposit ERC20's for saving
    function storeToken12Weeks(uint128 amount) onlyContractUse public
    {
        uint32 accountId = FindOrAddAccount(msg.sender);
        require(accountId_freeze[accountId] == false);
        require(AccountId_Balance[accountId].available == 0);
        
        require(Token(prAddress).transferFrom(msg.sender, this, amount));
        
        uint256 interst = Mul(amount, interest12weeks) / 10000;
        
        totalBonus = Sub(totalBonus, interst);
        AccountId_Balance[accountId].available = Add(AccountId_Balance[accountId].available, amount + interst);
        AccountId_Balance[accountId].maturity = now + 12 weeks;
    }
    // Withdraw ERC20's to personal addresstrue
    function withdrawToken() public
    {
        uint32 accountId = FindOrAddAccount(msg.sender);
        require(AccountId_Balance[accountId].maturity < now);
        uint256 amount = AccountId_Balance[accountId].available; 
        require(amount > 0);
        AccountId_Balance[accountId].available = 0;
        require(Token(prAddress).transfer(msg.sender, amount));
    }

    // Below two emergency functions will be never used in normal situations.
    // These function is only prepared for emergency case such as smart contract hacking Vulnerability or smart contract abolishment
    // Withdrawn fund by these function cannot belong to any operators or owners.
    // Withdrawn fund should be distributed to individual accounts having original ownership of withdrawn fund.
    
    function emergencyWithdrawalETH(uint256 amount) onlyOwner public
    {
        require(msg.sender.send(amount));
    }
    function emergencyWithdrawalToken(uint256 amount) onlyOwner public
    {
        Token(prAddress).transfer(msg.sender, amount);
    }

    function getMyBalance() view public returns (uint256 available, uint256 maturity)
    {
        uint32 accountId = FindOrRevertAccount();
        available = AccountId_Balance[accountId].available;
        maturity = AccountId_Balance[accountId].maturity;
    }
    
    function getTimeStamp() view public returns (uint256)
    {
        return now;
    }
}