import smartpy as sp


class RegistrarStorage(sp.Contract):
    def __init__(self, _ownerAddress, _mainContractAddress):
        self.init(
            contractOwner=_ownerAddress,
            mainContract=_mainContractAddress,
            resolveAddressFromSafleId=sp.map(),
            auctionProcess=sp.map(),
            coinAddressToSafleId=sp.map(),
            OtherCoin=sp.map(
                tkey=sp.TNat,
                tvalue=sp.TRecord(
                    isIndexMapped=sp.TBool,
                    aliasName=sp.TString,
                    coinName=sp.TString
                )
            ),
            isCoinMapped=sp.map(),
            Registrars=sp.map(
                tkey=sp.TAddress,
                tvalue=sp.TRecord(
                    isRegisteredRegistrar=sp.TBool,
                    registrarName=sp.TString,
                    registarAddress=sp.TAddress
                )
            ),
            safleIdToCoinAddress=sp.map(
                tkey=sp.TString,
                tvalue=sp.TMap(sp.TNat, sp.TString)
            ),
            resolveUserAddress=sp.map(),
            registrarNameToAddress=sp.map(),
            isAddressTaken=sp.map(),
            totalRegistrars=0,
            totalSafleIdRegistered=0,
            auctionContractAddress=sp.address("KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT"),
            resolveOldSafleIdFromAddress=sp.map(
                tkey=sp.TAddress,
                tvalue=sp.TList(sp.TBytes)
            ),
            resolveOldSafleID=sp.map(),
            totalRegistrarUpdates=sp.map(),
            resolveOldRegistrarAddress=sp.map(
                tkey=sp.TAddress,
                tvalue=sp.TList(sp.TBytes)
            ),
            totalSafleIDCount=sp.map(),
            unavailableSafleIds=sp.map()
        )

    def onlyOwner(self):
        sp.verify(self.data.contractOwner == sp.sender)

    def onlyMainContract(self):
        sp.verify(self.data.mainContract == sp.sender)

    def registrarChecks(self, _registrarName):
        regNameBytes = sp.pack(_registrarName)
        sp.verify(~self.data.registrarNameToAddress.contains(regNameBytes), "Registrar name is already taken.")
        sp.verify(~self.data.resolveAddressFromSafleId.contains(regNameBytes), "This Registrar name is already registered as an SafleID.")

    def safleIdChecks(self, _safleId, _registrar):
        idBytes = sp.pack(_safleId)

        sp.verify(self.data.Registrars.contains(_registrar), "Invalid Registrar.")
        sp.verify(~self.data.registrarNameToAddress.contains(idBytes), "This SafleId is taken by a Registrar.")
        sp.verify(~self.data.resolveAddressFromSafleId.contains(idBytes), "This SafleId is already registered.")
        sp.verify(~self.data.unavailableSafleIds.contains(_safleId), "SafleId is already used once, not available now")

    def auctionContract(self):
        sp.verify(sp.sender == self.data.auctionContractAddress)

    def coinAddressCheck(self, _userAddress, _index, _registrar):
        sp.verify(self.data.Registrars.contains(_registrar), "Invalid Registrar")
        sp.verify(self.data.OtherCoin.contains(_index), "This index number is not mapped.");
        sp.if self.data.auctionProcess.contains(_userAddress):
            sp.verify(~self.data.auctionProcess[_userAddress])

    @sp.entry_point
    def upgradeMainContractAddress(self, params):
        self.onlyOwner()

        self.data.mainContract = params._mainContractAddress

    @sp.entry_point
    def registerRegistrar(self, params):
        self.registrarChecks(params._registrarName)
        self.onlyMainContract()

        regNameBytes = sp.pack(params._registrarName)
        self.data.Registrars[params._registrar] = sp.record(
            isRegisteredRegistrar=True,
            registrarName=params._registrarName,
            registarAddress=params._registrar
        )

        self.data.registrarNameToAddress[regNameBytes] = params._registrar
        self.data.isAddressTaken[params._registrar] = True
        self.data.totalRegistrars += 1

    @sp.entry_point
    def updateRegistrar(self, params):
        self.registrarChecks(params._newRegistrarName)
        self.onlyMainContract()

        newNameBytes = sp.pack(params._newRegistrarName)

        sp.if ~self.data.totalRegistrarUpdates.contains(params._registrar):
            self.data.totalRegistrarUpdates[params._registrar] = 0
        sp.if ~self.data.resolveOldRegistrarAddress.contains(params._registrar):
            self.data.resolveOldRegistrarAddress[params._registrar] = sp.list([])

        sp.verify(self.data.isAddressTaken.get(params._registrar, False) == True, "Registrar should register first.")
        sp.verify(self.data.totalRegistrarUpdates[params._registrar]+1 <= 5, "Maximum update count reached.") #MAX_NAME_UPDATES

        registrarObject = self.data.Registrars[params._registrar]
        oldName = registrarObject.registrarName
        oldNameBytes = sp.pack(oldName)
        del self.data.registrarNameToAddress[oldNameBytes]

        self.data.resolveOldRegistrarAddress[params._registrar].push(
            sp.pack(self.data.Registrars[params._registrar].registrarName)
        )

        self.data.Registrars[params._registrar].registrarName = params._newRegistrarName
        self.data.Registrars[params._registrar].registarAddress = params._registrar

        self.data.registrarNameToAddress[newNameBytes] = params._registrar
        self.data.totalRegistrarUpdates[params._registrar] += 1

    @sp.onchain_view()
    def resolveRegistrarName(self, params):
        regNameBytes = sp.pack(params._name)
        sp.verify(self.data.registrarNameToAddress.contains(regNameBytes), "Resolver : Registrar is not yet registered for this SafleID.")
        sp.result(self.data.registrarNameToAddress[regNameBytes])

    @sp.entry_point
    def registerSafleId(self, params):
        self.safleIdChecks(params._safleId, params._registrar)
        self.onlyMainContract()

        sp.verify(self.data.isAddressTaken.get(params._userAddress, False) == False, "SafleID already registered")

        idBytes = sp.pack(params._safleId)

        self.data.resolveAddressFromSafleId[idBytes] = params._userAddress
        self.data.isAddressTaken[params._userAddress] = True
        self.data.resolveUserAddress[params._userAddress] = params._safleId
        self.data.totalSafleIdRegistered += 1

    @sp.entry_point
    def updateSafleId(self, params):
        self.safleIdChecks(params._safleId, params._registrar)
        self.onlyMainContract()

        sp.if ~self.data.totalSafleIDCount.contains(params._userAddress):
            self.data.totalSafleIDCount[params._userAddress] = 0

        sp.verify(self.data.totalSafleIDCount[params._userAddress]+1 <= 5, "Maximum update count reached.") #MAX_NAME_UPDATES
        sp.verify(self.data.isAddressTaken.get(params._userAddress, False) == True, "SafleID not registered.")
        sp.verify(self.data.auctionProcess.get(params._userAddress, False) == False, "SafleId cannot be updated inbetween Auction.")

        idBytes = sp.pack(params._safleId)

        oldName = self.data.resolveUserAddress[params._userAddress]
        oldIdBytes = sp.pack(oldName)

        self.data.unavailableSafleIds[oldName] = True
        del self.data.resolveAddressFromSafleId[oldIdBytes]
        self.oldSafleIds(params._userAddress, oldIdBytes)

        self.data.resolveAddressFromSafleId[idBytes] = params._userAddress
        self.data.resolveUserAddress[params._userAddress] = params._safleId

        self.data.totalSafleIDCount[params._userAddress] += 1
        self.data.totalSafleIdRegistered += 1

    @sp.onchain_view()
    def resolveSafleId(self, params):
        idBytes = sp.pack(params._safleId)
        sp.verify(sp.len(sp.pack(params._safleId)) != 0, "Resolver : user SafleID should not be empty.")
        sp.verify(self.data.resolveAddressFromSafleId.contains(idBytes), "Resolver : User is not yet registered for this SafleID.")
        sp.result(self.data.resolveAddressFromSafleId[idBytes])

    @sp.entry_point
    def transferSafleId(self, params):
        self.auctionContract()

        idBytes = sp.pack(params._safleId)

        sp.verify(self.data.isAddressTaken.get(params._oldOwner, False) == True, "You are not an owner of this safleId.")
        sp.verify(self.data.resolveAddressFromSafleId.contains(idBytes), "This SafleId does not have an owner.")

        self.oldSafleIds(params._oldOwner, idBytes)
        self.data.isAddressTaken[params._oldOwner] = False

        self.data.resolveAddressFromSafleId[idBytes] = params._newOwner

        self.data.auctionProcess[params._oldOwner] = False
        self.data.isAddressTaken[params._newOwner] = True
        self.data.resolveUserAddress[params._newOwner] = params._safleId

    def oldSafleIds(self, _userAddress, _safleId):
        sp.if ~self.data.resolveOldSafleIdFromAddress.contains(_userAddress):
            self.data.resolveOldSafleIdFromAddress[_userAddress] = sp.list([])

        self.data.resolveOldSafleIdFromAddress[_userAddress].push(_safleId)
        self.data.resolveOldSafleID[_safleId] = _userAddress

    @sp.entry_point
    def setAuctionContract(self, params):
        self.onlyOwner()

        self.data.auctionContractAddress = params._auctionAddress

    @sp.entry_point
    def auctionInProcess(self, params):
        sp.set_type(params._safleId, sp.TString)
        self.auctionContract()

        idBytes = sp.pack(params._safleId)

        sp.verify(sp.len(params._safleId) != 0, "Resolver : User SafleID should not be empty.")
        sp.verify(self.data.resolveAddressFromSafleId.contains(idBytes), "Resolver : User is not yet registered for this SafleID.")
        self.data.auctionProcess[params._safleIdOwner] = True

    @sp.entry_point
    def mapCoin(self, params):
        self.onlyMainContract()

        sp.verify(self.data.OtherCoin.get(params._indexnumber, sp.record(
                isIndexMapped = False,
                aliasName = "",
                coinName = ""
            )).isIndexMapped == False, "This index number has already been mapped.")
        sp.verify(self.data.isCoinMapped.get(params._coinName, False) == False, "This coin is already mapped.")
        sp.verify(self.data.Registrars[params._registrar].isRegisteredRegistrar, "Invalid Registrar.")

        self.data.OtherCoin[params._indexnumber] = sp.record(
            isIndexMapped = True,
            aliasName = params._aliasName,
            coinName = params._coinName
        )

        self.data.isCoinMapped[params._coinName] = True

    @sp.entry_point
    def registerCoinAddress(self, params):
        sp.set_type(params._index, sp.TNat)

        self.coinAddressCheck(params._userAddress, params._index, params._registrar)
        self.onlyMainContract()

        sp.verify(self.data.Registrars[params._registrar].isRegisteredRegistrar, "Invalid Registrar.")
        sp.verify(self.data.auctionProcess.get(params._userAddress, False) == False)
        sp.verify(self.data.OtherCoin[params._index].isIndexMapped == True)

        safleId = self.data.resolveUserAddress[params._userAddress]
        sp.if ~self.data.safleIdToCoinAddress.contains(safleId):
            self.data.safleIdToCoinAddress[safleId] = sp.map({})
        self.data.safleIdToCoinAddress[safleId][params._index] = params._address
        self.data.coinAddressToSafleId[params._address] = safleId

    @sp.entry_point
    def updateCoinAddress(self, params):
        sp.set_type(params._index, sp.TNat)

        self.coinAddressCheck(params._userAddress, params._index, params._registrar)
        self.onlyMainContract()

        sp.verify(self.data.Registrars[params._registrar].isRegisteredRegistrar, "Invalid Registrar")
        sp.verify(self.data.auctionProcess.get(params._userAddress, False) == False)
        sp.verify(self.data.OtherCoin[params._index].isIndexMapped == True, "This index number is not mapped.")

        safleId = self.data.resolveUserAddress[params._userAddress]
        sp.verify(self.data.safleIdToCoinAddress[safleId].contains(params._index))

        self.data.safleIdToCoinAddress[safleId][params._index] = params._newAddress
        self.data.coinAddressToSafleId[params._newAddress] = safleId

    @sp.onchain_view()
    def coinAddressToId(self, params):
        sp.result(self.data.coinAddressToSafleId[params._address])

    @sp.onchain_view()
    def idToCoinAddress(self, params):
        sp.result(self.data.safleIdToCoinAddress[params._safleId][params._index])

