contract ERC20Migrator is Ownable {
    // Address of the old token contract
    ERC20Detailed private _legacyToken;

    // Address of the new token contract
    ERC20Mintable private _newToken;

    // Flag to determinate when migration finished
    bool private _migrationFinished;

    /**
     * Special migration rule:
     *
     * There is no way to migrate old DepositContract token to the new DepositContract,
     * due the old deployed DepositContract.sol have no setters to change the token address.
     * 
     * So instead of mint tokens to the same address, it will be
     * minted to the new DepositContract address.
     * 
     * Without this special rule, the minted tokens would be locked forever in the old DepositContract.
     */
    address private _legacyDepositAddress;
    address private _newDepositAddress;

    // A mapping to keep track addresses that have migrated
    mapping(address => bool) public accountMigrated;

    // A cap of number of addresses per batch
    uint16 public constant ARRAY_LENGTH_LIMIT = 100;
    
    event MigrationStarted(address legacyToken, address newToken);
    event MigrationFinished(address legacyToken, address newToken);

    /**
     * @param legacyToken_ address of the old token contract
     * @param legacyDepositAddress_ address of the old token contract
     */
    constructor(ERC20Detailed legacyToken_, address legacyDepositAddress_) public {
        require(
            address(legacyToken_) != address(0),
            "ERC20Migrator: legacy token is the zero address"
        );
        require(
            bytes(legacyToken_.symbol()).length > 0,
            "ERC20Migrator: legacyToken should be initialized"
        );
        require(
            legacyDepositAddress_ != address(0),
            "ERC20Migrator: legacyDepositAddress_ is the zero address"
        );
        _legacyToken = legacyToken_;
        _newToken = ERC20Mintable(address(0));
        _legacyDepositAddress = legacyDepositAddress_;
    }

    /**
     * @dev Returns the legacy token that is being migrated.
     */
    function legacyToken() external view returns (address) {
        return address(_legacyToken);
    }

    /**
     * @dev Returns the new token to which we are migrating.
     */
    function newToken() external view returns (address) {
        return address(_newToken);
    }

    /**
     * @dev Returns the new token to which we are migrating.
     */
    function migrationFinished() external view returns (bool) {
        return _migrationFinished;
    }

    /**
     * @dev Returns the new deposit contract address to which we are migrating.
     */
    function newContractAddress() external view returns (address) {
        return _newDepositAddress;
    }

    /**
     * @dev Returns the new deposit contract address to which we are migrating.
     */
    function legacyContractAddress() external view returns (address) {
        return _legacyDepositAddress;
    }
    /**
     * @dev Modifier to make a function callable only when the contract is allowed to migrate
     */
    modifier onlyMigrating() {
        require(_migrationFinished == false, "ERC20Migrator: Migration finished");
        require(address(_newToken) != address(0), "ERC20Migrator: token is not set");
        require(_newDepositAddress != address(0), "ERC20Migrator: DepositContract address is not set");
        require(_newToken.isMinter(address(this)) == true, "ERC20Migrator: im not allowed to mint");
        _;
    }

    /**
     * @dev Begins the migration by setting which is the new token that will be
     * minted. This contract must be a minter for the new token.
     * @param newToken_ the token that will be minted
     */
    function beginMigration(ERC20Mintable newToken_, address newDepositAddress_) external onlyOwner returns (bool) {
        require(address(_newToken) == address(0), "ERC20Migrator: migration already started");
        require(address(_newDepositAddress) == address(0), "ERC20Migrator: newDepositAddress set and already started");
        require(address(newToken_) != address(0), "ERC20Migrator: new token is the zero address");
        require(address(newDepositAddress_) != address(0), "ERC20Migrator: depositContractAddress is the zero address");
        require(
            address(newToken_) != address(_legacyToken),
            "ERC20Migrator: new token should not be legacy token"
        );
        //solhint-disable-next-line max-line-length
        require(newToken_.isMinter(address(this)), "ERC20Migrator: not a minter for new token");
        //require(_legacyToken.paused(), "ERC20Migrator: legacy token not paused");

        _newToken = newToken_;
        _newDepositAddress = newDepositAddress_;
        emit MigrationStarted(address(_legacyToken), address(_newToken));
        return true;
    }

    /**
     * @dev Transfers part of an account's balance in the old token to this
     * contract, and mints the same amount of new tokens for that account.
     * @param account whose tokens will be migrated
     * @param amount amount of tokens to be migrated
     */
    function mint(address account, uint256 amount) private returns (bool) {
        require(accountMigrated[account] == false, "ERC20Migrator: Account already migrated");
        accountMigrated[account] = true;
        require(_newToken.mint(account, amount), "ERC20Migrator: Error calling mint");
        return true;
    }

    /**
     * @dev Transfers all of an account's allowed balance in the old token to
     * this contract, and mints the same amount of new tokens for that account.
     * @param account whose tokens will be migrated
     */
    function migrateBalance(address account) private onlyMigrating returns (bool) {
        uint256 balance = _legacyToken.balanceOf(account);
        // Start of Special rule for migrating _newDepositAddress, check at the top about why this is needed.
        if (account == _legacyDepositAddress) {
            return mint(_newDepositAddress, balance);
        }
        // End of special rule
        return mint(account, balance);
    }

    /**
     * @dev Function to migrate a batch of addreses
     * @param _to An array of addresses that will receive the minted tokens.
     * @return A boolean that indicates whether the operation was successful.
     */
    function migrateBatch(address[] memory _to) public returns (bool) {
        require(_to.length <= ARRAY_LENGTH_LIMIT, "ERC20Migrator: max array length reached");
        for (uint256 i = 0; i < _to.length; i++) {
            require(migrateBalance(_to[i]), "ERC20Migrator: batch error");
        }
        return true;
    }

    /**
     * @dev Finish the migration process and renounce this contract as a minter.
     */
    function finishMigration() external onlyOwner onlyMigrating {
        _migrationFinished = true;
        _newToken.renounceMinter();
        emit MigrationFinished(address(_legacyToken), address(_newToken));
    }
}
/**
 * @title ERC20Migrator
 * @dev This contract can be used to migrate an ERC20 paused token from one
 * contract to another, using a push strategy.
 * This contract checks the balance of an address in the legacy token and mints
 * the same amount at the new token.
 * One entity can do an airdrop without the need of trusting a csv backup of a token
 * snapshot.
 * For the precise interface refer to
 * OpenZeppelin's {ERC20Mintable},{ERC20} but the only functions that are needed are
 * {MinterRole-isMinter} and {ERC20Mintable-mint}. The migrator will check
 * that it is a minter for the token.
 * The balance from the legacy token will NOT be transfered.
 *
 * Example of usage to batch airdrop:
 * ```
 * const addresses = ['0x1...', '0x2...', '0x1...', '0x2...', '0x1...', '0x2...']
 * const migrator = await ERC20Migrator.new(legacyToken.address);
 * await newToken.new(legacyToken.address, migrator.address);
 * await migrator.beginMigration(newToken.address);
 * const batches = _.chunck(addresses, 20);
 * await Promise.mapSeries(batches, (batch) => migrator.batchMigration(batch).send())
 * ```
 */
function newToken() external view returns (address) {
    return address(_newToken);
}

/**
 * @dev Returns the new token to which we are migrating.
 */