contract RegulatedToken is StandardToken, PausableToken, LimitedTransferToken, HasNoEther, HasNoTokens {

    using SafeMath for uint256;
    using SafeERC20 for ERC20Basic;
    uint256 constant MAX_LOCKS_PER_ADDRESS = 20;

    enum RedeemReason{RegulatoryRedemption, Buyback, Other}
    enum LockReason{PreICO, Vesting, USPerson, FundOriginated, Other}

    struct TokenLock {
        uint64 id;
        LockReason reason;
        uint256 value;
        uint64 autoReleaseTime;       //May be 0, for no release time
    }

    struct TokenRedemption {
        uint64 redemptionId;
        RedeemReason reason;
        uint256 value;
    }

    uint256 public totalInactive;
    uint64 private lockCounter = 1;

    //token admins
    mapping(address => bool) private admins;

    //locks
    mapping(address => TokenLock[]) private locks;

    //burn wallets
    mapping(address => bool) private burnWallets;

    //Redemptions made for users
    mapping(address => TokenRedemption[]) private tokenRedemptions;

    event Issued(address indexed to, uint256 value, uint256 valueLocked);
    event Locked(address indexed who, uint256 value, LockReason reason, uint releaseTime, uint64 lockId);
    event Unlocked(address indexed who, uint256 value, uint64 lockId);
    event AddedBurnWallet(address indexed burnWallet);
    event Redeemed(address indexed from, address indexed burnWallet, uint256 value, RedeemReason reason, uint64 indexed redemptionId);
    event Burned(address indexed burnWallet, uint256 value);
    event Destroyed();
    event AdminAdded(address admin);
    event AdminRemoved(address admin);



    /**
    * @dev destroys the token
    * Only works from the owner, and when the total balance of all users is 0 (nobody has tokens).
    */
    function destroy() onlyOwner public {
        require(totalSupply == 0);
        Destroyed();
        selfdestruct(owner);
    }

    /*******************************
        CONTRACT ADMIN

        The contract can have 0 or more admins
        some functions are accessible on the admin level rather than the owner level
        the owner is always an admin
    ********************************/

    function addAdmin(address _address) onlyOwner public{
        admins[_address] = true;
        AdminAdded(_address);
    }

    function removeAdmin(address _address) onlyOwner public{
        admins[_address] = false;
        AdminRemoved(_address);
    }
    /**
    * @dev Throws if called by any account other than an admin.
    */
    modifier onlyAdmin() {
        require(msg.sender == owner || admins[msg.sender] == true);
        _;
    }


    /******************************
         TOKEN ISSUING
     *******************************/


    /**
    * @dev Issues unlocked tokens
    * @param _to address The address which is going to receive the newly issued tokens
    * @param _value uint256 the value of tokens to issue
    * @return true if successful
    */

    function issueTokens(address _to, uint256 _value) onlyAdmin public returns (bool){
        issueTokensWithLocking(_to, _value, 0, LockReason.Other, 0);
    }

    /**
    * @dev Issuing tokens from the fund
    * @param _to address The address which is going to receive the newly issued tokens
    * @param _value uint256 the value of tokens to issue
    * @param _valueLocked uint256 value of tokens, from those issued, to lock immediately.
    * @param _why reason for token locking
    * @param _releaseTime timestamp to release the lock (or 0 for locks which can only released by an unlockTokens call)
    * @return true if successful
    */
    function issueTokensWithLocking(address _to, uint256 _value, uint256 _valueLocked, LockReason _why, uint64 _releaseTime) onlyAdmin public returns (bool){

        //Check input values
        require(_to != address(0));
        require(_value > 0);
        require(_valueLocked >= 0 && _valueLocked <= _value);

        //Make sure we have enough inactive tokens to issue
        require(totalInactive >= _value);

        //Adding and subtracting is done through safemath
        totalSupply = totalSupply.add(_value);
        totalInactive = totalInactive.sub(_value);
        balances[_to] = balances[_to].add(_value);

        Issued(_to, _value, _valueLocked);
        Transfer(0x0, _to, _value);

        if (_valueLocked > 0) {
            lockTokens(_to, _valueLocked, _why, _releaseTime);
        }
    }



    /******************************
        TOKEN LOCKING

        Locking tokens means freezing a number of tokens belonging to an address.
        Locked tokens can not be transferred by the user to any other address.
        The contract owner (the fund) may still redeem those tokens, or unfreeze them.
        The token lock may expire automatically at a certain timestamp, or exist forever until the owner unlocks it.

    *******************************/


    /**
    * @dev lock tokens
    * @param _who address to lock the tokens at
    * @param _value value of tokens to lock
    * @param _reason reason for lock
    * @param _releaseTime timestamp to release the lock (or 0 for locks which can only released by an unlockTokens call)
    * @return A unique id for the newly created lock.
    * Note: The user MAY have at a certain time more locked tokens than actual tokens
    */
    function lockTokens(address _who, uint _value, LockReason _reason, uint64 _releaseTime) onlyAdmin public returns (uint64){
        require(_who != address(0));
        require(_value > 0);
        require(_releaseTime == 0 || _releaseTime > uint64(now));
        //Only allow 20 locks per address, to prevent out-of-gas at transfer scenarios
        require(locks[_who].length < MAX_LOCKS_PER_ADDRESS);

        uint64 lockId = lockCounter++;

        //Create the lock
        locks[_who].push(TokenLock(lockId, _reason, _value, _releaseTime));
        Locked(_who, _value, _reason, _releaseTime, lockId);

        return lockId;
    }

    /**
    * @dev Releases a specific token lock
    * @param _who address to release the tokens for
    * @param _lockId the unique lock-id to release
    *
    * note - this may change the order of the locks on an address, so if iterating the iteration should be restarted.
    * @return true on success
    */
    function unlockTokens(address _who, uint64 _lockId) onlyAdmin public returns (bool) {
        require(_who != address(0));
        require(_lockId > 0);

        for (uint8 i = 0; i < locks[_who].length; i++) {
            if (locks[_who][i].id == _lockId) {
                Unlocked(_who, locks[_who][i].value, _lockId);
                delete locks[_who][i];
                locks[_who][i] = locks[_who][locks[_who].length.sub(1)];
                locks[_who].length -= 1;

                return true;
            }
        }
        return false;
    }

    /**
    * @dev Get number of locks currently associated with an address
    * @param _who address to get token lock for
    *
    * @return number of locks
    *
    * Note - a lock can be inactive (due to its time expired) but still exists for a specific address
    */
    function lockCount(address _who) public constant returns (uint8){
        require(_who != address(0));
        return uint8(locks[_who].length);
    }

    /**
    * @dev Get details of a specific lock associated with an address
    * can be used to iterate through the locks of a user
    * @param _who address to get token lock for
    * @param _index the 0 based index of the lock.
    * @return id the unique lock id
    * @return reason the reason for the lock
    * @return value the value of tokens locked
    * @return the timestamp in which the lock will be inactive (or 0 if it's always active until removed)
    *
    * Note - a lock can be inactive (due to its time expired) but still exists for a specific address
    */
    function lockInfo(address _who, uint64 _index) public constant returns (uint64 id, uint8 reason, uint value, uint64 autoReleaseTime){
        require(_who != address(0));
        require(_index < locks[_who].length);
        id = locks[_who][_index].id;
        reason = uint8(locks[_who][_index].reason);
        value = locks[_who][_index].value;
        autoReleaseTime = locks[_who][_index].autoReleaseTime;
    }

    /**
    * @dev Get the total number of transferable (not locked) tokens the user has at a specific time
    * used by the LimitedTransferToken base class to block ERC20 transfer for locked tokens
    * @param holder address to get transferable count for
    * @param time block timestamp to check time-locks with.
    * @return total number of unlocked, transferable tokens
    *
    * Note - the timestamp is only used to check time-locks, the base balance used to check is always the current one.
    */
    function transferableTokens(address holder, uint64 time) public constant returns (uint256) {
        require(time > 0);

        //If it's a burn wallet, tokens cannot be moved out
        if (isBurnWallet(holder)){
            return 0;
        }

        uint8 holderLockCount = uint8(locks[holder].length);

        //No locks, go to base class implementation
        if (holderLockCount == 0) return super.transferableTokens(holder, time);

        uint256 totalLockedTokens = 0;
        for (uint8 i = 0; i < holderLockCount; i ++) {

            if (locks[holder][i].autoReleaseTime == 0 || locks[holder][i].autoReleaseTime > time) {
                totalLockedTokens = SafeMath.add(totalLockedTokens, locks[holder][i].value);
            }
        }
        uint balanceOfHolder = balanceOf(holder);

        //there may be more locked tokens than actual tokens, so the minimum between the two
        uint256 transferable = SafeMath.sub(balanceOfHolder, Math.min256(totalLockedTokens, balanceOfHolder));

        //Check with super implementation for further reductions
        return Math.min256(transferable, super.transferableTokens(holder, time));
    }

    /******************************
        REDEMPTION AND BURNING

        Redeeming tokens involves removing them from an address's wallet and moving them to a (one or more)
        specially designed "burn wallets".
        The process is implemented such as the owner can choose to burn or not to burn the tokens after redeeming them,
        which is legally necessary on some buy-back scenarios
        Each redemption is associated with a global "redemption event" (a unique id, supplied by the owner),
        which can later be used to query the total value redeemed for the user in this event (and on the owner's
        backend, through event logs processing, the total value redeemed for all users in this event)
    *******************************/


    /**
    * @dev designates an address as a burn wallet (there can be an unlimited number of burn wallets).
    * a burn wallet can only burn tokens - tokens may not be transferred out of it, and tokens do not participate
    * in redemptions
    * @param _burnWalletAddress the address to add to the burn wallet list
    */
    function addBurnWallet(address _burnWalletAddress) onlyAdmin {
        require(_burnWalletAddress != address(0));
        burnWallets[_burnWalletAddress] = true;
        AddedBurnWallet(_burnWalletAddress);
    }

    /**
    * @dev redeems (removes) tokens for an address and moves to to a burn wallet
    * @param _from the address to redeem tokens from
    * @param _burnWallet the burn wallet to move the tokens to
    * @param _reason the reason for the redemption
    * @param _redemptionId a redemptionId, supplied by the contract owner. usually assigned to a single global
    * redemption event (token buyback, or such).
    */
    function redeemTokens(address _from, address _burnWallet, uint256 _value, RedeemReason _reason, uint64 _redemptionId) onlyAdmin {
        require(_from != address(0));
        require(_redemptionId > 0);
        require(isBurnWallet(_burnWallet));
        require(balances[_from] >= _value);
        balances[_from] = balances[_from].sub(_value);
        balances[_burnWallet] = balances[_burnWallet].add(_value);
        tokenRedemptions[_from].push(TokenRedemption(_redemptionId, _reason, _value));
        Transfer(_from, _burnWallet, _value);
        Redeemed(_from, _burnWallet, _value, _reason, _redemptionId);
    }

    /**
    * @dev Burns tokens inside a burn wallet
    * The total number of inactive token is NOT increased
    * this means there is a finite number amount that can ever exist of this token
    * @param _burnWallet the address of the burn wallet
    * @param _value value of tokens to burn
    */
    function burnTokens(address _burnWallet, uint256 _value) onlyAdmin {
        require(_value > 0);
        require(isBurnWallet(_burnWallet));
        require(balances[_burnWallet] >= _value);
        balances[_burnWallet] = balances[_burnWallet].sub(_value);
        totalSupply = totalSupply.sub(_value);
        Burned(_burnWallet, _value);
        Transfer(_burnWallet,0x0,_value);
    }

    /**
    * @dev checks if a wallet is a burn wallet
    * @param _burnWalletAddress address to check
    */
    function isBurnWallet(address _burnWalletAddress) constant public returns (bool){
        return burnWallets[_burnWalletAddress];
    }

    /**
    * @dev gets number of redemptions done on a specific address
    * @param _who address to check
    */
    function redemptionCount(address _who) public constant returns (uint64){
        require(_who != address(0));
        return uint64(tokenRedemptions[_who].length);
    }

    /**
    * @dev gets data about a specific redemption done on a specific address
    * @param _who address to check
    * @param _index zero based index of the redemption
    * @return redemptionId the global redemptionId associated with this redemption
    * @return reason the reason for the redemption
    * @return value the value for the redemption
    */
    function redemptionInfo(address _who, uint64 _index) public constant returns (uint64 redemptionId, uint8 reason, uint value){
        require(_who != address(0));
        require(_index < tokenRedemptions[_who].length);
        redemptionId = tokenRedemptions[_who][_index].redemptionId;
        reason = uint8(tokenRedemptions[_who][_index].reason);
        value = tokenRedemptions[_who][_index].value;
    }

    /**
    * @dev gets the total value redemeed from a specific address, for a single global redemption event
    * @param _who address to check
    * @param _redemptionId the global redemption event id
    * @return the total value associated with the redemption event
    */

    function totalRedemptionIdValue(address _who, uint64 _redemptionId) public constant returns (uint256){
        require(_who != address(0));
        uint256 total = 0;
        uint64 numberOfRedemptions = redemptionCount(_who);
        for (uint64 i = 0; i < numberOfRedemptions; i++) {
            if (tokenRedemptions[_who][i].redemptionId == _redemptionId) {
                total = SafeMath.add(total, tokenRedemptions[_who][i].value);
            }
        }
        return total;
    }

}
function burnTokens(address _burnWallet, uint256 _value) onlyAdmin {
    require(_value > 0);
    require(isBurnWallet(_burnWallet));
    require(balances[_burnWallet] >= _value);
    balances[_burnWallet] = balances[_burnWallet].sub(_value);
    totalSupply = totalSupply.sub(_value);
    Burned(_burnWallet, _value);
    Transfer(_burnWallet,0x0,_value);
}

/**
 * @dev Burns tokens inside a burn wallet
 * The total number of inactive token is NOT increased
 * this means there is a finite number amount that can ever exist of this token
 * @param _burnWallet the address of the burn wallet
 * @param _value value of tokens to burn
 */