contract LockedCoin is ERC777 {

    Lockable public coin; //MASTER COIN
    string public uri="https://www.epigeon.org/lockable";
    
    mapping(address => mapping(bytes32 => uint256)) private _lockedAmmountByHash;
    mapping(address => mapping(bytes32 => uint256)) private _lockedAmmountByHashOfSender;
    mapping(address => mapping(bytes32 => address)) private _holderToLockerByHash;

    constructor () ERC777("LockedCoin", "LdC", new address[](0)) {
        coin = Lockable(msg.sender);
    }
    
    function balanceByHash(address to, bytes32 hash) public view returns (uint256 amount){
        return _lockedAmmountByHash[to][hash];
    }
    
    function burnByHash(address to, bytes32 hash) public returns (uint256 amount){
        require(msg.sender == address(coin), "Only available for master coin");
        require(_lockedAmmountByHash[to][hash] > 0, "No amount locked with given hash");
        uint256 lockedValue = _lockedAmmountByHash[to][hash];
        _burn(to, lockedValue, "", "");
        _lockedAmmountByHash[to][hash] = 0;
        _lockedAmmountByHashOfSender[_holderToLockerByHash[to][hash]][hash] = 0;
        _holderToLockerByHash[to][hash] = address(0);
        
        return lockedValue;
    }
    
    function burnByHash(address from, address to, bytes32 hash) public returns (uint256 amount){
        require(msg.sender == address(coin), "Only available for master coin");
        require(_lockedAmmountByHash[to][hash] > 0, "No amount locked with given hash");
        require(_holderToLockerByHash[to][hash] == from, "Not locked by this address");
        uint256 lockedValue = _lockedAmmountByHash[to][hash];
        _burn(to, lockedValue, "", "");
        _lockedAmmountByHash[to][hash] = 0;
        _lockedAmmountByHashOfSender[from][hash] = 0;
        _holderToLockerByHash[to][hash] = address(0); 
        
        return lockedValue;
    }
    
    function getOwner() public view returns (address owner) {
        return coin.getOwner();
    }
    
    function lockerOf(address to, bytes32 hash) public view returns (address from){
        return _holderToLockerByHash[to][hash];
    }
    
    function lockedAmount(address from, bytes32 hash) public view returns (uint256 amount){
        return _lockedAmmountByHashOfSender[from][hash];
    }
    
    function mintByHash(address to, uint256 amount, bytes32 hash, address from) public {
        require(msg.sender == address(coin), "Only available for master coin");
        require(_lockedAmmountByHash[to][hash] == 0, "Amount locked with the same hash");
        _mint(to, amount, "", "");
        _lockedAmmountByHash[to][hash] = amount;
        _lockedAmmountByHashOfSender[from][hash] = amount;
        _holderToLockerByHash[to][hash] = from;
    }
    
    function reclaim(address to, string memory unlockerPhrase) public {
        bytes32 hash = keccak256(bytes(unlockerPhrase));
        require(_holderToLockerByHash[to][hash] == msg.sender, "Not locked by this address");
        burnByHash(msg.sender, to, hash);
        coin.reclaimByLockedCoinContract(msg.sender, to, hash);
    }
    
    function setUri(string memory url) public {
        require(msg.sender == coin.getOwner());
        uri = url;
    }
    
    function unlock(string memory unlockerPhrase) public {
        bytes32 hash = keccak256(bytes(unlockerPhrase));
        coin.unlockByLockedCoinContract(msg.sender, hash);
    }
    
    //TRANSFERS ONLY ALLOWED FROM MASTER COIN
    function _beforeTokenTransfer(address operator, address from, address to, uint256 amount) internal override {
        require(msg.sender == address(coin), "Unlock to transferable coin");
        super._beforeTokenTransfer(operator, from, to, amount);
    }
}
//----------------------------------------------------------------------------------
function _beforeTokenTransfer(address operator, address from, address to, uint256 amount) internal override {
    require(msg.sender == address(coin), "Unlock to transferable coin");
    super._beforeTokenTransfer(operator, from, to, amount);
}

//TRANSFERS ONLY ALLOWED FROM MASTER COIN