contract IkuraToken is IkuraTokenEvent, DSMath, DSAuth {
  //
  // constants
  //

  // 手数料率
  // 0.01pips = 1
  // 例). 手数料を 0.05% とする場合は 500
  uint _transferFeeRate = 0;

  // 最低手数料額
  // 1 = 1dJPY
  // amount * 手数料率で算出した金額がここで設定した最低手数料を下回る場合は、最低手数料額を手数料とする
  uint8 _transferMinimumFee = 0;

  // ロジックバージョン
  uint _logicVersion = 2;

  //
  // libraries
  //

  /*using ProposalLibrary for ProposalLibrary.Entity;
  ProposalLibrary.Entity proposalEntity;*/

  //
  // private
  //

  // データの永続化ストレージ
  IkuraStorage _storage;
  IkuraAssociation _association;

  constructor() DSAuth() public {
    // @NOTE リリース時にcontractのdeploy -> watch contract -> setOwnerの流れを
    //省略したい場合は、ここで直接controllerのアドレスを指定するとショートカットできます
    // 勿論テストは通らなくなるので、テストが通ったら試してね
    /*address controllerAddress = 0x34c5605A4Ef1C98575DB6542179E55eE1f77A188;
    owner = controllerAddress;
    LogSetOwner(controllerAddress);*/
  }

  // ----------------------------------------------------------------------------------------------------
  // 以降はERC20に準拠した関数
  // ----------------------------------------------------------------------------------------------------

  /**
   * ERC20 Token Standardに準拠した関数
   *
   * dJPYの発行高を返す
   *
   * @return 発行高
   */
  function totalSupply(address sender) public view returns (uint) {
    sender; // #HACK

    return _storage.totalSupply();
  }

  /**
   * ERC20 Token Standardに準拠した関数
   *
   * 特定のアドレスのdJPY残高を返す
   *
   * @param sender 実行アドレス
   * @param addr アドレス
   *
   * @return 指定したアドレスのdJPY残高
   */
  function balanceOf(address sender, address addr) public view returns (uint) {
    sender; // #HACK

    return _storage.coinBalance(addr);
  }

  /**
   * ERC20 Token Standardに準拠した関数
   *
   * 指定したアドレスに対してdJPYを送金する
   * 以下の条件を満たす必要がある
   *
   * - メッセージの送信者の残高 >= 送金額
   * - 送金額 > 0
   * - 送金先のアドレスの残高 + 送金額 > 送金元のアドレスの残高（overflowのチェックらしい）
   *
   * @param sender 送金元アドレス
   * @param to 送金対象アドレス
   * @param amount 送金額
   *
   * @return 条件を満たして処理に成功した場合はtrue、失敗した場合はfalse
   */
  function transfer(address sender, address to, uint amount) public auth returns (bool success) {
    uint fee = transferFee(sender, sender, to, amount);
    uint totalAmount = add(amount, fee);

    require(_storage.coinBalance(sender) >= totalAmount);
    require(amount > 0);

    // 実行者の口座からamount + feeの金額が控除される
    _storage.subCoinBalance(sender, totalAmount);

    // toの口座にamountが振り込まれる
    _storage.addCoinBalance(to, amount);

    if (fee > 0) {
      // 手数料を受け取るオーナーのアドレスを選定
      address owner = selectOwnerAddressForTransactionFee(sender);

      // オーナーの口座にfeeが振り込まれる
      _storage.addCoinBalance(owner, fee);
    }

    return true;
  }

  /**
   * ERC20 Token Standardに準拠した関数
   *
   * from（送信元のアドレス）からto（送信先のアドレス）へamount分だけ送金する。
   * 主に口座からの引き出しに利用され、契約によってサブ通貨の送金手数料を徴収することができるようになる。
   * この操作はfrom（送信元のアドレス）が何らかの方法で意図的に送信者を許可する場合を除いて失敗すべき。
   * この許可する処理はapproveコマンドで実装しましょう。
   *
   * 以下の条件を満たす場合だけ送金を認める
   *
   * - 送信元の残高 >= 金額
   * - 送金する金額 > 0
   * - 送信者に対して送信元が許可している金額 >= 送金する金額
   * - 送信先の残高 + 金額 > 送信元の残高（overflowのチェックらしい）
   # - 送金処理を行うユーザーの口座残高 >= 送金処理の手数料
   *
   * @param sender 実行アドレス
   * @param from 送金元アドレス
   * @param to 送金先アドレス
   * @param amount 送金額
   *
   * @return 条件を満たして処理に成功した場合はtrue、失敗した場合はfalse
   */
  function transferFrom(address sender, address from, address to, uint amount) public auth returns (bool success) {
    uint fee = transferFee(sender, from, to, amount);

    require(_storage.coinBalance(from) >= amount);
    require(_storage.coinAllowance(from, sender) >= amount);
    require(amount > 0);
    require(add(_storage.coinBalance(to), amount) > _storage.coinBalance(to));

    if (fee > 0) {
      require(_storage.coinBalance(sender) >= fee);

      // 手数料を受け取るオーナーのアドレスを選定
      address owner = selectOwnerAddressForTransactionFee(sender);

      // 手数料はこの関数を実行したユーザー（主に取引所とか）から徴収する
      _storage.subCoinBalance(sender, fee);

      _storage.addCoinBalance(owner, fee);
    }

    // 送金元から送金額を引く
    _storage.subCoinBalance(from, amount);

    // 送金許可している金額を減らす
    _storage.subCoinAllowance(from, sender, amount);

    // 送金口座に送金額を足す
    _storage.addCoinBalance(to, amount);

    return true;
  }

  /**
   * ERC20 Token Standardに準拠した関数
   *
   * spender（支払い元のアドレス）にsender（送信者）がamount分だけ支払うのを許可する
   * この関数が呼ばれる度に送金可能な金額を更新する。
   *
   * @param sender 実行アドレス
   * @param spender 送金元アドレス
   * @param amount 送金額
   *
   * @return 基本的にtrueを返す
   */
  function approve(address sender, address spender, uint amount) public auth returns (bool success) {
    _storage.setCoinAllowance(sender, spender, amount);

    return true;
  }

  /**
   * ERC20 Token Standardに準拠した関数
   *
   * 受取側に対して支払い側がどれだけ送金可能かを返す
   *
   * @param sender 実行アドレス
   * @param owner 受け取り側のアドレス
   * @param spender 支払い元のアドレス
   *
   * @return 許可されている送金料
   */
  function allowance(address sender, address owner, address spender) public view returns (uint remaining) {
    sender; // #HACK

    return _storage.coinAllowance(owner, spender);
  }

  // ----------------------------------------------------------------------------------------------------
  // 以降はERC20以外の独自実装
  // ----------------------------------------------------------------------------------------------------

  /**
   * 特定のアドレスのSHINJI残高を返す
   *
   * @param sender 実行アドレス
   * @param owner アドレス
   *
   * @return 指定したアドレスのSHINJIトークン量
   */
  function tokenBalanceOf(address sender, address owner) public view returns (uint balance) {
    sender; // #HACK

    return _storage.tokenBalance(owner);
  }

  /**
   * 指定したアドレスに対してSHINJIトークンを送金する
   *
   * - 送信元の残トークン量 >= トークン量
   * - 送信するトークン量 > 0
   * - 送信先の残高 + 金額 > 送信元の残高（overflowのチェック）
   *
   * @param sender 実行アドレス
   * @param to 送金対象アドレス
   * @param amount 送金額
   *
   * @return 条件を満たして処理に成功した場合はtrue、失敗した場合はfalse
   */
  function transferToken(address sender, address to, uint amount) public auth returns (bool success) {
    require(_storage.tokenBalance(sender) >= amount);
    require(amount > 0);
    require(add(_storage.tokenBalance(to), amount) > _storage.tokenBalance(to));

    _storage.subTokenBalance(sender, amount);
    _storage.addTokenBalance(to, amount);

    emit IkuraTransferToken(sender, to, amount);

    return true;
  }

  /**
   * 送金元、送金先、送金金額によって対象のトランザクションの手数料を決定する
   * 送金金額に対して手数料率をかけたものを計算し、最低手数料金額とのmax値を取る。
   *
   * @param sender 実行アドレス
   * @param from 送金元
   * @param to 送金先
   * @param amount 送金金額
   *
   * @return 手数料金額
   */
  function transferFee(address sender, address from, address to, uint amount) public view returns (uint) {
    sender; from; to; // #avoid warning
    if (_transferFeeRate > 0) {
      uint denominator = 1000000; // 0.01 pips だから 100 * 100 * 100 で 100万
      uint numerator = mul(amount, _transferFeeRate);

      uint fee = numerator / denominator;
      uint remainder = sub(numerator, mul(denominator, fee));

      // 余りがある場合はfeeに1を足す
      if (remainder > 0) {
        fee++;
      }

      if (fee < _transferMinimumFee) {
        fee = _transferMinimumFee;
      }

      return fee;
    } else {
      return 0;
    }
  }

  /**
   * 手数料率を返す
   *
   * @param sender 実行アドレス
   *
   * @return 手数料率
   */
  function transferFeeRate(address sender) public view returns (uint) {
    sender; // #HACK

    return _transferFeeRate;
  }

  /**
   * 最低手数料額を返す
   *
   * @param sender 実行アドレス
   *
   * @return 最低手数料額
   */
  function transferMinimumFee(address sender) public view returns (uint8) {
    sender; // #HACK

    return _transferMinimumFee;
  }

  /**
   * 手数料を振り込む口座を選択する
   * #TODO とりあえず一個目のオーナーに固定。後で選定ロジックを変える
   *
   * @param sender 実行アドレス
   * @return 特定のオーナー口座
   */
  function selectOwnerAddressForTransactionFee(address sender) public view returns (address) {
    sender; // #HACK

    return _storage.primaryOwner();
  }

  /**
   * dJPYを鋳造する
   *
   * - コマンドを実行したユーザがオーナーである
   * - 鋳造する量が0より大きい
   *
   * 場合は成功する
   *
   * @param sender 実行アドレス
   * @param amount 鋳造する金額
   */
  function mint(address sender, uint amount) public auth returns (bool) {
    require(amount > 0);

    _association.newProposal(keccak256('mint'), sender, amount, '');

    return true;
    /*return proposalEntity.mint(sender, amount);*/
  }

  /**
   * dJPYを消却する
   *
   * - コマンドを実行したユーザがオーナーである
   * - 鋳造する量が0より大きい
   * - dJPYの残高がamountよりも大きい
   * - SHINJIをamountよりも大きい
   *
   * 場合は成功する
   *
   * @param sender 実行アドレス
   * @param amount 消却する金額
   */
  function burn(address sender, uint amount) public auth returns (bool) {
    require(amount > 0);
    require(_storage.coinBalance(sender) >= amount);
    require(_storage.tokenBalance(sender) >= amount);

    _association.newProposal(keccak256('burn'), sender, amount, '');

    return true;
    /*return proposalEntity.burn(sender, amount);*/
  }

  /**
   * 提案を承認する。
   * #TODO proposalIdは分からないので、別のものからproposalを特定しないといかんよ
   */
  function confirmProposal(address sender, bytes32 type_, uint proposalId) public auth {
    _association.confirmProposal(type_, sender, proposalId);
    /*proposalEntity.confirmProposal(sender, type_, proposalId);*/
  }

  /**
   * 指定した種類の提案数を取得する
   *
   * @param type_ 提案の種類（'mint' | 'burn' | 'transferMinimumFee' | 'transferFeeRate'）
   *
   * @return 提案数（承認されていないものも含む）
   */
  function numberOfProposals(bytes32 type_) public view returns (uint) {
    return _association.numberOfProposals(type_);
    /*return proposalEntity.numberOfProposals(type_);*/
  }

  /**
   * 関連づける承認プロセスを変更する
   *
   * @param association_ 新しい承認プロセス
   */
  function changeAssociation(address association_) public auth returns (bool) {
    _association = IkuraAssociation(association_);
    return true;
  }

  /**
   * 永続化ストレージを設定する
   *
   * @param storage_ 永続化ストレージのインスタンス（アドレス）
   */
  function changeStorage(address storage_) public auth returns (bool) {
    _storage = IkuraStorage(storage_);
    return true;
  }

  /**
   * ロジックのバージョンを返す
   *
   * @param sender 実行ユーザーのアドレス
   *
   * @return バージョン情報
   */
  function logicVersion(address sender) public view returns (uint) {
    sender; // #HACK

    return _logicVersion;
  }
}
/**
 *
 * トークンロジック
 *
 */
function confirmProposal(address sender, bytes32 type_, uint proposalId) public auth {
  _association.confirmProposal(type_, sender, proposalId);
  /*proposalEntity.confirmProposal(sender, type_, proposalId);*/
}

/**
 * 提案を承認する。
 * #TODO proposalIdは分からないので、別のものからproposalを特定しないといかんよ
 */