contract VirtualSynth is ERC20, IVirtualSynth {
    using SafeMath for uint;
    using SafeDecimalMath for uint;

    ISynth public synth;
    IAddressResolver public resolver;

    bool public settled = false;

    uint8 public constant decimals = 18;

    // track initial supply so we can calculate the rate even after all supply is burned
    uint public initialSupply;

    // track final settled amount of the synth so we can calculate the rate after settlement
    uint public settledAmount;

    constructor(
        ISynth _synth,
        IAddressResolver _resolver,
        address _recipient,
        uint _amount
    ) public ERC20() {
        synth = _synth;
        resolver = _resolver;

        // Assumption: the synth will be issued to us within the same transaction,
        // and this supply matches that
        _mint(_recipient, _amount);

        initialSupply = _amount;
    }

    // INTERNALS

    function exchanger() internal view returns (IExchanger) {
        return IExchanger(resolver.requireAndGetAddress("Exchanger", "Exchanger contract not found"));
    }

    function secsLeft() internal view returns (uint) {
        return exchanger().maxSecsLeftInWaitingPeriod(address(this), synth.currencyKey());
    }

    function calcRate() internal view returns (uint) {
        if (initialSupply == 0) {
            return 0;
        }

        uint synthBalance;

        if (!settled) {
            synthBalance = IERC20(address(synth)).balanceOf(address(this));
            (uint reclaim, uint rebate, ) = exchanger().settlementOwing(address(this), synth.currencyKey());

            if (reclaim > 0) {
                synthBalance = synthBalance.sub(reclaim);
            } else if (rebate > 0) {
                synthBalance = synthBalance.add(rebate);
            }
        } else {
            synthBalance = settledAmount;
        }

        return synthBalance.divideDecimalRound(initialSupply);
    }

    function balanceUnderlying(address account) internal view returns (uint) {
        uint vBalanceOfAccount = balanceOf(account);

        return vBalanceOfAccount.multiplyDecimalRound(calcRate());
    }

    function settleSynth() internal {
        if (settled) {
            return;
        }
        settled = true;

        exchanger().settle(address(this), synth.currencyKey());

        settledAmount = IERC20(address(synth)).balanceOf(address(this));

        emit Settled(totalSupply(), settledAmount);
    }

    // VIEWS

    function name() external view returns (string memory) {
        return string(abi.encodePacked("Virtual Synth ", synth.currencyKey()));
    }

    function symbol() external view returns (string memory) {
        return string(abi.encodePacked("v", synth.currencyKey()));
    }

    // get the rate of the vSynth to the synth.
    function rate() external view returns (uint) {
        return calcRate();
    }

    // show the balance of the underlying synth that the given address has, given
    // their proportion of totalSupply
    function balanceOfUnderlying(address account) external view returns (uint) {
        return balanceUnderlying(account);
    }

    function secsLeftInWaitingPeriod() external view returns (uint) {
        return secsLeft();
    }

    function readyToSettle() external view returns (bool) {
        return secsLeft() == 0;
    }

    // PUBLIC FUNCTIONS

    // Perform settlement of the underlying exchange if required,
    // then burn the accounts vSynths and transfer them their owed balanceOfUnderlying
    function settle(address account) external {
        settleSynth();

        IERC20(address(synth)).transfer(account, balanceUnderlying(account));

        _burn(account, balanceOf(account));
    }

    event Settled(uint totalSupply, uint amountAfterSettled);
}
// Inheritance
// Libraries
// Internal references
// Note: use OZ's IERC20 here as using ours will complain about conflicting names
// during the build
// https://docs.synthetix.io/contracts/source/contracts/virtualsynth
function exchanger() internal view returns (IExchanger) {
    return IExchanger(resolver.requireAndGetAddress("Exchanger", "Exchanger contract not found"));
}

// INTERNALS