contract MBDAAsset {

    using SafeMath for uint256;

    //
    // events
    //
    // ERC20 events
    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);

    // mint/burn events
    event Mint(address indexed _to, uint256 _amount, uint256 _newTotalSupply);
    event Burn(address indexed _from, uint256 _amount, uint256 _newTotalSupply);

    // admin events
    event BlockLockSet(uint256 _value);
    event NewAdmin(address _newAdmin);
    event NewManager(address _newManager);
    event NewInvestor(address _newInvestor);
    event RemovedInvestor(address _investor);
    event FundAssetsChanged(string indexed tokenSymbol, string assetInfo, uint8 amount, uint totalAssetAmount);

    modifier onlyAdmin {
        require(msg.sender == admin, "Only admin can perform this operation");
        _;
    }

    modifier managerOrAdmin {
        require(msg.sender == manager || msg.sender == admin, "Only manager or admin can perform this operation");
        _;
    }

    modifier boardOrAdmin {
        require(msg.sender == board || msg.sender == admin, "Only admin or board can perform this operation");
        _;
    }

    modifier blockLock(address _sender) {
        require (!isLocked() || _sender == admin, "Contract is locked except for the admin");
        _;
    }

    struct Asset {
        string assetTicker;
        string assetInfo;
        uint8 assetPercentageParticipation;
    }

    struct Investor {
        string info;
        bool exists;
    }

    uint256 public totalSupply;
    string public name;
    uint8 public decimals;
    string public symbol;
    address public admin;
    address public board;
    address public manager;
    uint256 public lockedUntilBlock;
    bool public canChangeAssetsType;
    bool public canChangeAssetsQuantity;
    bool public hasWhiteList;
    bool public isSyndicate;
    string public urlFinancialDetailsDocument;
    bytes32 public financialDetailsHash;
    string[] public tradingPlatforms;
    mapping (address => uint256) public balances;
    mapping (address => mapping (address => uint256)) public allowed;
    mapping (address => Investor) public clearedInvestors;
    Asset[] public assets;

    /**
    * @dev Constructor
	* @param _fundAdmin - Fund admin
    * @param _fundBoard - Board
    * @param _tokenName - Detailed ERC20 token name
    * @param _decimalUnits - Detailed ERC20 decimal units
    * @param _tokenSymbol - Detailed ERC20 token symbol
    * @param _lockedUntilBlock - Block lock
    * @param _newTotalSupply - Total Supply owned by the contract itself, only Manager can move
    * @param _canChangeAssetsType - True allows the Manager to change assets in the portfolio
    * @param _canChangeAssetsQuantity - True allows Manager to rebalance the portfolio
    * @param _hasWhiteList - Allows transfering only between whitelisted addresses
    * @param _isSyndicate - Allows secondary market
    */
    constructor
    (
        address _fundAdmin,
        address _fundBoard,
        string memory _tokenName,
        uint8 _decimalUnits,
        string memory _tokenSymbol,
        uint256 _lockedUntilBlock,
        uint256 _newTotalSupply,
        bool _canChangeAssetsType,
        bool _canChangeAssetsQuantity,
        bool _hasWhiteList,
        bool _isSyndicate
    )
        public
    {
        name = _tokenName;
        require(_decimalUnits<=18, "Decimal units should be 18 or lower");
        decimals = _decimalUnits;
        symbol = _tokenSymbol;
        lockedUntilBlock = _lockedUntilBlock;
        admin = _fundAdmin;
        board = _fundBoard;
        totalSupply = _newTotalSupply;
        canChangeAssetsType = _canChangeAssetsType;
        canChangeAssetsQuantity = _canChangeAssetsQuantity;
        hasWhiteList = _hasWhiteList;
        isSyndicate = _isSyndicate;
        balances[address(this)] = totalSupply;
        Investor memory tmp = Investor("Contract", true);
        clearedInvestors[address(this)] = tmp;
        emit NewInvestor(address(this));
    }

    /**
    * @dev Set financial details url
	* @param _url - URL
    * @return True if success
    */
    function setFinancialDetails(string memory _url)
        public
        onlyAdmin
        returns (bool)
    {
        urlFinancialDetailsDocument = _url;
        return true;
    }

    /**
    * @dev Set financial details IPFS hash
	* @param _hash - URL
    * @return True if success
    */
    function setFinancialDetailsHash(bytes32 _hash)
        public
        onlyAdmin
        returns (bool)
    {
        financialDetailsHash = _hash;
        return true;
    }

    /**
    * @dev Add trading platform
	* @param _details - Details of the trading platform
    * @return True if success
    */
    function addTradingPlatform(string memory _details)
        public
        onlyAdmin
        returns (bool)
    {
        tradingPlatforms.push(_details);
        return true;
    }

    /**
    * @dev Remove trading platform
	* @param _index - Index of the trading platform to be removed
    * @return True if success
    */
    function removeTradingPlatform(uint256 _index)
        public
        onlyAdmin
        returns (bool)
    {
        require(_index < tradingPlatforms.length, "Invalid platform index");
        tradingPlatforms[_index] = tradingPlatforms[tradingPlatforms.length - 1];
        tradingPlatforms.length--;
        return true;
    }

    /**
    * @dev Whitelists an Investor
	* @param _investor - Address of the investor
    * @param _investorInfo - Info
    * @return True if success
    */
    function addNewInvestor(address _investor, string memory _investorInfo)
        public
        onlyAdmin
        returns (bool)
    {
        require(_investor != address(0), "Invalid investor address");
        Investor memory tmp = Investor(_investorInfo, true);
        clearedInvestors[_investor] = tmp;
        emit NewInvestor(_investor);
        return true;
    }

    /**
    * @dev Removes an Investor from whitelist
	* @param _investor - Address of the investor
    * @return True if success
    */
    function removeInvestor(address _investor)
        public
        onlyAdmin
        returns (bool)
    {
        require(_investor != address(0), "Invalid investor address");
        delete(clearedInvestors[_investor]);
        emit RemovedInvestor(_investor);
        return true;
    }

    /**
    * @dev Add new asset to Portfolio
	* @param _assetTicker - Ticker
    * @param _assetInfo - Info
    * @param _assetPercentageParticipation - % of portfolio taken by the asset
    * @return True if success
    */
    function addNewAsset(string memory _assetTicker, string memory _assetInfo, uint8 _assetPercentageParticipation)
        public
        onlyAdmin
        returns (bool success)
    {
        uint totalPercentageAssets = 0;
        for (uint256 i = 0; i < assets.length; i++) {
            require(keccak256(bytes(_assetTicker)) != keccak256(bytes(assets[i].assetTicker)), "An asset cannot be assigned twice");
            totalPercentageAssets = SafeMath.add(assets[i].assetPercentageParticipation, totalPercentageAssets);
        }
        totalPercentageAssets = SafeMath.add(totalPercentageAssets,_assetPercentageParticipation);
        require(totalPercentageAssets<=100, "Total assets number cannot be higher than 100");
        emit FundAssetsChanged(_assetTicker, _assetInfo, _assetPercentageParticipation, totalPercentageAssets);
        Asset memory newAsset = Asset(_assetTicker, _assetInfo, _assetPercentageParticipation);
        assets.push(newAsset);
        return true;
    }

    /**
    * @dev Remove asset from Portfolio
	* @param _assetIndex - Asset
    * @return True if success
    */
    function removeAnAsset(uint8 _assetIndex)
        public
        onlyAdmin
        returns (bool)
    {
        require(canChangeAssetsType, "Cannot change asset portfolio");
        require(_assetIndex<assets.length, "Invalid asset index number. Greater than total assets");
        string memory assetTicker = assets[_assetIndex].assetTicker;
        assets[_assetIndex] = assets[assets.length-1];
        delete assets[assets.length-1];
        assets.length--;
        emit FundAssetsChanged(assetTicker, "", 0, 0);
        return true;
    }

    /**
    * @dev Updates an asset
	* @param _assetTicker - Ticker
    * @param _assetInfo - Info to update
    * @param _newAmount - % of portfolio taken by the asset
    * @return True if success
    */
    function updateAnAssetQuantity(string memory _assetTicker, string memory _assetInfo, uint8 _newAmount)
        public
        onlyAdmin
        returns (bool)
    {
        require(canChangeAssetsQuantity, "Cannot change asset amount");
        require(_newAmount>0, "Cannot set zero asset amount");
        uint256 totalAssets = 0;
        uint256 assetIndex = 0;
        for (uint256 i = 0; i < assets.length; i++) {
            if (keccak256(bytes(_assetTicker)) == keccak256(bytes(assets[i].assetTicker))) {
                assetIndex = i;
                totalAssets = SafeMath.add(totalAssets, _newAmount);
            } else {
                totalAssets = SafeMath.add(totalAssets, assets[i].assetPercentageParticipation);
            }
        }
        emit FundAssetsChanged(_assetTicker, _assetInfo, _newAmount, totalAssets);
        require(totalAssets<=100, "Fund assets total percentage must be less than 100");
        assets[assetIndex].assetPercentageParticipation = _newAmount;
        assets[assetIndex].assetInfo = _assetInfo;
        return true;
    }

    /**
    * @return Number of assets in Portfolio
    */
    function totalAssetsArray()
        public
        view
        returns (uint256)
    {
        return assets.length;
    }

    /**
    * @dev ERC20 Transfer
	* @param _to - destination address
    * @param _value - value to transfer
    * @return True if success
    */
    function transfer(address _to, uint256 _value)
        public
        blockLock(msg.sender)
        returns (bool)
    {
        address from = (admin == msg.sender) ? address(this) : msg.sender;
        require(isTransferValid(from, _to, _value), "Invalid Transfer Operation");
        balances[from] = balances[from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(from, _to, _value);
        return true;
    }

    /**
    * @dev ERC20 Approve
	* @param _spender - destination address
    * @param _value - value to be approved
    * @return True if success
    */
    function approve(address _spender, uint256 _value)
        public
        blockLock(msg.sender)
        returns (bool)
    {
        require(_spender != address(0), "ERC20: approve to the zero address");

        address from = (admin == msg.sender) ? address(this) : msg.sender;
        allowed[from][_spender] = _value;
        emit Approval(from, _spender, _value);
        return true;
    }

    /**
    * @dev ERC20 TransferFrom
	* @param _from - source address
    * @param _to - destination address
    * @param _value - value
    * @return True if success
    */
    function transferFrom(address _from, address _to, uint256 _value)
        public
        blockLock(_from)
        returns (bool)
    {
        // check sufficient allowance
        require(_value <= allowed[_from][msg.sender], "Value informed is invalid");
        require(isTransferValid(_from, _to, _value), "Invalid Transfer Operation");
        // transfer tokens
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value, "Value lower than approval");

        emit Transfer(_from, _to, _value);
        return true;
    }

    /**
    * @dev Mint new tokens. Can only be called by minter or owner
    * @param _to - destination address
    * @param _value - value
    * @return True if success
    */
    function mint(address _to, uint256 _value)
        public
        managerOrAdmin
        blockLock(msg.sender)
        returns (bool)
    {

        balances[_to] = balances[_to].add(_value);
        totalSupply = totalSupply.add(_value);

        emit Mint(_to, _value, totalSupply);
        emit Transfer(address(0), _to, _value);

        return true;
    }

    /**
    * @dev Burn tokens
    * @param _account - address
    * @param _value - value
    * @return True if success
    */
    function burn(address payable _account, uint256 _value)
        public
        payable
        blockLock(msg.sender)
        managerOrAdmin
        returns (bool)
      {
        require(_account != address(0), "ERC20: burn from the zero address");

        totalSupply = totalSupply.sub(_value);
        balances[_account] = balances[_account].sub(_value);
        emit Transfer(_account, address(0), _value);
        emit Burn(_account, _value, totalSupply);
        if (msg.value > 0) {
            (bool success,) = _account.call.value(msg.value)("");
            require(success, "Ether transfer failed.");
        }
        return true;
    }

    /**
    * @dev Set block lock. Until that block (exclusive) transfers are disallowed
    * @param _lockedUntilBlock - Block Number
    * @return True if success
    */
    function setBlockLock(uint256 _lockedUntilBlock)
        public
        boardOrAdmin
        returns (bool)
    {
        lockedUntilBlock = _lockedUntilBlock;
        emit BlockLockSet(_lockedUntilBlock);
        return true;
    }

    /**
    * @dev Replace current admin with new one
    * @param _newAdmin New token admin
    * @return True if success
    */
    function replaceAdmin(address _newAdmin)
        public
        boardOrAdmin
        returns (bool)
    {
        require(_newAdmin != address(0x0), "Null address");
        admin = _newAdmin;
        emit NewAdmin(_newAdmin);
        return true;
    }

    /**
    * @dev Set an account can perform some operations
    * @param _newManager Manager address
    * @return True if success
    */
    function setManager(address _newManager)
        public
        onlyAdmin
        returns (bool)
    {
        manager = _newManager;
        emit NewManager(_newManager);
        return true;
    }

    /**
    * @dev ERC20 balanceOf
    * @param _owner Owner address
    * @return True if success
    */
    function balanceOf(address _owner)
        public
        view
        returns (uint256)
    {
        return balances[_owner];
    }

    /**
    * @dev ERC20 allowance
    * @param _owner Owner address
    * @param _spender Address allowed to spend from Owner's balance
    * @return uint256 allowance
    */
    function allowance(address _owner, address _spender)
        public
        view
        returns (uint256)
    {
        return allowed[_owner][_spender];
    }

    /**
    * @dev Are transfers currently disallowed
    * @return True if disallowed
    */
    function isLocked()
        public
        view
        returns (bool)
    {
        return lockedUntilBlock > block.number;
    }

    /**
    * @dev Checks if transfer parameters are valid
    * @param _from Source address
    * @param _to Destination address
    * @param _amount Amount to check
    * @return True if valid
    */
    function isTransferValid(address _from, address _to, uint256 _amount)
        public
        view
        returns (bool)
    {
        if (_from == address(0)) {
            return false;
        }

        if (_to == address(0)) {
            return false;
        }

        if (!hasWhiteList) {
            return  balances[_from] >= _amount;    // sufficient balance
        }

        bool fromOK = clearedInvestors[_from].exists;

        if (!isSyndicate) {
            return  balances[_from] >= _amount &&   // sufficient balance
            fromOK                                     // a seller holder within the whitelist
            ;
        }

        bool toOK = clearedInvestors[_to].exists;

        return  balances[_from] >= _amount &&   // sufficient balance
            fromOK &&                              // a seller holder within the whitelist
            toOK                                   // a buyer holder within the whitelist
        ;
    }
}
/**
 * @title MBDAAsset is a template for MB Digital Asset token
 * */
function setFinancialDetailsHash(bytes32 _hash)
    public
    onlyAdmin
    returns (bool)
{
    financialDetailsHash = _hash;
    return true;
}

/**
 * @dev Set financial details IPFS hash
 	* @param _hash - URL
 * @return True if success
 */