pragma solidity ^0.4.19;

/**
 * @title IDXM Contract. IDEX Membership Token contract.
 *
 * @author Ray Pulver, ray@auroradao.com
 */

interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }

contract SafeMath {
  function safeMul(uint256 a, uint256 b) returns (uint256) {
    uint256 c = a * b;
    require(a == 0 || c / a == b);
    return c;
  }
  function safeSub(uint256 a, uint256 b) returns (uint256) {
    require(b <= a);
    return a - b;
  }
  function safeAdd(uint256 a, uint256 b) returns (uint256) {
    uint c = a + b;
    require(c >= a && c >= b);
    return c;
  }
}

contract Owned {
  address public owner;
  function Owned() {
    owner = msg.sender;
  }
  function setOwner(address _owner) returns (bool success) {
    owner = _owner;
    return true;
  }
  modifier onlyOwner {
    require(msg.sender == owner);
    _;
  }
}

contract IDXM is Owned, SafeMath {
  uint8 public decimals = 8;
  bytes32 public standard = 'Token 0.1';
  bytes32 public name = 'IDEX Membership';
  bytes32 public symbol = 'IDXM';
  uint256 public totalSupply;

  event Approval(address indexed from, address indexed spender, uint256 amount);

  mapping (address => uint256) public balanceOf;
  mapping (address => mapping (address => uint256)) public allowance;

  event Transfer(address indexed from, address indexed to, uint256 value);

  uint256 public baseFeeDivisor;
  uint256 public feeDivisor;
  uint256 public singleIDXMQty;

  function () external {
    throw;
  }

  uint8 public feeDecimals = 8;

  struct Validity {
    uint256 last;
    uint256 ts;
  }

  mapping (address => Validity) public validAfter;
  uint256 public mustHoldFor = 604800;
  mapping (address => uint256) public exportFee;

  /**
   * Constructor.
   *
   */
  function IDXM() {
    totalSupply = 200000000000;
    balanceOf[msg.sender] = totalSupply;
    exportFee[0x00000000000000000000000000000000000000ff] = 100000000;
    precalculate();
  }

  bool public balancesLocked = false;

  function uploadBalances(address[] addresses, uint256[] balances) onlyOwner {
    require(!balancesLocked);
    require(addresses.length == balances.length);
    uint256 sum;
    for (uint256 i = 0; i < uint256(addresses.length); i++) {
      sum = safeAdd(sum, safeSub(balances[i], balanceOf[addresses[i]]));
      balanceOf[addresses[i]] = balances[i];
    }
    balanceOf[owner] = safeSub(balanceOf[owner], sum);
  }

  function lockBalances() onlyOwner {
    balancesLocked = true;
  }

  /**
   * @notice Transfer `_amount` from `msg.sender.address()` to `_to`.
   *
   * @param _to Address that will receive.
   * @param _amount Amount to be transferred.
   */
  function transfer(address _to, uint256 _amount) returns (bool success) {
    require(!locked);
    require(balanceOf[msg.sender] >= _amount);
    require(balanceOf[_to] + _amount >= balanceOf[_to]);
    balanceOf[msg.sender] -= _amount;
    uint256 preBalance = balanceOf[_to];
    balanceOf[_to] += _amount;
    bool alreadyMax = preBalance >= singleIDXMQty;
    if (!alreadyMax) {
      if (now >= validAfter[_to].ts + mustHoldFor) validAfter[_to].last = preBalance;
      validAfter[_to].ts = now;
    }
    if (validAfter[msg.sender].last > balanceOf[msg.sender]) validAfter[msg.sender].last = balanceOf[msg.sender];
    Transfer(msg.sender, _to, _amount);
    return true;
  }

  /**
   * @notice Transfer `_amount` from `_from` to `_to`.
   *
   * @param _from Origin address
   * @param _to Address that will receive
   * @param _amount Amount to be transferred.
   * @return result of the method call
   */
  function transferFrom(address _from, address _to, uint256 _amount) returns (bool success) {
    require(!locked);
    require(balanceOf[_from] >= _amount);
    require(balanceOf[_to] + _amount >= balanceOf[_to]);
    require(_amount <= allowance[_from][msg.sender]);
    balanceOf[_from] -= _amount;
    uint256 preBalance = balanceOf[_to];
    balanceOf[_to] += _amount;
    allowance[_from][msg.sender] -= _amount;
    bool alreadyMax = preBalance >= singleIDXMQty;
    if (!alreadyMax) {
      if (now >= validAfter[_to].ts + mustHoldFor) validAfter[_to].last = preBalance;
      validAfter[_to].ts = now;
    }
    if (validAfter[_from].last > balanceOf[_from]) validAfter[_from].last = balanceOf[_from];
    Transfer(_from, _to, _amount);
    return true;
  }

  /**
   * @notice Approve spender `_spender` to transfer `_amount` from `msg.sender.address()`
   *
   * @param _spender Address that receives the cheque
   * @param _amount Amount on the cheque
   * @param _extraData Consequential contract to be executed by spender in same transcation.
   * @return result of the method call
   */
  function approveAndCall(address _spender, uint256 _amount, bytes _extraData) returns (bool success) {
    tokenRecipient spender = tokenRecipient(_spender);
    if (approve(_spender, _amount)) {
      spender.receiveApproval(msg.sender, _amount, this, _extraData);
      return true;
    }
  }

  /**
   * @notice Approve spender `_spender` to transfer `_amount` from `msg.sender.address()`
   *
   * @param _spender Address that receives the cheque
   * @param _amount Amount on the cheque
   * @return result of the method call
   */
  function approve(address _spender, uint256 _amount) returns (bool success) {
    require(!locked);
    allowance[msg.sender][_spender] = _amount;
    Approval(msg.sender, _spender, _amount);
    return true;
  }

  function setExportFee(address addr, uint256 fee) onlyOwner {
    require(addr != 0x00000000000000000000000000000000000000ff);
    exportFee[addr] = fee;
  }

  function setHoldingPeriod(uint256 ts) onlyOwner {
    mustHoldFor = ts;
  }


  /* --------------- fee calculation method ---------------- */

  /**
   * @notice 'Returns the fee for a transfer from `from` to `to` on an amount `amount`.
   *
   * Fee's consist of a possible
   *    - import fee on transfers to an address
   *    - export fee on transfers from an address
   * IDXM ownership on an address
   *    - reduces fee on a transfer from this address to an import fee-ed address
   *    - reduces the fee on a transfer to this address from an export fee-ed address
   * IDXM discount does not work for addresses that have an import fee or export fee set up against them.
   *
   * IDXM discount goes up to 100%
   *
   * @param from From address
   * @param to To address
   * @param amount Amount for which fee needs to be calculated.
   *
   */
  function feeFor(address from, address to, uint256 amount) constant external returns (uint256 value) {
    uint256 fee = exportFee[from];
    if (fee == 0) return 0;
    uint256 amountHeld;
    if (balanceOf[to] != 0) {
      if (validAfter[to].ts + mustHoldFor < now) amountHeld = balanceOf[to];
      else amountHeld = validAfter[to].last;
      if (amountHeld >= singleIDXMQty) return 0;
      return amount*fee*(singleIDXMQty - amountHeld) / feeDivisor;
    } else return amount*fee / baseFeeDivisor;
  }
  
  bool public locked = true;

  function unlockToken() onlyOwner {
    locked = false;
  }

  function precalculate() internal returns (bool success) {
    baseFeeDivisor = pow10(1, feeDecimals);
    feeDivisor = pow10(1, feeDecimals + decimals);
    singleIDXMQty = pow10(1, decimals);
  }
  function div10(uint256 a, uint8 b) internal returns (uint256 result) {
    for (uint8 i = 0; i < b; i++) {
      a /= 10;
    }
    return a;
  }
  function pow10(uint256 a, uint8 b) internal returns (uint256 result) {
    for (uint8 i = 0; i < b; i++) {
      a *= 10;
    }
    return a;
  }
}