package saltmanager

import (
    "github.com/dgraph-io/badger"
    "github.com/iotaledger/goshimmer/packages/daemon"
    "github.com/iotaledger/goshimmer/packages/node"
    "github.com/iotaledger/goshimmer/packages/settings"
    "github.com/iotaledger/goshimmer/plugins/autopeering/types/salt"
    "time"
)

var (
    PRIVATE_SALT *salt.Salt
    PUBLIC_SALT  *salt.Salt
)

func Configure(plugin *node.Plugin) {
    PRIVATE_SALT = createSalt(PRIVATE_SALT_SETTINGS_KEY, PRIVATE_SALT_LIFETIME, Events.UpdatePrivateSalt.Trigger)
    PUBLIC_SALT = createSalt(PUBLIC_SALT_SETTINGS_KEY, PUBLIC_SALT_LIFETIME, Events.UpdatePublicSalt.Trigger)
}

func generateNewSalt(key []byte, lifetime time.Duration) *salt.Salt {
    newSalt := salt.New(lifetime)

    if err := settings.Set(key, newSalt.Marshal()); err != nil {
        panic(err)
    }

    return newSalt
}

func getSalt(key []byte, lifetime time.Duration) *salt.Salt {
    saltBytes, err := settings.Get(key)
    if err != nil {
        if err == badger.ErrKeyNotFound {
            return generateNewSalt(key, lifetime)
        } else {
            panic(err)
        }
    }

    if resultingSalt, err := salt.Unmarshal(saltBytes); err != nil {
        panic(err)
    } else {
        return resultingSalt
    }
}

func updatePublicSalt(saltToUpdate *salt.Salt, settingsKey []byte, lifeSpan time.Duration, updateCallback func(params ...interface{})) {
    newSalt := salt.New(lifeSpan)

    saltToUpdate.Bytes = newSalt.Bytes
    saltToUpdate.ExpirationTime = newSalt.ExpirationTime

    if err := settings.Set(settingsKey, saltToUpdate.Marshal()); err != nil {
        panic(err)
    }

    updateCallback(saltToUpdate)

    scheduleUpdateForSalt(saltToUpdate, settingsKey, lifeSpan, updateCallback)
}

func scheduleUpdateForSalt(saltToUpdate *salt.Salt, settingsKey []byte, lifeSpan time.Duration, callback func(params ...interface{})) {
    now := time.Now()

    if saltToUpdate.ExpirationTime.Before(now) {
        updatePublicSalt(saltToUpdate, settingsKey, lifeSpan, callback)
    } else {
        daemon.BackgroundWorker(func() {
            select {
            case <-time.After(saltToUpdate.ExpirationTime.Sub(now)):
                updatePublicSalt(saltToUpdate, settingsKey, lifeSpan, callback)
            case <-daemon.ShutdownSignal:
                return
            }
        })
    }
}

func createSalt(settingsKey []byte, lifeSpan time.Duration, updateCallback func(params ...interface{})) *salt.Salt {
    newSalt := getSalt(settingsKey, lifeSpan)

    scheduleUpdateForSalt(newSalt, settingsKey, lifeSpan, updateCallback)

    return newSalt
}