contract DrivezyPrivateCoinImplementation is DSAuth, APMath {
    DrivezyPrivateCoinStorage public coinStorage;
    DrivezyPrivateCoinSharedStorage public sharedStorage;
    DrivezyPrivateCoin public coin;


    /**
     * custom events
     */

    /* storage を設定したときに発生するイベント
     * {address} senderAddress - 実行者のアドレス
     * {address} contractAddress - 設定した storage のコントラクトアドレス
     */
    event SetStorage(address indexed senderAddress, address indexed contractAddress);

    /* shared storage を設定したときに発生するイベント
     * {address} senderAddress - 実行者のアドレス
     * {address} contractAddress - 設定した shared storage のコントラクトアドレス
     */
    event SetSharedStorage(address indexed senderAddress, address indexed contractAddress);

    /* coin を設定したときに発生するイベント
     * {address} senderAddress - 実行者のアドレス
     * {address} contractAddress - 設定した coin のコントラクトアドレス
     */
    event SetCoin(address indexed senderAddress, address indexed contractAddress);

    /* mint したときに発生するイベント
     * {address} senderAddress - 実行者のアドレス
     * {address} receiverAddress - コインを受け取るユーザのアドレス
     * {uint} amount - 発行高
     */
    event Mint(address indexed senderAddress, address indexed receiverAddress, uint amount);

    /* burn したときに発生するイベント
     * {address} senderAddress - 実行者のアドレス
     * {address} receiverAddress - コインを消却するユーザのアドレス
     * {uint} amount - 消却高
     */
    event Burn(address indexed senderAddress, address indexed receiverAddress, uint amount);

    /* addApprovedAddress したときに発生するイベント
     * {address} senderAddress - 実行者のアドレス
     * {address} userAddress - 許可されたユーザのアドレス
     */
    event AddApprovedAddress(address indexed senderAddress, address indexed userAddress);

    /* removeApprovedAddress したときに発生するイベント
     * {address} senderAddress - 実行者のアドレス
     * {address} userAddress - 許可が取り消されたユーザのアドレス
     */
    event RemoveApprovedAddress(address indexed senderAddress, address indexed userAddress);

    /**
     * 総発行高を返す
     * @return {uint} コインの総発行高
     */
    function totalSupply() auth public view returns (uint) {
        return coinStorage.totalSupply();
    }

    /**
     * 指定したアドレスが保有するコインの残高を返す
     * @param addr {address} - コインの残高を調べたいアドレス
     * @return {uint} コインの残高
     */
    function balanceOf(address addr) auth public view returns (uint) {
        return coinStorage.coinBalanceOf(addr);
    }

    /**
     * ERC20 Token Standardに準拠した関数
     *
     * あるユーザが保有するコインを指定したアドレスに送金する
     * @param sender {address} - 送信元 (資金源) のアドレス
     * @param to {address} - 宛先のアドレス
     * @param amount {uint} - 送付するコインの分量
     * @return {bool} コインの残高
     */
    function transfer(address sender, address to, uint amount) auth public returns (bool) {
        // 残高を超えて送金してないか確認
        require(coinStorage.coinBalanceOf(sender) >= amount);

        // 1円以上送ろうとしているか確認
        require(amount > 0);

        // 受取者がオーナーまたは許可された (KYC 通過済み) アドレスかを確認
        require(canTransfer(sender, to));

        // 送金元の残高を減らし、送金先の残高を増やす
        coinStorage.setCoinBalance(sender, safeSub(coinStorage.coinBalanceOf(sender), amount));
        coinStorage.setCoinBalance(to, safeAdd(coinStorage.coinBalanceOf(to), amount));

        // 送金先がコントラクトで、isDrivezyPrivateTokenAcceptable が true を返すコントラクトでは
        // receiveToken() 関数をコールする
        if (isContract(to)) {
            DrivezyPrivateCoinAcceptableContract receiver = DrivezyPrivateCoinAcceptableContract(to);
            if (receiver.isDrivezyPrivateTokenAcceptable()) {
                require(receiver.receiveToken(sender, amount));
            }
        }
        return true;
    }

    /**
     * ERC20 Token Standardに準拠した関数
     *
     * 指定したユーザが保有するコインを指定したアドレスに送金する
     * @param sender {address} - 送付操作を実行するユーザのアドレス
     * @param from {address} - 資金源となるユーザのアドレス
     * @param to {address} - 宛先のアドレス
     * @param amount {uint} - 送付するコインの分量
     * @return {bool} 送付に成功した場合は true を返す
     */
    function transferFrom(address sender, address from, address to, uint amount) auth public returns (bool) {
        // アローアンスを超えて送金してないか確認
        require(coinStorage.coinAllowanceOf(sender, from) >= amount);

        // transfer 処理に引き継ぐ
        transfer(from, to, amount);

        // アローアンスを減らす
        coinStorage.setCoinAllowance(from, sender, safeSub(coinStorage.coinAllowanceOf(sender, from), amount));

        return true;
    }

    /**
     * ERC20 Token Standardに準拠した関数
     *
     * spender（支払い元のアドレス）にsender（送信者）がamount分だけ支払うのを許可する
     * この関数が呼ばれる度に送金可能な金額を更新する。
     *
     * @param sender {address} - 許可操作を実行するユーザのアドレス
     * @param spender (address} - 送付操作を許可する対象ユーザのアドレス
     * @param amount {uint} - 送付を許可するコインの分量
     * @return {bool} 許可に成功した場合は true を返す
     */
    function approve(address sender, address spender, uint amount) auth public returns (bool) {
        coinStorage.setCoinAllowance(sender, spender, amount);
        return true;
    }

    /**
     * ERC20 Token Standardに準拠した関数
     *
     * 指定したユーザに対し、送付操作が許可されているトークンの分量を返す
     *
     * @param owner {address} - 資金源となるユーザのアドレス
     * @param spender {address} - 送付操作を許可しているユーザのアドレス
     * @return {uint} 許可されているトークンの分量を返す
     */
    function allowance(address owner, address spender) auth public constant returns (uint) {
        return coinStorage.coinAllowanceOf(owner, spender);
    }

    /**
     * トークンストレージ (このトークンに限り有効なストレージ) を設定する <Ownerのみ実行可能>
     * @param addr {address} - DrivezyPrivateCoinStorage のアドレス
     * @return {bool} Storage の設定に成功したら true を返す
     */
    function setStorage(address addr) auth public returns (bool) {
        coinStorage = DrivezyPrivateCoinStorage(addr);
        SetStorage(msg.sender, addr);
        return true;
    }

    /**
     * 共有ストレージ (一連の発行において共通利用するストレージ) を設定する <Ownerのみ実行可能>
     * @param addr {address} - DrivezyPrivateCoinSharedStorage のアドレス
     * @return {bool} Storage の設定に成功したら true を返す
     */
    function setSharedStorage(address addr) auth public returns (bool) {
        sharedStorage = DrivezyPrivateCoinSharedStorage(addr);
        SetSharedStorage(msg.sender, addr);
        return true;
    }

    /**
     * Coin (ERC20 準拠の公開するコントラクト) を設定する <Ownerのみ実行可能>
     * @param addr {address} - DrivezyPrivateCoin のアドレス
     * @return {bool} Coin の設定に成功したら true を返す
     */
    function setCoin(address addr) auth public returns (bool) {
        coin = DrivezyPrivateCoin(addr);
        SetCoin(msg.sender, addr);
        return true;
    }

    /**
     * 指定したアドレスにコインを発行する <Ownerのみ実行可能>
     * @param receiver {address} - 発行したコインの受取アカウント
     * @param amount {uint} - 発行量
     * @return {bool} 発行に成功したら true を返す
     */
    function mint(address receiver, uint amount) auth public returns (bool) {
        // 1円以上発行しようとしているか確認
        require(amount > 0);

        // 発行残高を増やす
        coinStorage.setTotalSupply(safeAdd(coinStorage.totalSupply(), amount));

        // 自分自身に発行する
        // 発行に先立ち、自分がトークンを持てるようにする
        addApprovedAddress(address(this));
        coinStorage.setCoinBalance(address(this), safeAdd(coinStorage.coinBalanceOf(address(this)), amount));

        // 自分自身から相手に送付する
        require(coin.transfer(receiver, amount));

        // ログに保存
        Mint(msg.sender, receiver, amount);

        return true;
    }

    /**
     * 指定したアドレスからコインを回収する <Ownerのみ実行可能>
     * @param receiver {address} - 回収先のアカウント
     * @param amount {uint} - 回収量
     * @return {bool} 回収に成功したら true を返す
     */
    function burn(address receiver, uint amount) auth public returns (bool) {
        // 1円以上回収しようとしているか確認
        require(amount > 0);

        // 回収先のアカウントの所持金額以下を回収しようとしているか確認
        require(coinStorage.coinBalanceOf(receiver) >= amount);

        // 回収する残量の approve を強制的に設定する
        // 回収に先立ち、自分がトークンを持てるようにする
        approve(address(this), receiver, amount);
        addApprovedAddress(address(this));

        // 自分自身のコントラクトに回収する
        require(coin.transferFrom(receiver, address(this), amount));

        // 回収後、コインを溶かす
        coinStorage.setTotalSupply(safeSub(coinStorage.totalSupply(), amount));
        coinStorage.setCoinBalance(address(this), safeSub(coinStorage.coinBalanceOf(address(this)), amount));

        // ログに保存
        Burn(msg.sender, receiver, amount);

        return true;
    }

    /**
     * 指定したアドレスをホワイトリストに追加 <Ownerのみ実行可能>
     * @param addr {address} - 追加するアカウント
     * @return {bool} 追加に成功したら true を返す
     */
    function addApprovedAddress(address addr) auth public returns (bool) {
        sharedStorage.addApprovedAddress(addr);
        AddApprovedAddress(msg.sender, addr);
        return true;
    }

    /**
     * 指定したアドレスをホワイトリストから削除 <Ownerのみ実行可能>
     * @param addr {address} - 削除するアカウント
     * @return {bool} 削除に成功したら true を返す
     */
    function removeApprovedAddress(address addr) auth public returns (bool) {
        sharedStorage.removeApprovedAddress(addr);
        RemoveApprovedAddress(msg.sender, addr);
        return true;
    }

    /**
     * ユーザ間の送金を許可する <Ownerのみ実行可能>
     * @return {bool} 許可に成功したら true を返す
     */
    function allowTransferBetweenUsers() auth public returns (bool) {
        coinStorage.allowTransferBetweenUsers();
        return true;
    }

    /**
     * ユーザ間の送金を禁止する <Ownerのみ実行可能>
     * @return {bool} 禁止に成功したら true を返す
     */
    function disallowTransferBetweenUsers() auth public returns (bool) {
        coinStorage.disallowTransferBetweenUsers();
        return true;
    }

    /**
     * DSAuth の canCall(src, dst, sig) の override
     * シグネチャと実行者レベルで関数の実行可否を返す
     #
     * @param src {address} - 呼び出し元ユーザのアドレス
     * @param dst {address} - 実行先コントラクトのアドレス
     * @param sig {bytes4} - 関数のシグネチャ (SHA3)
     * @return {bool} 関数が実行可能であれば true を返す
     */
    function canCall(address src, address dst, bytes4 sig) public constant returns (bool) {
        dst; // HACK - 引数を使わないとコンパイラが警告を出す
        sig; // HACK - こちらも同様

        // オーナーによる実行、「信用するコントラクト」からの呼び出し、コインからの呼び出しは許可
        return src == owner || sharedStorage.isOwnerAddress(src) || sharedStorage.isTrustedContractAddress(src) || src == address(coin);
    }

    /**
     * 指定したユーザ間での転送が承認されるかどうか
     * - 受取者が approvedAddress か ownerAddress に属する
     * - coinStorage.canTransferBetweenUsers = false の場合、受取者か送信者のいずれかが ownerAddress または trustedContractAddress に属する
     * @param from {address} - 送付者のアドレス
     * @param to {address} - 受取者のアドレス
     * @return {bool} 転送できる場合は true を返す
     */
    function canTransfer(address from, address to) internal constant returns (bool) {
        // 受取者がオーナーまたは許可された (KYC 通過済み) アドレスかを確認
        require(sharedStorage.isOwnerAddress(to) || sharedStorage.isApprovedAddress(to));

        // ユーザ間の送金が許可されているか、そうでない場合は送り手または受け手が「オーナー」あるいは「信頼できるコントラクト」に入っているか。
        require(coinStorage.canTransferBetweenUsers() || sharedStorage.isOwnerAddress(from) || sharedStorage.isTrustedContractAddress(from) || sharedStorage.isOwnerAddress(to) || sharedStorage.isTrustedContractAddress(to));

        return true;
    }

    /**
     * DSAuth の isAuthorized(src, sig) の override
     * @param src {address} - コントラクトの実行者
     * @param sig {bytes4} - コントラクトのシグネチャの SHA3 値
     * @return {bool} 呼び出し可能な関数の場合は true を返す
     */
    function isAuthorized(address src, bytes4 sig) internal constant returns (bool) {
        return canCall(src, address(this), sig);
    }

    /**
     * 指定されたアドレスがコントラクトであるか判定する
     * @param addr {address} - 判定対象のコントラクト
     * @return {bool} コントラクトであれば true
     */
    function isContract(address addr) public view returns (bool result) {
        uint length;
        assembly {
            // アドレスが持つマシン語のサイズを取得する
            length := extcodesize(addr)
        }

        // 当該アドレスがマシン語を持てばコントラクトと見做せる
        return (length > 0);
    }

    /**
     * DrivezyPrivateCoinAcceptableContract#isDrivezyPrivateTokenAcceptable の override
     * このコントラクトは Private Token を受け取らない
     * @return {bool} 常に false を返す
     */
    function isDrivezyPrivateTokenAcceptable() public pure returns (bool result) {
        return false;
    }
}
function approve(address sender, address spender, uint amount) auth public returns (bool) {
    coinStorage.setCoinAllowance(sender, spender, amount);
    return true;
}

/**
 * ERC20 Token Standardに準拠した関数
 *
 * spender（支払い元のアドレス）にsender（送信者）がamount分だけ支払うのを許可する
 * この関数が呼ばれる度に送金可能な金額を更新する。
 *
 * @param sender {address} - 許可操作を実行するユーザのアドレス
 * @param spender (address} - 送付操作を許可する対象ユーザのアドレス
 * @param amount {uint} - 送付を許可するコインの分量
 * @return {bool} 許可に成功した場合は true を返す
 */