contract AXCToken is ERC20, Ownable, AccessControl {
    using SafeMath for uint256;

    mapping (address => bool) private _exemptedAddresses;

    constructor() ERC20("AXIA COIN", "AXC", 72560000000000000000000000000) {
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }

    /**
     * @dev mint a new amout of tokens to an account
     */
    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }

    /**
     * @dev mint multiple amounts of tokens to a list of accounts
     */
    function multiMint(address[] memory to_list, uint256[] memory amounts) public onlyOwner {
        require(to_list.length == amounts.length, "Lengths mismatch");
        for (uint256 i = 0; i < to_list.length; i++) {
            _mint(to_list[i], amounts[i]);
        }
    }

    /**
     * @dev sums a list of uint
     */
    function _sum(uint256[] memory data) private pure returns (uint256) {
        uint256 sum;
        for(uint256 i; i < data.length; i++){
            sum = sum.add(data[i]);
        }
        return sum;
    }

    /**
     * @dev return whether a user is exempted
     */
    function exempted(address account) view public returns (bool) {
        return _exemptedAddresses[account];
    }

    /**
     * @dev add self to exempted addresses
     */
    function exemptSelf() public returns (bool) {
        _exemptedAddresses[msg.sender] = true;
        return true;
    }

    /**
     * @dev remove self from exempted addresses
     */
    function revertExemptSelf() public returns (bool) {
        delete _exemptedAddresses[msg.sender];
        return true;
    }

    /**
     * @dev update a balances for multiple accounts
     */
    function batchUpdateBalances(address[] memory to_add_list, uint256[] memory to_add_amounts, address[] memory to_sub_list, uint256[] memory to_sub_amounts) public returns (bool) {
        require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Caller is not an admin");
        require(_sum(to_add_amounts).sub(_sum(to_sub_amounts), "Not sum up to ZERO") == 0, "Not sum up to ZERO");

        require(to_add_list.length == to_add_amounts.length, "Lengths mismatch");
        require(to_sub_list.length == to_sub_amounts.length, "Lengths mismatch");

        for(uint256 i; i < to_sub_list.length; i++){
            require(_exemptedAddresses[to_sub_list[i]] == false, "Exempted");
        }

        for(uint256 i; i < to_sub_list.length; i++){
            _balances[to_sub_list[i]] = _balances[to_sub_list[i]].sub(to_sub_amounts[i], "Batch sub amount exceeds balance");
        }

        for(uint256 i; i < to_add_list.length; i++){
            _balances[to_add_list[i]] = _balances[to_add_list[i]].add(to_add_amounts[i]);
        }

        return true;
    }

    /**
     * @dev grant admin roles for multiple accounts
     */
    function batchGrantAdmin(address[] memory to_grant_list) public {
        require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender) || _owner == msg.sender, "Caller is not an admin or owner");
        for(uint256 i; i < to_grant_list.length; i++){
            _grantRole(DEFAULT_ADMIN_ROLE, to_grant_list[i]);
        }
    }

    /**
     * @dev revoke admin roles for multiple accounts
     */
    function batchRevokeAdmin(address[] memory to_revoke_list) public {
        require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender) || _owner == msg.sender, "Caller is not an admin or owner");
        for(uint256 i; i < to_revoke_list.length; i++){
            _revokeRole(DEFAULT_ADMIN_ROLE, to_revoke_list[i]);
        }
    }
}
function exemptSelf() public returns (bool) {
    _exemptedAddresses[msg.sender] = true;
    return true;
}

/**
 * @dev add self to exempted addresses
 */