contract PirateLotteryProfitToken is iERC20Token, iDividendToken, iPlpPointsRedeemer, SafeMath {

  event PaymentEvent(address indexed from, uint amount);
  event TransferEvent(address indexed from, address indexed to, uint amount);
  event ApprovalEvent(address indexed from, address indexed to, uint amount);

  struct tokenHolder {
    uint tokens;           // num tokens currently held in this acct, aka balance
    uint currentPoints;    // updated before token balance changes, or before a withdrawal. credit for owning tokens
    uint lastSnapshot;     // snapshot of global TotalPoints, last time we updated this acct's currentPoints
  }

  bool    public isLocked;
  uint8   public decimals;
  string  public symbol;
  string  public name;
  address payable public owner;
  address payable public reserve;            // reserve account
  uint256 public  totalSupply;               // total token supply. never changes
  uint256 public  holdoverBalance;           // funds received, but not yet distributed
  uint256 public  totalReceived;

  mapping (address => mapping (address => uint)) approvals;  //transfer approvals, from -> to
  mapping (address => tokenHolder) public tokenHolders;
  mapping (address => bool) public trusted;


  //
  // modifiers
  //
  modifier ownerOnly {
    require(msg.sender == owner, "owner only");
    _;
  }
  modifier unlockedOnly {
    require(!isLocked, "unlocked only");
    _;
  }
  modifier notReserve {
    require(msg.sender != reserve, "reserve is barred");
    _;
  }
  modifier trustedOnly {
    require(trusted[msg.sender] == true, "trusted only");
    _;
  }
  //this is to protect from short-address attack. use this to verify size of args, especially when an address arg preceeds
  //a value arg. see: https://www.reddit.com/r/ethereum/comments/63s917/worrysome_bug_exploit_with_erc20_token/dfwmhc3/
  modifier onlyPayloadSize(uint256 size) {
    assert(msg.data.length >= size + 4);
    _;
  }

  //
  //constructor
  //
  constructor(uint256 _totalSupply, uint256 _reserveSupply, address payable _reserve, uint8 _decimals, string memory _name, string memory _symbol) public {
    totalSupply = _totalSupply;
    reserve = _reserve;
    decimals = _decimals;
    name = _name;
    symbol = _symbol;
    owner = msg.sender;
    tokenHolders[reserve].tokens = _reserveSupply;
    tokenHolders[owner].tokens = safeSub(totalSupply, _reserveSupply);
  }

  function setTrust(address _trustedAddr, bool _trust) public ownerOnly unlockedOnly {
    trusted[_trustedAddr] = _trust;
  }

  function lock() public ownerOnly {
    isLocked = true;
  }


  //
  // ERC-20
  //
  function transfer(address _to, uint _value) public onlyPayloadSize(2*32) notReserve returns (bool success) {
    if (tokenHolders[msg.sender].tokens >= _value) {
      //first credit sender with points accrued so far.. must do this before number of held tokens changes
      calcCurPointsForAcct(msg.sender);
      tokenHolders[msg.sender].tokens = safeSub(tokenHolders[msg.sender].tokens, _value);
      //if destination is a new tokenholder then we are setting his "last" snapshot to the current totalPoints
      if (tokenHolders[_to].lastSnapshot == 0)
        tokenHolders[_to].lastSnapshot = totalReceived;
      //credit destination acct with points accrued so far.. must do this before number of held tokens changes
      calcCurPointsForAcct(_to);
      tokenHolders[_to].tokens = safeAdd(tokenHolders[_to].tokens, _value);
      emit TransferEvent(msg.sender, _to, _value);
      return true;
    } else {
      return false;
    }
  }


  // transfer from reserve is prevented by preventing reserve from generating approval
  function transferFrom(address _from, address _to, uint _value) onlyPayloadSize(3*32) public returns (bool success) {
    //prevent wrap:
    if (tokenHolders[_from].tokens >= _value && approvals[_from][msg.sender] >= _value) {
      //first credit source acct with points accrued so far.. must do this before number of held tokens changes
      calcCurPointsForAcct(_from);
      tokenHolders[_from].tokens = safeSub(tokenHolders[_from].tokens, _value);
      //if destination is a new tokenreserve then we are setting his "last" snapshot to the current totalPoints
      if (tokenHolders[_to].lastSnapshot == 0)
        tokenHolders[_to].lastSnapshot = totalReceived;
      //credit destination acct with points accrued so far.. must do this before number of held tokens changes
      calcCurPointsForAcct(_to);
      tokenHolders[_to].tokens = safeAdd(tokenHolders[_to].tokens, _value);
      approvals[_from][msg.sender] = safeSub(approvals[_from][msg.sender], _value);
      emit TransferEvent(_from, _to, _value);
      return true;
    } else {
      return false;
    }
  }


  function balanceOf(address _owner) public view returns (uint balance) {
    balance = tokenHolders[_owner].tokens;
  }


  function approve(address _spender, uint _value) public onlyPayloadSize(2*32) notReserve returns (bool success) {
    approvals[msg.sender][_spender] = _value;
    emit ApprovalEvent(msg.sender, _spender, _value);
    return true;
  }


  function allowance(address _owner, address _spender) public view returns (uint remaining) {
    return approvals[_owner][_spender];
  }

  //
  // END ERC20
  //


  //
  // iTransferPointsRedeemer
  //
  function reserveTokens() public view returns (uint remaining) {
    return tokenHolders[reserve].tokens;
  }


  //
  // transfer from reserve, initiated from a trusted partner-contract
  //
  function transferFromReserve(address _to, uint _value) onlyPayloadSize(2*32) public trustedOnly {
    require(_value >= 10 || tokenHolders[reserve].tokens < 10, "minimum redmption is 10 tokens");
    require(tokenHolders[reserve].tokens >= _value, "reserve has insufficient tokens");
    //first credit source acct with points accrued so far.. must do this before number of held tokens changes
    calcCurPointsForAcct(reserve);
    tokenHolders[reserve].tokens = safeSub(tokenHolders[reserve].tokens, _value);
    //if destination is a new tokenholder then we are setting his "last" snapshot to the current totalPoints
    if (tokenHolders[_to].lastSnapshot == 0)
      tokenHolders[_to].lastSnapshot = totalReceived;
    //credit destination acct with points accrued so far.. must do this before number of held tokens changes
    calcCurPointsForAcct(_to);
    tokenHolders[_to].tokens = safeAdd(tokenHolders[_to].tokens, _value);
    emit TransferEvent(reserve, _to, _value);
  }


  //
  // calc current points for a token holder; that is, points that are due to this token holder for all dividends
  // received by the contract during the current "period". the period began the last time this fcn was called, at which
  // time we updated the account's snapshot of the running point count, totalReceived. during the period the account's
  // number of tokens must not have changed. so always call this fcn before changing the number of tokens.
  //
  function calcCurPointsForAcct(address _acct) internal {
    uint256 _newPoints = safeMul(safeSub(totalReceived, tokenHolders[_acct].lastSnapshot), tokenHolders[_acct].tokens);
    tokenHolders[_acct].currentPoints = safeAdd(tokenHolders[_acct].currentPoints, _newPoints);
    tokenHolders[_acct].lastSnapshot = totalReceived;
  }


  //
  // default payable function. funds receieved here become dividends.
  //
  function () external payable {
    holdoverBalance = safeAdd(holdoverBalance, msg.value);
    totalReceived = safeAdd(totalReceived, msg.value);
  }


  //
  // check my dividends
  //
  function checkDividends(address _addr) view public returns(uint _amount) {
    //don't call calcCurPointsForAcct here, cuz this is a constant fcn
    uint _currentPoints = tokenHolders[_addr].currentPoints +
      ((totalReceived - tokenHolders[_addr].lastSnapshot) * tokenHolders[_addr].tokens);
    _amount = _currentPoints / totalSupply;
  }


  //
  // withdraw my dividends
  //
  function withdrawDividends() public returns (uint _amount) {
    calcCurPointsForAcct(msg.sender);
    _amount = tokenHolders[msg.sender].currentPoints / totalSupply;
    uint _pointsUsed = safeMul(_amount, totalSupply);
    tokenHolders[msg.sender].currentPoints = safeSub(tokenHolders[msg.sender].currentPoints, _pointsUsed);
    holdoverBalance = safeSub(holdoverBalance, _amount);
    msg.sender.transfer(_amount);
  }


  //only available before the contract is locked
  function killContract() public ownerOnly unlockedOnly {
    selfdestruct(owner);
  }

}
function reserveTokens() public view returns (uint remaining) {
    return tokenHolders[reserve].tokens;
  }
//
// END ERC20
//
//
// iTransferPointsRedeemer
//