package ocelot.desktop.util import ocelot.desktop.entity.traits.OcelotInterface import ocelot.desktop.ui.event.{BrainEvent, EventAware} import ocelot.desktop.ui.widget.LogWidget.{LogEntry, TextLogEntry} import ocelot.desktop.ui.widget.window.Windowed import ocelot.desktop.util.OcelotInterfaceLogStorage._ import ocelot.desktop.windows.OcelotInterfaceWindow import totoro.ocelot.brain.nbt.ExtendedNBT.extendNBTTagList import totoro.ocelot.brain.nbt.{NBT, NBTBase, NBTTagCompound, NBTTagString} import scala.collection.mutable import scala.jdk.CollectionConverters.BufferHasAsJava trait OcelotInterfaceLogStorage extends EventAware with Persistable with Windowed[OcelotInterfaceWindow] with Logging { def ocelotInterface: OcelotInterface def name: String override def createWindow(): OcelotInterfaceWindow = new OcelotInterfaceWindow(this) private var _messageLimit: Int = 1000 private val _entries = mutable.ArrayDeque.empty[LogEntry] eventHandlers += { case BrainEvent(OcelotInterface.LogEvent.Clear(_)) => clear() case BrainEvent(OcelotInterface.LogEvent.CardToUser(_, message)) => addEntry(LogEntry.Rx(message)) case BrainEvent(OcelotInterface.LogEvent.UserToCard(_, message)) => addEntry(LogEntry.Tx(message)) } private def loadEntry(nbt: NBTTagCompound): Either[String, LogEntry] = { nbt.getString(EntryKindTag) match { case EntryKindRx => Right(LogEntry.Rx(nbt.getString(EntryMessageTag))) case EntryKindTx => Right(LogEntry.Tx(nbt.getString(EntryMessageTag))) case "" => Left("entry kind not set") case k => Left(s"unknown entry kind: $k") } } private def saveEntry(entry: LogEntry): NBTTagCompound = { val result = new NBTTagCompound() entry match { case LogEntry.Rx(message) => result.setString(EntryKindTag, EntryKindRx) result.setString(EntryMessageTag, message) case LogEntry.Tx(message) => result.setString(EntryKindTag, EntryKindTx) result.setString(EntryMessageTag, message) } result } override def load(nbt: NBTTagCompound): Unit = { super.load(nbt) clear() if (nbt.hasKey(MessageLimitTag)) { messageLimit = nbt.getInteger(MessageLimitTag) } val entries = if (nbt.hasKey(EntriesTag)) { nbt.getTagList(EntriesTag, NBT.TAG_COMPOUND).iterator[NBTTagCompound].zipWithIndex.flatMap { case (entryNbt, idx) => loadEntry(entryNbt) match { case Left(err) => logger.warn( s"Could not restore log entry (idx $idx) of ocelot interface ${ocelotInterface.node.address}:" + s" $err" ) None case Right(entry) => Some(entry) } } } else { // old save format: plain messages nbt .getTagList(MessagesTag, NBT.TAG_STRING) .iterator[NBTTagString] .map(entryNbt => LogEntry.Rx(entryNbt.getString)) } addEntries(entries.toSeq) } override def save(nbt: NBTTagCompound): Unit = { super.save(nbt) nbt.setTagList(EntriesTag, _entries.map(saveEntry(_).asInstanceOf[NBTBase]).asJava) nbt.setInteger(MessageLimitTag, _messageLimit) } def messageLimit: Int = _messageLimit def messageLimit_=(limit: Int): Unit = { require(limit > 0) ensureFreeSpace(_entries.length - limit) _messageLimit = limit } def entryCount: Int = { _entries.length } def clear(): Unit = { val count = _entries.length _entries.clear() window.onMessagesRemoved(count) } def getEntry(index: Int): LogEntry = _entries(index) def findFirstTextEntry(exclude: String, start: Int): Int = { _entries.indexWhere(it => it.isInstanceOf[TextLogEntry] && it.asInstanceOf[TextLogEntry].message != exclude, start) } def findLastTextEntry(exclude: String, end: Int): Int = { _entries.lastIndexWhere(it => it.isInstanceOf[TextLogEntry] && it.asInstanceOf[TextLogEntry].message != exclude, end) } private def addEntry(entry: LogEntry): Unit = { ensureFreeSpace(1) _entries += entry onMessagesAdded(Some(entry)) } private def addEntries(entries: Seq[LogEntry]): Unit = { ensureFreeSpace(entries.length) val prevCount = _entries.length _entries ++= entries.view.takeRight(messageLimit) onMessagesAdded(_entries.view.takeRight(_entries.length - prevCount)) } private def ensureFreeSpace(n: Int): Unit = { val prevCount = _entries.length _entries.takeRightInPlace(messageLimit - n) val removedCount = prevCount - _entries.length if (removedCount > 0) { window.onMessagesRemoved(removedCount) } } protected def onMessagesAdded(entries: => Iterable[LogEntry]): Unit = { window.onMessagesAdded(entries) } } object OcelotInterfaceLogStorage { private val MessagesTag = "messages" private val EntriesTag = "entries" private val MessageLimitTag = "limit" private val EntryKindTag = "kind" private val EntryKindRx = "rx" private val EntryKindTx = "tx" private val EntryMessageTag = "msg" }