import smartpy as sp


class RegistrarStorage(sp.Contract):
    def __init__(self, _ownerAddress, _mainContractAddress):
        self.init(
            contractOwner=_ownerAddress,
            mainContract=_mainContractAddress,
            resolveAddressFromSafleId=sp.map(),
            auctionProcess=sp.map(),
            coinAddressToSafleId=sp.map(),
            OtherCoin=sp.map(
                tkey=sp.TNat,
                tvalue=sp.TRecord(
                    isIndexMapped=sp.TBool,
                    aliasName=sp.TString,
                    coinName=sp.TString
                )
            ),
            isCoinMapped=sp.map(),
            Registrars=sp.map(
                tkey=sp.TAddress,
                tvalue=sp.TRecord(
                    isRegisteredRegistrar=sp.TBool,
                    registrarName=sp.TString,
                    registarAddress=sp.TAddress
                )
            ),
            safleIdToCoinAddress=sp.map(
                tkey=sp.TString,
                tvalue=sp.TMap(sp.TNat, sp.TString)
            ),
            resolveUserAddress=sp.map(),
            registrarNameToAddress=sp.map(),
            isAddressTaken=sp.map(),
            totalRegistrars=0,
            totalSafleIdRegistered=0,
            auctionContractAddress=sp.address("KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT"),
            resolveOldSafleIdFromAddress=sp.map(
                tkey=sp.TAddress,
                tvalue=sp.TList(sp.TBytes)
            ),
            resolveOldSafleID=sp.map(),
            totalRegistrarUpdates=sp.map(),
            resolveOldRegistrarAddress=sp.map(
                tkey=sp.TAddress,
                tvalue=sp.TList(sp.TBytes)
            ),
            totalSafleIDCount=sp.map(),
            unavailableSafleIds=sp.map()
        )

    def onlyOwner(self):
        sp.verify(self.data.contractOwner == sp.sender)

    def onlyMainContract(self):
        sp.verify(self.data.mainContract == sp.sender)

    def registrarChecks(self, _registrarName):
        regNameBytes = sp.pack(_registrarName)
        sp.verify(~self.data.registrarNameToAddress.contains(regNameBytes), "Registrar name is already taken.")
        sp.verify(~self.data.resolveAddressFromSafleId.contains(regNameBytes), "This Registrar name is already registered as an SafleID.")

    def safleIdChecks(self, _safleId, _registrar):
        idBytes = sp.pack(_safleId)

        sp.verify(self.data.Registrars.contains(_registrar), "Invalid Registrar.")
        sp.verify(~self.data.registrarNameToAddress.contains(idBytes), "This SafleId is taken by a Registrar.")
        sp.verify(~self.data.resolveAddressFromSafleId.contains(idBytes), "This SafleId is already registered.")
        sp.verify(~self.data.unavailableSafleIds.contains(_safleId), "SafleId is already used once, not available now")

    def auctionContract(self):
        sp.verify(sp.sender == self.data.auctionContractAddress)

    def coinAddressCheck(self, _userAddress, _index, _registrar):
        sp.verify(self.data.Registrars.contains(_registrar), "Invalid Registrar")
        sp.verify(self.data.OtherCoin.contains(_index), "This index number is not mapped.");
        sp.if self.data.auctionProcess.contains(_userAddress):
            sp.verify(~self.data.auctionProcess[_userAddress])

    @sp.entry_point
    def upgradeMainContractAddress(self, params):
        self.onlyOwner()

        self.data.mainContract = params._mainContractAddress

    @sp.entry_point
    def registerRegistrar(self, params):
        self.registrarChecks(params._registrarName)
        self.onlyMainContract()

        regNameBytes = sp.pack(params._registrarName)
        self.data.Registrars[params._registrar] = sp.record(
            isRegisteredRegistrar=True,
            registrarName=params._registrarName,
            registarAddress=params._registrar
        )

        self.data.registrarNameToAddress[regNameBytes] = params._registrar
        self.data.isAddressTaken[params._registrar] = True
        self.data.totalRegistrars += 1

    @sp.entry_point
    def updateRegistrar(self, params):
        self.registrarChecks(params._newRegistrarName)
        self.onlyMainContract()

        newNameBytes = sp.pack(params._newRegistrarName)

        sp.if ~self.data.totalRegistrarUpdates.contains(params._registrar):
            self.data.totalRegistrarUpdates[params._registrar] = 0
        sp.if ~self.data.resolveOldRegistrarAddress.contains(params._registrar):
            self.data.resolveOldRegistrarAddress[params._registrar] = sp.list([])

        sp.verify(self.data.isAddressTaken.get(params._registrar, False) == True, "Registrar should register first.")
        sp.verify(self.data.totalRegistrarUpdates[params._registrar]+1 <= 5, "Maximum update count reached.") #MAX_NAME_UPDATES

        registrarObject = self.data.Registrars[params._registrar]
        oldName = registrarObject.registrarName
        oldNameBytes = sp.pack(oldName)
        del self.data.registrarNameToAddress[oldNameBytes]

        self.data.resolveOldRegistrarAddress[params._registrar].push(
            sp.pack(self.data.Registrars[params._registrar].registrarName)
        )

        self.data.Registrars[params._registrar].registrarName = params._newRegistrarName
        self.data.Registrars[params._registrar].registarAddress = params._registrar

        self.data.registrarNameToAddress[newNameBytes] = params._registrar
        self.data.totalRegistrarUpdates[params._registrar] += 1

    @sp.onchain_view()
    def resolveRegistrarName(self, params):
        regNameBytes = sp.pack(params._name)
        sp.verify(self.data.registrarNameToAddress.contains(regNameBytes), "Resolver : Registrar is not yet registered for this SafleID.")
        sp.result(self.data.registrarNameToAddress[regNameBytes])

    @sp.entry_point
    def registerSafleId(self, params):
        self.safleIdChecks(params._safleId, params._registrar)
        self.onlyMainContract()

        sp.verify(self.data.isAddressTaken.get(params._userAddress, False) == False, "SafleID already registered")

        idBytes = sp.pack(params._safleId)

        self.data.resolveAddressFromSafleId[idBytes] = params._userAddress
        self.data.isAddressTaken[params._userAddress] = True
        self.data.resolveUserAddress[params._userAddress] = params._safleId
        self.data.totalSafleIdRegistered += 1

    @sp.entry_point
    def updateSafleId(self, params):
        self.safleIdChecks(params._safleId, params._registrar)
        self.onlyMainContract()

        sp.if ~self.data.totalSafleIDCount.contains(params._userAddress):
            self.data.totalSafleIDCount[params._userAddress] = 0

        sp.verify(self.data.totalSafleIDCount[params._userAddress]+1 <= 5, "Maximum update count reached.") #MAX_NAME_UPDATES
        sp.verify(self.data.isAddressTaken.get(params._userAddress, False) == True, "SafleID not registered.")
        sp.verify(self.data.auctionProcess.get(params._userAddress, False) == False, "SafleId cannot be updated inbetween Auction.")

        idBytes = sp.pack(params._safleId)

        oldName = self.data.resolveUserAddress[params._userAddress]
        oldIdBytes = sp.pack(oldName)

        self.data.unavailableSafleIds[oldName] = True
        del self.data.resolveAddressFromSafleId[oldIdBytes]
        self.oldSafleIds(params._userAddress, oldIdBytes)

        self.data.resolveAddressFromSafleId[idBytes] = params._userAddress
        self.data.resolveUserAddress[params._userAddress] = params._safleId

        self.data.totalSafleIDCount[params._userAddress] += 1
        self.data.totalSafleIdRegistered += 1

    @sp.onchain_view()
    def resolveSafleId(self, params):
        idBytes = sp.pack(params._safleId)
        sp.verify(sp.len(sp.pack(params._safleId)) != 0, "Resolver : user SafleID should not be empty.")
        sp.verify(self.data.resolveAddressFromSafleId.contains(idBytes), "Resolver : User is not yet registered for this SafleID.")
        sp.result(self.data.resolveAddressFromSafleId[idBytes])

    @sp.entry_point
    def transferSafleId(self, params):
        self.auctionContract()

        idBytes = sp.pack(params._safleId)

        sp.verify(self.data.isAddressTaken.get(params._oldOwner, False) == True, "You are not an owner of this safleId.")
        sp.verify(self.data.resolveAddressFromSafleId.contains(idBytes), "This SafleId does not have an owner.")

        self.oldSafleIds(params._oldOwner, idBytes)
        self.data.isAddressTaken[params._oldOwner] = False

        self.data.resolveAddressFromSafleId[idBytes] = params._newOwner

        self.data.auctionProcess[params._oldOwner] = False
        self.data.isAddressTaken[params._newOwner] = True
        self.data.resolveUserAddress[params._newOwner] = params._safleId

    def oldSafleIds(self, _userAddress, _safleId):
        sp.if ~self.data.resolveOldSafleIdFromAddress.contains(_userAddress):
            self.data.resolveOldSafleIdFromAddress[_userAddress] = sp.list([])

        self.data.resolveOldSafleIdFromAddress[_userAddress].push(_safleId)
        self.data.resolveOldSafleID[_safleId] = _userAddress

    @sp.entry_point
    def setAuctionContract(self, params):
        self.onlyOwner()

        self.data.auctionContractAddress = params._auctionAddress

    @sp.entry_point
    def auctionInProcess(self, params):
        sp.set_type(params._safleId, sp.TString)
        self.auctionContract()

        idBytes = sp.pack(params._safleId)

        sp.verify(sp.len(params._safleId) != 0, "Resolver : User SafleID should not be empty.")
        sp.verify(self.data.resolveAddressFromSafleId.contains(idBytes), "Resolver : User is not yet registered for this SafleID.")
        self.data.auctionProcess[params._safleIdOwner] = True

    @sp.entry_point
    def mapCoin(self, params):
        self.onlyMainContract()

        sp.verify(self.data.OtherCoin.get(params._indexnumber, sp.record(
                isIndexMapped = False,
                aliasName = "",
                coinName = ""
            )).isIndexMapped == False, "This index number has already been mapped.")
        sp.verify(self.data.isCoinMapped.get(params._coinName, False) == False, "This coin is already mapped.")
        sp.verify(self.data.Registrars[params._registrar].isRegisteredRegistrar, "Invalid Registrar.")

        self.data.OtherCoin[params._indexnumber] = sp.record(
            isIndexMapped = True,
            aliasName = params._aliasName,
            coinName = params._coinName
        )

        self.data.isCoinMapped[params._coinName] = True

    @sp.entry_point
    def registerCoinAddress(self, params):
        sp.set_type(params._index, sp.TNat)

        self.coinAddressCheck(params._userAddress, params._index, params._registrar)
        self.onlyMainContract()

        sp.verify(self.data.Registrars[params._registrar].isRegisteredRegistrar, "Invalid Registrar.")
        sp.verify(self.data.auctionProcess.get(params._userAddress, False) == False)
        sp.verify(self.data.OtherCoin[params._index].isIndexMapped == True)

        safleId = self.data.resolveUserAddress[params._userAddress]
        sp.if ~self.data.safleIdToCoinAddress.contains(safleId):
            self.data.safleIdToCoinAddress[safleId] = sp.map({})
        self.data.safleIdToCoinAddress[safleId][params._index] = params._address
        self.data.coinAddressToSafleId[params._address] = safleId

    @sp.entry_point
    def updateCoinAddress(self, params):
        sp.set_type(params._index, sp.TNat)

        self.coinAddressCheck(params._userAddress, params._index, params._registrar)
        self.onlyMainContract()

        sp.verify(self.data.Registrars[params._registrar].isRegisteredRegistrar, "Invalid Registrar")
        sp.verify(self.data.auctionProcess.get(params._userAddress, False) == False)
        sp.verify(self.data.OtherCoin[params._index].isIndexMapped == True, "This index number is not mapped.")

        safleId = self.data.resolveUserAddress[params._userAddress]
        sp.verify(self.data.safleIdToCoinAddress[safleId].contains(params._index))

        self.data.safleIdToCoinAddress[safleId][params._index] = params._newAddress
        self.data.coinAddressToSafleId[params._newAddress] = safleId

    @sp.onchain_view()
    def coinAddressToId(self, params):
        sp.result(self.data.coinAddressToSafleId[params._address])

    @sp.onchain_view()
    def idToCoinAddress(self, params):
        sp.result(self.data.safleIdToCoinAddress[params._safleId][params._index])

#MAX_NAME_UPDATES #MAX_NAME_UPDATES