contract AbstractToken is ERC20, FsTKToken {
  using AddressExtension for address;
  using Math for uint256;

  struct Subscription {
    uint256 amount;
    uint256 startTime;
    uint256 interval;
    uint256 epoch;
    uint256 collectTime;
  }

  struct Account {
    uint256 balance;
    mapping (address => uint256) allowances;
    mapping (address => Subscription) subscriptions;
  }

  modifier liquid {
    require(isLiquid);
     _;
  }

  bool public isLiquid = true;
  bool public erc20ApproveChecking;
  mapping(address => Account) internal accounts;

  // *************************
  // * ERC 20
  // *************************

  function balanceOf(address owner) external view returns (uint256) {
    return accounts[owner].balance;
  }

  function allowance(address owner, address spender) external view returns (uint256) {
    return accounts[owner].allowances[spender];
  }

  function transfer(address to, uint256 value) external liquid returns (bool) {
    Account storage senderAccount = accounts[msg.sender];
    require(value <= senderAccount.balance);

    senderAccount.balance -= value;
    accounts[to].balance += value;

    Transfer(msg.sender, to, value);
    Transfer(msg.sender, to, value, new bytes(0));
    return true;
  }

  function transferFrom(address from, address to, uint256 value) external liquid returns (bool) {
    Account storage fromAccount = accounts[from];
    require(value <= fromAccount.balance && value <= fromAccount.allowances[msg.sender]);

    fromAccount.balance -= value;
    fromAccount.allowances[msg.sender] -= value;
    accounts[to].balance += value;

    Transfer(from, to, value);
    Transfer(from, to, value, new bytes(0));
    return true;
  }

  function approve(address spender, uint256 value) external returns (bool) {
    Account storage senderAccount = accounts[msg.sender];
    if (erc20ApproveChecking) {
      require((value == 0) || (senderAccount.allowances[spender] == 0));
    }
    senderAccount.allowances[spender] = value;

    Approval(msg.sender, spender, value);
    return true;
  }

  // *************************
  // * FsTK Token
  // *************************

  function transfer(address to, uint256 value, bytes data) external liquid returns (bool) {
    Account storage senderAccount = accounts[msg.sender];
    require(value <= senderAccount.balance);

    senderAccount.balance -= value;
    accounts[to].balance += value;

    Transfer(msg.sender, to, value);
    Transfer(msg.sender, to, value, data);

    if (!to.isAccount()) {
      TokenReceiver(to).tokenFallback(msg.sender, value, data);
    }
    return true;
  }

  function buyService(ServiceProvider service, uint256 value, bytes data) external liquid {
    uint256 gas = msg.gas;
    Account storage senderAccount = accounts[msg.sender];
    uint256 currentValue = senderAccount.allowances[service];
    senderAccount.allowances[service] = currentValue.add(value);
    service.serviceFallback(msg.sender, value, data, gas);
    senderAccount.allowances[service] = currentValue;
  }

  function transfer(uint256[] data) external liquid {
    Account storage senderAccount = accounts[msg.sender];
    for (uint256 i = 0; i < data.length; i++) {
      address receiver = address(data[i] >> 96);
      uint256 value = data[i] & 0xffffffffffffffffffffffff;
      require(value <= senderAccount.balance);

      senderAccount.balance -= value;
      accounts[receiver].balance += value;

      Transfer(msg.sender, receiver, value);
      Transfer(msg.sender, receiver, value, new bytes(0));
    }
  }

  function subscriptionOf(address owner, address collector) external view returns (Subscription) {
    return accounts[owner].subscriptions[collector];
  }

  function subscribe(address collector, uint256 startTime, uint256 interval, uint256 amount) external {
    accounts[msg.sender].subscriptions[collector] = Subscription({
      startTime: startTime,
      interval: interval,
      amount: amount,
      epoch: 0,
      collectTime: 0
    });
    Subscribe(msg.sender, collector, startTime, interval, amount);
  }

  function cancelSubscription(address collector) external {
    delete accounts[msg.sender].subscriptions[collector];
    CancelSubscription(msg.sender, collector);
  }

  function collect(address from) external {
    Account storage fromAccount = accounts[from];
    Subscription storage info = fromAccount.subscriptions[msg.sender];
    uint256 epoch = (block.timestamp.sub(info.startTime)) / info.interval + 1;
    require(info.amount > 0 && epoch > info.epoch);
    uint256 totalAmount = (epoch - info.epoch).mul(info.amount);
    if (totalAmount > fromAccount.balance) {
      delete fromAccount.subscriptions[msg.sender];
      CancelSubscription(from, msg.sender);
    } else {
      info.collectTime = block.timestamp;
      fromAccount.balance -= totalAmount;
      accounts[msg.sender].balance += totalAmount;

      Transfer(from, msg.sender, totalAmount);
      Transfer(from, msg.sender, totalAmount, new bytes(0));
    }
  }

  function collect(address[] froms) external {
    for (uint256 i = 0; i < froms.length; i++) {
      address from = froms[i];
      Account storage fromAccount = accounts[from];
      Subscription storage info = fromAccount.subscriptions[msg.sender];
      uint256 epoch = (block.timestamp.sub(info.startTime)) / info.interval + 1;
      require(info.amount > 0 && epoch > info.epoch);
      uint256 totalAmount = (epoch - info.epoch).mul(info.amount);
      if (totalAmount > fromAccount.balance) {
        delete fromAccount.subscriptions[msg.sender];
        CancelSubscription(from, msg.sender);
      } else {
        info.collectTime = block.timestamp;
        fromAccount.balance -= totalAmount;
        accounts[msg.sender].balance += totalAmount;
  
        Transfer(from, msg.sender, totalAmount);
        Transfer(from, msg.sender, totalAmount, new bytes(0));
      }
    }
  }

  function approve(address spender, uint256 expectedValue, uint256 newValue) external {
    Account storage senderAccount = accounts[msg.sender];
    require(senderAccount.allowances[spender] == expectedValue);

    senderAccount.allowances[spender] = newValue;

    Approval(msg.sender, spender, newValue);
  }

  function increaseAllowance(address spender, uint256 value) external {
    Account storage senderAccount = accounts[msg.sender];
    uint256 newValue = senderAccount.allowances[spender].add(value);
    senderAccount.allowances[spender] = newValue;

    Approval(msg.sender, spender, newValue);
  }

  function decreaseAllowance(address spender, uint256 value) external {
    Account storage senderAccount = accounts[msg.sender];
    uint256 newValue = senderAccount.allowances[spender].sub(value);
    senderAccount.allowances[spender] = newValue;

    Approval(msg.sender, spender, newValue);
  }

  function decreaseAllowanceOrEmtpy(address spender, uint256 value) external {
    Account storage senderAccount = accounts[msg.sender];
    uint256 currentValue = senderAccount.allowances[spender];
    uint256 newValue;
    if (value < currentValue) {
      newValue = currentValue - value;
    }
    senderAccount.allowances[spender] = newValue;

    Approval(msg.sender, spender, newValue);
  }

  function setLiquid(bool _isLiquid) public {
    isLiquid = _isLiquid;
  }

  function setERC20ApproveChecking(bool _erc20ApproveChecking) public {
    erc20ApproveChecking = _erc20ApproveChecking;
  }
}
function transfer(address to, uint256 value, bytes data) external liquid returns (bool) {
  Account storage senderAccount = accounts[msg.sender];
  require(value <= senderAccount.balance);

  senderAccount.balance -= value;
  accounts[to].balance += value;

  Transfer(msg.sender, to, value);
  Transfer(msg.sender, to, value, data);

  if (!to.isAccount()) {
    TokenReceiver(to).tokenFallback(msg.sender, value, data);
  }
  return true;
}

// *************************
// * FsTK Token
// *************************