Code Sketch


bank of yoi
By: Mhalsakant School
Category: Programming
import java.awt.BorderLayout
import java.awt.Color
import java.awt.Component
import java.awt.Dimension
import java.awt.FlowLayout
import java.awt.Font
import java.awt.GradientPaint
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
import java.awt.GridLayout
import java.awt.Insets
import java.awt.RenderingHints
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.EOFException
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.Date
import java.util.UUID
import javax.swing.BorderFactory
import javax.swing.Box
import javax.swing.BoxLayout
import javax.swing.JButton
import javax.swing.JFileChooser
import javax.swing.JFrame
import javax.swing.JLabel
import javax.swing.JOptionPane
import javax.swing.JPanel
import javax.swing.JPasswordField
import javax.swing.JScrollPane
import javax.swing.JTabbedPane
import javax.swing.JTextArea
import javax.swing.JTextField
import javax.swing.SwingConstants
import javax.swing.SwingUtilities
import javax.swing.UIManager
import javax.swing.WindowConstants
import scala.collection.mutable.ArrayBuffer
import scala.math.BigDecimal

final class BankAccount(
val id: String,
val name: String,
var passwordHash: String,
var balance: BigDecimal,
val unlimited: Boolean,
val founder: Boolean,
val createdAt: Long
) {
def shownBalance: String = {
if (unlimited) {
"UNLIMITED RUPEES"
} else {
BankUtil.money(balance)
}
}
}

final class BankMessage(
val id: String,
val toId: String,
val fromId: String,
val kind: String,
val amount: BigDecimal,
val text: String,
val createdAt: Long,
var read: Boolean,
var handled: Boolean
)

object BankUtil {

val personalLimit: BigDecimal =
BigDecimal("100000")

val personalSafeMaximum: BigDecimal =
BigDecimal("99999.99")

val yadneshPasswordHash =
"7f3f667469f4945d7b63b6831e26f1bd84bd02d57b455542b6144fe2b2a6a92d"

val bhaveshPasswordHash =
"064adaded7ad21776cf2bef2a3b06c024a3191ca89e0074b4411898d5385eaa6"

val bhimrajPasswordHash =
"2730bbb2ffa53a5acfa95b7b4670589a35aaf79ef43cc072448564310a585316"

val alokPasswordHash =
"d9eacbdce8fbd63f1f22f5fa7487362234f8d6a03e434bdffa7fcbd1280d83fa"

def hashPassword(password: String): String = {
val md =
MessageDigest.getInstance("SHA-256")

val bytes =
  md.digest(
    password.getBytes(
      StandardCharsets.UTF_8
    )
  )

val result =
  new StringBuilder

var i = 0

while (i < bytes.length) {
  val n =
    bytes(i) & 255

  val hex =
    Integer.toHexString(n)

  if (hex.length == 1) {
    result.append("0")
  }

  result.append(hex)
  i += 1
}

result.toString

}

def verifyPassword(
entered: String,
storedHash: String
): Boolean = {
hashPassword(entered) == storedHash
}

def money(value: BigDecimal): String = {
"?" +
value.bigDecimal
.setScale(
2,
java.math.RoundingMode.HALF_UP
)
.stripTrailingZeros()
.toPlainString
}

def parseAmount(
text: String
): Option[BigDecimal] = {

val cleaned =
  text.trim.replace(",", "")

if (
  cleaned.isEmpty ||
  cleaned.length > 120
) {
  None
} else {
  try {

    val value =
      BigDecimal(cleaned)

    if (value > BigDecimal(0)) {
      Some(value)
    } else {
      None
    }

  } catch {

    case _: Throwable =>
      None
  }
}

}

def parsePersonalBalance(
text: String
): Option[BigDecimal] = {

val cleaned =
  text.trim.replace(",", "")

if (cleaned.isEmpty) {
  Some(BigDecimal(0))
} else {
  try {

    val value =
      BigDecimal(cleaned)

    if (
      value >= BigDecimal(0) &&
      value < personalLimit
    ) {
      Some(value)
    } else {
      None
    }

  } catch {

    case _: Throwable =>
      None
  }
}

}

def cleanName(
text: String
): String = {

val source =
  text.trim

val result =
  new StringBuilder

var i = 0
var lastWasSpace = false

while (i < source.length) {

  val ch =
    source.charAt(i)

  val whitespace =
    ch == ' ' ||
    ch == '\t' ||
    ch == '\n' ||
    ch == '\r'

  if (whitespace) {

    if (
      result.nonEmpty &&
      !lastWasSpace
    ) {
      result.append(' ')
    }

    lastWasSpace = true

  } else {

    result.append(ch)
    lastWasSpace = false
  }

  i += 1
}

result.toString.trim

}

def reservedName(
name: String
): Boolean = {

val n =
  cleanName(name).toLowerCase

n == "yadnesh shahare" ||
n == "bhavesh shahare" ||
n == "bhimraj" ||
n == "alok"

}

def isYadnesh(
account: BankAccount
): Boolean = {
account.name.equalsIgnoreCase(
"Yadnesh Shahare"
)
}

def isBhavesh(
account: BankAccount
): Boolean = {
account.name.equalsIgnoreCase(
"Bhavesh Shahare"
)
}

def timeText(
time: Long
): String = {
new SimpleDateFormat(
"dd MMM yyyy, HH:mm:ss"
).format(
new Date(time)
)
}

def shortId(
id: String
): String = {

if (id.length <= 10) {
  id.toUpperCase
} else {
  id.substring(0, 10).toUpperCase
}

}
}

final class BankStore {

val dataFile =
new File(
System.getProperty("user.home") +
File.separator +
"YadneshNationalBank" +
File.separator +
"bank-data.ydb"
)

val accounts =
ArrayBuffer.empty[BankAccount]

val messages =
ArrayBuffer.empty[BankMessage]

var status =
"Local bank database ready."

initialize()

private def ensureFolder(): Unit = {

val folder =
  dataFile.getParentFile

if (
  folder != null &&
  !folder.exists()
) {
  folder.mkdirs()
}

}

private def createFounders(): Unit = {

accounts.clear()

accounts += new BankAccount(
  "YAD-" +
    UUID.randomUUID().toString,
  "Yadnesh Shahare",
  BankUtil.yadneshPasswordHash,
  BigDecimal(0),
  true,
  true,
  System.currentTimeMillis()
)

accounts += new BankAccount(
  "BHA-" +
    UUID.randomUUID().toString,
  "Bhavesh Shahare",
  BankUtil.bhaveshPasswordHash,
  BigDecimal(0),
  true,
  true,
  System.currentTimeMillis()
)

accounts += new BankAccount(
  "BHI-" +
    UUID.randomUUID().toString,
  "Bhimraj",
  BankUtil.bhimrajPasswordHash,
  BigDecimal("75000"),
  false,
  true,
  System.currentTimeMillis()
)

accounts += new BankAccount(
  "ALO-" +
    UUID.randomUUID().toString,
  "Alok",
  BankUtil.alokPasswordHash,
  BigDecimal("50000"),
  false,
  true,
  System.currentTimeMillis()
)

}

private def enforceBalanceRules(): Boolean = {

var changed =
  false

accounts.foreach { account =>

  if (
    BankUtil.isYadnesh(account) ||
    BankUtil.isBhavesh(account)
  ) {

    if (!account.unlimited) {
      account.balance =
        BigDecimal(0)

      changed =
        true
    }

  } else {

    if (account.balance >= BankUtil.personalLimit) {

      account.balance =
        BankUtil.personalSafeMaximum

      changed =
        true
    }

    if (account.unlimited) {

      account.balance =
        BankUtil.personalSafeMaximum

      changed =
        true
    }
  }
}

changed

}

private def initialize(): Unit = {

if (!dataFile.exists()) {

  createFounders()
  messages.clear()
  save()

} else {

  val loaded =
    load(dataFile)

  if (!loaded) {

    createFounders()
    messages.clear()
    save()

  } else {

    val changed =
      enforceBalanceRules()

    if (changed) {
      save()
    }
  }
}

}

def findByName(
name: String
): Option[BankAccount] = {

val wanted =
  BankUtil.cleanName(name)

accounts.find(
  account =>
    account.name.equalsIgnoreCase(wanted)
)

}

def findById(
id: String
): Option[BankAccount] = {

accounts.find(
  account =>
    account.id == id
)

}

def findYadnesh():
Option[BankAccount] = {

accounts.find(
  account =>
    BankUtil.isYadnesh(account)
)

}

def addMessage(
message: BankMessage
): Unit = {

messages += message

}

def unreadCount(
accountId: String
): Int = {

messages.count(
  message =>
    message.toId == accountId &&
    !message.read
)

}

def messagesFor(
accountId: String
): Seq[BankMessage] = {

messages
  .filter(
    message =>
      message.toId == accountId ||
      message.fromId == accountId
  )
  .sortBy(
    message =>
      -message.createdAt
  )
  .toSeq

}

def save(): Boolean = {

var output:
  DataOutputStream = null

try {

  ensureFolder()

  output =
    new DataOutputStream(
      new FileOutputStream(
        dataFile
      )
    )

  output.writeUTF(
    "YADNESHBANK-3"
  )

  output.writeInt(
    accounts.size
  )

  accounts.foreach { account =>

    output.writeUTF(
      account.id
    )

    output.writeUTF(
      account.name
    )

    output.writeUTF(
      account.passwordHash
    )

    output.writeUTF(
      account.balance.toString
    )

    output.writeBoolean(
      account.unlimited
    )

    output.writeBoolean(
      account.founder
    )

    output.writeLong(
      account.createdAt
    )
  }

  output.writeInt(
    messages.size
  )

  messages.foreach { message =>

    output.writeUTF(
      message.id
    )

    output.writeUTF(
      message.toId
    )

    output.writeUTF(
      message.fromId
    )

    output.writeUTF(
      message.kind
    )

    output.writeUTF(
      message.amount.toString
    )

    output.writeUTF(
      message.text
    )

    output.writeLong(
      message.createdAt
    )

    output.writeBoolean(
      message.read
    )

    output.writeBoolean(
      message.handled
    )
  }

  output.flush()

  status =
    "Saved locally."

  true

} catch {

  case ex: Throwable =>

    status =
      "Save warning: " +
        ex.getMessage

    false

} finally {

  if (output != null) {

    try {
      output.close()
    } catch {
      case _: Throwable =>
    }
  }
}

}

def exportTo(
file: File
): Boolean = {

var output:
  DataOutputStream = null

try {

  output =
    new DataOutputStream(
      new FileOutputStream(file)
    )

  output.writeUTF(
    "YADNESHBANK-3"
  )

  output.writeInt(
    accounts.size
  )

  accounts.foreach { account =>

    output.writeUTF(
      account.id
    )

    output.writeUTF(
      account.name
    )

    output.writeUTF(
      account.passwordHash
    )

    output.writeUTF(
      account.balance.toString
    )

    output.writeBoolean(
      account.unlimited
    )

    output.writeBoolean(
      account.founder
    )

    output.writeLong(
      account.createdAt
    )
  }

  output.writeInt(
    messages.size
  )

  messages.foreach { message =>

    output.writeUTF(
      message.id
    )

    output.writeUTF(
      message.toId
    )

    output.writeUTF(
      message.fromId
    )

    output.writeUTF(
      message.kind
    )

    output.writeUTF(
      message.amount.toString
    )

    output.writeUTF(
      message.text
    )

    output.writeLong(
      message.createdAt
    )

    output.writeBoolean(
      message.read
    )

    output.writeBoolean(
      message.handled
    )
  }

  output.flush()

  status =
    "Export complete."

  true

} catch {

  case ex: Throwable =>

    status =
      "Export failed: " +
        ex.getMessage

    false

} finally {

  if (output != null) {

    try {
      output.close()
    } catch {
      case _: Throwable =>
    }
  }
}

}

def importFrom(
file: File
): Boolean = {

var input:
  DataInputStream = null

try {

  input =
    new DataInputStream(
      new FileInputStream(file)
    )

  val magic =
    input.readUTF()

  if (
    magic != "YADNESHBANK-3" &&
    magic != "YADNESHBANK-2" &&
    magic != "YADNESHBANK-1"
  ) {

    throw new IOException(
      "Invalid Yadnesh Bank data file."
    )
  }

  val newAccounts =
    ArrayBuffer.empty[BankAccount]

  val newMessages =
    ArrayBuffer.empty[BankMessage]

  val accountCount =
    input.readInt()

  var i =
    0

  while (
    i < accountCount
  ) {

    newAccounts +=
      new BankAccount(
        input.readUTF(),
        input.readUTF(),
        input.readUTF(),
        BigDecimal(
          input.readUTF()
        ),
        input.readBoolean(),
        input.readBoolean(),
        input.readLong()
      )

    i += 1
  }

  val messageCount =
    input.readInt()

  var j =
    0

  while (
    j < messageCount
  ) {

    newMessages +=
      new BankMessage(
        input.readUTF(),
        input.readUTF(),
        input.readUTF(),
        input.readUTF(),
        BigDecimal(
          input.readUTF()
        ),
        input.readUTF(),
        input.readLong(),
        input.readBoolean(),
        input.readBoolean()
      )

    j += 1
  }

  if (newAccounts.isEmpty) {

    throw new IOException(
      "No accounts were found in the selected file."
    )
  }

  accounts.clear()
  accounts ++= newAccounts

  messages.clear()
  messages ++= newMessages

  enforceBalanceRules()
  save()

  status =
    "Import complete."

  true

} catch {

  case _: EOFException =>

    status =
      "Import failed: incomplete file."

    false

  case ex: Throwable =>

    status =
      "Import failed: " +
        ex.getMessage

    false

} finally {

  if (input != null) {

    try {
      input.close()
    } catch {
      case _: Throwable =>
    }
  }
}

}

private def load(
file: File
): Boolean = {

var input:
  DataInputStream = null

try {

  input =
    new DataInputStream(
      new FileInputStream(file)
    )

  val magic =
    input.readUTF()

  if (
    magic != "YADNESHBANK-3" &&
    magic != "YADNESHBANK-2" &&
    magic != "YADNESHBANK-1"
  ) {
    return false
  }

  accounts.clear()
  messages.clear()

  val accountCount =
    input.readInt()

  var i =
    0

  while (
    i < accountCount
  ) {

    accounts +=
      new BankAccount(
        input.readUTF(),
        input.readUTF(),
        input.readUTF(),
        BigDecimal(
          input.readUTF()
        ),
        input.readBoolean(),
        input.readBoolean(),
        input.readLong()
      )

    i += 1
  }

  val messageCount =
    input.readInt()

  var j =
    0

  while (
    j < messageCount
  ) {

    messages +=
      new BankMessage(
        input.readUTF(),
        input.readUTF(),
        input.readUTF(),
        input.readUTF(),
        BigDecimal(
          input.readUTF()
        ),
        input.readUTF(),
        input.readLong(),
        input.readBoolean(),
        input.readBoolean()
      )

    j += 1
  }

  true

} catch {

  case _: Throwable =>
    false

} finally {

  if (input != null) {

    try {
      input.close()
    } catch {
      case _: Throwable =>
    }
  }
}

}
}

final class BankBackground
extends JPanel {

setOpaque(
true
)

override def paintComponent(
g: java.awt.Graphics
): Unit = {

super.paintComponent(g)

val graphics =
  g.create().asInstanceOf[
    java.awt.Graphics2D
  ]

try {

  graphics.setRenderingHint(
    RenderingHints.KEY_ANTIALIASING,
    RenderingHints.VALUE_ANTIALIAS_ON
  )

  val width =
    getWidth

  val height =
    getHeight

  graphics.setPaint(
    new GradientPaint(
      0f,
      0f,
      new Color(
        9,
        18,
        33
      ),
      0f,
      height.toFloat,
      new Color(
        35,
        54,
        83
      )
    )
  )

  graphics.fillRect(
    0,
    0,
    width,
    height
  )

  graphics.setColor(
    new Color(
      255,
      255,
      255,
      14
    )
  )

  graphics.fillOval(
    width - 320,
    -120,
    450,
    450
  )

  graphics.fillOval(
    -150,
    height - 230,
    370,
    370
  )

  graphics.setColor(
    new Color(
      235,
      182,
      43,
      16
    )
  )

  graphics.fillOval(
    width - 200,
    height - 200,
    370,
    370
  )

} finally {

  graphics.dispose()
}

}
}

final class BankHeader(
titleText: String,
subtitleText: String
) extends JPanel {

setOpaque(
false
)

setLayout(
new BorderLayout(
14,
0
)
)

setBorder(
BorderFactory.createEmptyBorder(
14,
18,
14,
18
)
)

val logo =
new JPanel() {

  setOpaque(
    false
  )

  override def paintComponent(
    g: java.awt.Graphics
  ): Unit = {

    val graphics =
      g.create().asInstanceOf[
        java.awt.Graphics2D
      ]

    try {

      graphics.setRenderingHint(
        RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON
      )

      graphics.setColor(
        new Color(
          236,
          184,
          55
        )
      )

      graphics.fillOval(
        0,
        0,
        52,
        52
      )

      graphics.setColor(
        new Color(
          20,
          31,
          48
        )
      )

      graphics.setFont(
        new Font(
          "SansSerif",
          Font.BOLD,
          18
        )
      )

      val text =
        "YN"

      val metrics =
        graphics.getFontMetrics

      val x =
        (52 - metrics.stringWidth(
          text
        )) / 2

      val y =
        (52 - metrics.getHeight) / 2 +
          metrics.getAscent

      graphics.drawString(
        text,
        x,
        y
      )

    } finally {

      graphics.dispose()
    }
  }
}

logo.setPreferredSize(
new Dimension(
52,
52
)
)

val textPanel =
new JPanel()

textPanel.setOpaque(
false
)

textPanel.setLayout(
new BoxLayout(
textPanel,
BoxLayout.Y_AXIS
)
)

val title =
new JLabel(
titleText
)

title.setForeground(
Color.WHITE
)

title.setFont(
new Font(
"SansSerif",
Font.BOLD,
22
)
)

val subtitle =
new JLabel(
subtitleText
)

subtitle.setForeground(
new Color(
205,
214,
228
)
)

subtitle.setFont(
new Font(
"SansSerif",
Font.PLAIN,
12
)
)

textPanel.add(
title
)

textPanel.add(
Box.createVerticalStrut(
4
)
)

textPanel.add(
subtitle
)

add(
logo,
BorderLayout.WEST
)

add(
textPanel,
BorderLayout.CENTER
)
}

object BankApp {

val store =
new BankStore()

var frame:
JFrame =
null

var currentAccount:
Option[BankAccount] =
None

val pageBackground =
new Color(
246,
248,
251
)

val cardColor =
Color.WHITE

val darkText =
new Color(
33,
43,
58
)

val mutedText =
new Color(
103,
115,
132
)

val gold =
new Color(
207,
162,
45
)

val darkButton =
new Color(
30,
43,
61
)

val green =
new Color(
32,
125,
73
)

val red =
new Color(
171,
58,
58
)

def main(
args: Array[String]
): Unit = {
start()
}

def start(): Unit = {

SwingUtilities.invokeLater(
  new Runnable {

    override def run(): Unit = {

      try {

        UIManager.setLookAndFeel(
          UIManager.getSystemLookAndFeelClassName
        )

      } catch {
        case _: Throwable =>
      }

      frame =
        new JFrame(
          "Yadnesh National Bank"
        )

      frame.setDefaultCloseOperation(
        WindowConstants.EXIT_ON_CLOSE
      )

      frame.setMinimumSize(
        new Dimension(
          1000,
          650
        )
      )

      frame.setSize(
        1180,
        760
      )

      frame.setLocationRelativeTo(
        null
      )

      showLogin(
        ""
      )

      frame.setVisible(
        true
      )
    }
  }
)

}

def label(
text: String,
size: Int,
bold: Boolean
): JLabel = {

val result =
  new JLabel(
    text
  )

result.setForeground(
  darkText
)

result.setFont(
  new Font(
    "SansSerif",
    if (bold) {
      Font.BOLD
    } else {
      Font.PLAIN
    },
    size
  )
)

result

}

def muted(
text: String
): JLabel = {

val result =
  label(
    text,
    12,
    false
  )

result.setForeground(
  mutedText
)

result

}

def styleField(
field: JTextField
): Unit = {

field.setFont(
  new Font(
    "SansSerif",
    Font.PLAIN,
    14
  )
)

field.setForeground(
  darkText
)

field.setBackground(
  Color.WHITE
)

field.setBorder(
  BorderFactory.createCompoundBorder(
    BorderFactory.createLineBorder(
      new Color(
        201,
        209,
        220
      )
    ),
    BorderFactory.createEmptyBorder(
      8,
      10,
      8,
      10
    )
  )
)

}

def styleButton(
button: JButton,
primary: Boolean
): Unit = {

button.setFont(
  new Font(
    "SansSerif",
    Font.BOLD,
    13
  )
)

button.setFocusPainted(
  false
)

button.setOpaque(
  true
)

if (primary) {

  button.setBackground(
    darkButton
  )

  button.setForeground(
    Color.WHITE
  )

} else {

  button.setBackground(
    new Color(
      239,
      242,
      247
    )
  )

  button.setForeground(
    darkText
  )
}

button.setBorder(
  BorderFactory.createCompoundBorder(
    BorderFactory.createLineBorder(
      new Color(
        204,
        211,
        220
      )
    ),
    BorderFactory.createEmptyBorder(
      8,
      14,
      8,
      14
    )
  )
)

}

def card(): JPanel = {

val panel =
  new JPanel(
    new BorderLayout(
      10,
      10
    )
  )

panel.setBackground(
  cardColor
)

panel.setBorder(
  BorderFactory.createCompoundBorder(
    BorderFactory.createLineBorder(
      new Color(
        226,
        230,
        236
      )
    ),
    BorderFactory.createEmptyBorder(
      18,
      18,
      18,
      18
    )
  )
)

panel

}

def showLogin(
message: String
): Unit = {

currentAccount =
  None

val root =
  new BankBackground

root.setLayout(
  new BorderLayout()
)

root.add(
  new BankHeader(
    "YADNESH NATIONAL BANK",
    "Secure local banking simulator ? no password is displayed anywhere"
  ),
  BorderLayout.NORTH
)

val center =
  new JPanel(
    new GridBagLayout()
  )

center.setOpaque(
  false
)

val loginCard =
  card()

loginCard.setPreferredSize(
  new Dimension(
    650,
    500
  )
)

val form =
  new JPanel(
    new GridBagLayout()
  )

form.setOpaque(
  false
)

val heading =
  label(
    "Secure Login",
    25,
    true
  )

val description =
  muted(
    "Enter account name and password."
  )

val nameField =
  new JTextField()

val passwordField =
  new JPasswordField()

styleField(
  nameField
)

styleField(
  passwordField
)

val loginButton =
  new JButton(
    "LOGIN"
  )

val newAccountButton =
  new JButton(
    "OPEN NEW ACCOUNT"
  )

styleButton(
  loginButton,
  true
)

styleButton(
  newAccountButton,
  false
)

val statusLabel =
  label(
    if (message.isEmpty) {
      " "
    } else {
      message
    },
    12,
    false
  )

if (
  message.toLowerCase.contains(
    "success"
  )
) {

  statusLabel.setForeground(
    green
  )

} else {

  statusLabel.setForeground(
    red
  )
}

val founderInfo =
  new JTextArea(
    "FOUNDER ACCOUNTS\n\n" +
    "Yadnesh Shahare  ?  UNLIMITED RUPEES\n" +
    "Bhavesh Shahare  ?  UNLIMITED RUPEES\n" +
    "Bhimraj          ?  BELOW ?1,00,000\n" +
    "Alok             ?  BELOW ?1,00,000\n\n" +
    "PASSWORDS: HIDDEN\n" +
    "No password is shown on this page or in the bank directory."
  )

founderInfo.setEditable(
  false
)

founderInfo.setLineWrap(
  true
)

founderInfo.setWrapStyleWord(
  true
)

founderInfo.setFont(
  new Font(
    "SansSerif",
    Font.PLAIN,
    12
  )
)

founderInfo.setForeground(
  darkText
)

founderInfo.setBackground(
  new Color(
    249,
    250,
    252
  )
)

founderInfo.setBorder(
  BorderFactory.createCompoundBorder(
    BorderFactory.createLineBorder(
      new Color(
        226,
        230,
        236
      )
    ),
    BorderFactory.createEmptyBorder(
      10,
      12,
      10,
      12
    )
  )
)

var row =
  0

def addRow(
  titleText: String,
  component: java.awt.Component
): Unit = {

  val left =
    new GridBagConstraints()

  left.gridx = 0
  left.gridy = row
  left.weightx = 0.0
  left.anchor =
    GridBagConstraints.WEST

  left.insets =
    new Insets(
      6,
      0,
      6,
      12
    )

  form.add(
    label(
      titleText,
      12,
      true
    ),
    left
  )

  val right =
    new GridBagConstraints()

  right.gridx = 1
  right.gridy = row
  right.weightx = 1.0
  right.fill =
    GridBagConstraints.HORIZONTAL

  right.insets =
    new Insets(
      6,
      0,
      6,
      0
    )

  form.add(
    component,
    right
  )

  row += 1
}

val headingGbc =
  new GridBagConstraints()

headingGbc.gridx = 0
headingGbc.gridy = row
headingGbc.gridwidth = 2
headingGbc.weightx = 1.0
headingGbc.fill =
  GridBagConstraints.HORIZONTAL

headingGbc.insets =
  new Insets(
    0,
    0,
    5,
    0
  )

form.add(
  heading,
  headingGbc
)

row += 1

val descriptionGbc =
  new GridBagConstraints()

descriptionGbc.gridx = 0
descriptionGbc.gridy = row
descriptionGbc.gridwidth = 2
descriptionGbc.weightx = 1.0
descriptionGbc.fill =
  GridBagConstraints.HORIZONTAL

descriptionGbc.insets =
  new Insets(
    0,
    0,
    12,
    0
  )

form.add(
  description,
  descriptionGbc
)

row += 1

addRow(
  "ACCOUNT NAME",
  nameField
)

addRow(
  "PASSWORD",
  passwordField
)

val buttons =
  new JPanel(
    new GridLayout(
      1,
      2,
      10,
      0
    )
  )

buttons.setOpaque(
  false
)

buttons.add(
  loginButton
)

buttons.add(
  newAccountButton
)

addRow(
  "ACTION",
  buttons
)

addRow(
  "STATUS",
  statusLabel
)

val infoGbc =
  new GridBagConstraints()

infoGbc.gridx = 0
infoGbc.gridy = row
infoGbc.gridwidth = 2
infoGbc.weightx = 1.0
infoGbc.weighty = 1.0
infoGbc.fill =
  GridBagConstraints.BOTH

form.add(
  founderInfo,
  infoGbc
)

loginCard.add(
  form,
  BorderLayout.CENTER
)

center.add(
  loginCard
)

root.add(
  center,
  BorderLayout.CENTER
)

val footer =
  label(
    "Password security: SHA-256 hash ? passwords never displayed",
    11,
    false
  )

footer.setForeground(
  new Color(
    205,
    214,
    228
  )
)

footer.setHorizontalAlignment(
  SwingConstants.CENTER
)

root.add(
  footer,
  BorderLayout.SOUTH
)

loginButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      val name =
        BankUtil.cleanName(
          nameField.getText
        )

      val password =
        new String(
          passwordField.getPassword
        )

      store.findByName(
        name
      ) match {

        case None =>

          statusLabel.setText(
            "Account not found."
          )

          statusLabel.setForeground(
            red
          )

        case Some(account) =>

          if (
            BankUtil.verifyPassword(
              password,
              account.passwordHash
            )
          ) {

            currentAccount =
              Some(account)

            showDashboard()

          } else {

            statusLabel.setText(
              "Incorrect password."
            )

            statusLabel.setForeground(
              red
            )
          }
      }
    }
  }
)

newAccountButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      showCreateAccount()
    }
  }
)

frame.setTitle(
  "Yadnesh National Bank"
)

frame.setContentPane(
  root
)

frame.setSize(
  1180,
  760
)

frame.setLocationRelativeTo(
  null
)

frame.setVisible(
  true
)

frame.getRootPane.setDefaultButton(
  loginButton
)

SwingUtilities.invokeLater(
  new Runnable {

    override def run(): Unit = {
      nameField.requestFocusInWindow()
    }
  }
)

}

def showCreateAccount(): Unit = {

val root =
  new BankBackground

root.setLayout(
  new BorderLayout()
)

root.add(
  new BankHeader(
    "OPEN NEW ACCOUNT",
    "Personal accounts always stay below ?1,00,000"
  ),
  BorderLayout.NORTH
)

val center =
  new JPanel(
    new GridBagLayout()
  )

center.setOpaque(
  false
)

val cardPanel =
  card()

cardPanel.setPreferredSize(
  new Dimension(
    800,
    580
  )
)

val form =
  new JPanel(
    new GridBagLayout()
  )

form.setOpaque(
  false
)

val nameField =
  new JTextField()

val passwordField =
  new JPasswordField()

val confirmField =
  new JPasswordField()

val balanceField =
  new JTextField(
    "0"
  )

styleField(
  nameField
)

styleField(
  passwordField
)

styleField(
  confirmField
)

styleField(
  balanceField
)

val createButton =
  new JButton(
    "CREATE ACCOUNT"
  )

val backButton =
  new JButton(
    "BACK TO LOGIN"
  )

styleButton(
  createButton,
  true
)

styleButton(
  backButton,
  false
)

val statusLabel =
  label(
    " ",
    12,
    false
  )

statusLabel.setForeground(
  red
)

var row =
  0

def addRow(
  titleText: String,
  component: java.awt.Component
): Unit = {

  val a =
    new GridBagConstraints()

  a.gridx = 0
  a.gridy = row
  a.weightx = 0.0
  a.anchor =
    GridBagConstraints.WEST

  a.insets =
    new Insets(
      6,
      0,
      6,
      12
    )

  form.add(
    label(
      titleText,
      12,
      true
    ),
    a
  )

  val b =
    new GridBagConstraints()

  b.gridx = 1
  b.gridy = row
  b.weightx = 1.0
  b.fill =
    GridBagConstraints.HORIZONTAL

  b.insets =
    new Insets(
      6,
      0,
      6,
      0
    )

  form.add(
    component,
    b
  )

  row += 1
}

val heading =
  new GridBagConstraints()

heading.gridx = 0
heading.gridy = row
heading.gridwidth = 2
heading.weightx = 1.0
heading.fill =
  GridBagConstraints.HORIZONTAL

form.add(
  label(
    "Create Personal Account",
    24,
    true
  ),
  heading
)

row += 1

val description =
  new GridBagConstraints()

description.gridx = 0
description.gridy = row
description.gridwidth = 2
description.weightx = 1.0
description.fill =
  GridBagConstraints.HORIZONTAL

description.insets =
  new Insets(
    0,
    0,
    12,
    0
  )

form.add(
  muted(
    "No password will ever be shown. Your balance must be below ?1,00,000."
  ),
  description
)

row += 1

addRow(
  "FULL NAME",
  nameField
)

addRow(
  "PASSWORD",
  passwordField
)

addRow(
  "CONFIRM PASSWORD",
  confirmField
)

addRow(
  "OPENING BALANCE ?",
  balanceField
)

val buttons =
  new JPanel(
    new FlowLayout(
      FlowLayout.LEFT,
      8,
      0
    )
  )

buttons.setOpaque(
  false
)

buttons.add(
  createButton
)

buttons.add(
  backButton
)

addRow(
  "ACTION",
  buttons
)

addRow(
  "STATUS",
  statusLabel
)

val rules =
  new JTextArea(
    "RULES\n\n" +
    "? Yadnesh Shahare is reserved.\n" +
    "? Bhavesh Shahare is reserved.\n" +
    "? Bhimraj is reserved.\n" +
    "? Alok is reserved.\n" +
    "? Only Yadnesh Shahare and Bhavesh Shahare have unlimited balances.\n" +
    "? Every other account must stay below ?1,00,000.\n" +
    "? Passwords are stored as SHA-256 hashes."
  )

rules.setEditable(
  false
)

rules.setLineWrap(
  true
)

rules.setWrapStyleWord(
  true
)

rules.setFont(
  new Font(
    "SansSerif",
    Font.PLAIN,
    12
  )
)

rules.setForeground(
  darkText
)

rules.setBackground(
  new Color(
    249,
    250,
    252
  )
)

rules.setBorder(
  BorderFactory.createCompoundBorder(
    BorderFactory.createLineBorder(
      new Color(
        226,
        230,
        236
      )
    ),
    BorderFactory.createEmptyBorder(
      10,
      12,
      10,
      12
    )
  )
)

val rulesGbc =
  new GridBagConstraints()

rulesGbc.gridx = 0
rulesGbc.gridy = row
rulesGbc.gridwidth = 2
rulesGbc.weightx = 1.0
rulesGbc.weighty = 1.0
rulesGbc.fill =
  GridBagConstraints.BOTH

form.add(
  rules,
  rulesGbc
)

cardPanel.add(
  form,
  BorderLayout.CENTER
)

center.add(
  cardPanel
)

root.add(
  center,
  BorderLayout.CENTER
)

createButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      val name =
        BankUtil.cleanName(
          nameField.getText
        )

      val password =
        new String(
          passwordField.getPassword
        )

      val confirm =
        new String(
          confirmField.getPassword
        )

      val balanceText =
        balanceField.getText

      var error =
        ""

      if (
        name.length < 3
      ) {

        error =
          "Enter a valid name."

      } else if (
        BankUtil.reservedName(name)
      ) {

        error =
          "That name is reserved."

      } else if (
        store.findByName(name).nonEmpty
      ) {

        error =
          "An account with that name already exists."

      } else if (
        password.length < 4
      ) {

        error =
          "Password must contain at least 4 characters."

      } else if (
        password != confirm
      ) {

        error =
          "Passwords do not match."

      } else {

        BankUtil.parsePersonalBalance(
          balanceText
        ) match {

          case None =>

            error =
              "Balance must be below ?1,00,000."

          case Some(balance) =>

            val account =
              new BankAccount(
                "USR-" +
                  UUID.randomUUID().toString,
                name,
                BankUtil.hashPassword(
                  password
                ),
                balance,
                false,
                false,
                System.currentTimeMillis()
              )

            store.accounts +=
              account

            store.save()

            showLogin(
              "Account created successfully."
            )

        }
      }

      if (
        error.nonEmpty
      ) {

        statusLabel.setText(
          error
        )

        statusLabel.setForeground(
          red
        )
      }
    }
  }
)

backButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      showLogin(
        ""
      )
    }
  }
)

frame.setContentPane(
  root
)

frame.setSize(
  1180,
  760
)

frame.setLocationRelativeTo(
  null
)

frame.setVisible(
  true
)

SwingUtilities.invokeLater(
  new Runnable {

    override def run(): Unit = {
      nameField.requestFocusInWindow()
    }
  }
)

}

def showDashboard(): Unit = {

currentAccount match {

  case None =>

    showLogin(
      "Please log in."
    )

  case Some(account) =>

    val root =
      new JPanel(
        new BorderLayout()
      )

    root.setBackground(
      pageBackground
    )

    val header =
      new BankHeader(
        "YADNESH NATIONAL BANK",
        "Logged in: " +
          account.name +
          " ? Account " +
          BankUtil.shortId(
            account.id
          )
      )

    header.setOpaque(
      true
    )

    header.setBackground(
      new Color(
        18,
        29,
        47
      )
    )

    root.add(
      header,
      BorderLayout.NORTH
    )

    val tabs =
      new JTabbedPane()

    tabs.setFont(
      new Font(
        "SansSerif",
        Font.BOLD,
        12
      )
    )

    tabs.addTab(
      "OVERVIEW",
      buildOverview(
        account
      )
    )

    tabs.addTab(
      "TRANSFER MONEY",
      buildTransfer(
        account
      )
    )

    tabs.addTab(
      "GET MONEY",
      buildGetMoney(
        account
      )
    )

    tabs.addTab(
      "MESSAGES" +
        unreadTabText(account),
      buildMessages(
        account
      )
    )

    tabs.addTab(
      "BANK ACCOUNTS",
      buildDirectory(
        account
      )
    )

    tabs.addTab(
      "DATA & SETTINGS",
      buildSettings(
        account
      )
    )

    root.add(
      tabs,
      BorderLayout.CENTER
    )

    val footer =
      new JPanel(
        new BorderLayout(
          10,
          0
        )
      )

    footer.setBackground(
      new Color(
        235,
        239,
        245
      )
    )

    footer.setBorder(
      BorderFactory.createEmptyBorder(
        5,
        10,
        5,
        10
      )
    )

    val saveStatus =
      label(
        store.status,
        10,
        false
      )

    saveStatus.setForeground(
      mutedText
    )

    val logoutButton =
      new JButton(
        "LOG OUT"
      )

    styleButton(
      logoutButton,
      false
    )

    footer.add(
      saveStatus,
      BorderLayout.CENTER
    )

    footer.add(
      logoutButton,
      BorderLayout.EAST
    )

    root.add(
      footer,
      BorderLayout.SOUTH
    )

    logoutButton.addActionListener(
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          showLogin(
            "Logged out successfully."
          )
        }
      }
    )

    frame.setTitle(
      "Yadnesh National Bank ? " +
        account.name
    )

    frame.setContentPane(
      root
    )

    frame.setSize(
      1180,
      760
    )

    frame.setLocationRelativeTo(
      null
    )

    frame.setVisible(
      true
    )
}

}

def unreadTabText(
account: BankAccount
): String = {

val count =
  store.unreadCount(
    account.id
  )

if (
  count > 0
) {
  " (" + count + ")"
} else {
  ""
}

}

def metricCard(
titleText: String,
valueText: String,
subtitleText: String
): JPanel = {

val panel =
  card()

val content =
  new JPanel()

content.setOpaque(
  false
)

content.setLayout(
  new BoxLayout(
    content,
    BoxLayout.Y_AXIS
  )
)

val title =
  label(
    titleText,
    11,
    true
  )

title.setForeground(
  mutedText
)

val value =
  label(
    valueText,
    20,
    true
  )

if (
  valueText.contains(
    "UNLIMITED"
  )
) {

  value.setForeground(
    gold
  )
}

val subtitle =
  label(
    subtitleText,
    11,
    false
  )

subtitle.setForeground(
  mutedText
)

content.add(
  title
)

content.add(
  Box.createVerticalStrut(
    6
  )
)

content.add(
  value
)

content.add(
  Box.createVerticalStrut(
    4
  )
)

content.add(
  subtitle
)

panel.add(
  content,
  BorderLayout.CENTER
)

panel

}

def buildOverview(
account: BankAccount
): JPanel = {

val root =
  new JPanel(
    new BorderLayout(
      12,
      12
    )
  )

root.setBackground(
  pageBackground
)

root.setBorder(
  BorderFactory.createEmptyBorder(
    15,
    15,
    15,
    15
  )
)

val metrics =
  new JPanel(
    new GridLayout(
      1,
      3,
      12,
      0
    )
  )

metrics.setOpaque(
  false
)

metrics.add(
  metricCard(
    "AVAILABLE BALANCE",
    account.shownBalance,
    if (account.unlimited) {
      "Founder ? unlimited"
    } else {
      "Personal ? below ?1,00,000"
    }
  )
)

val unread =
  store.unreadCount(
    account.id
  )

metrics.add(
  metricCard(
    "UNREAD MESSAGES",
    unread.toString,
    if (unread == 1) {
      "1 new message"
    } else {
      unread.toString +
        " new messages"
    }
  )
)

metrics.add(
  metricCard(
    "ACCOUNT TYPE",
    if (account.founder) {
      "FOUNDER"
    } else {
      "PERSONAL"
    },
    "ID: " +
      BankUtil.shortId(
        account.id
      )
  )
)

root.add(
  metrics,
  BorderLayout.NORTH
)

val bottom =
  new JPanel(
    new GridLayout(
      1,
      2,
      12,
      0
    )
  )

bottom.setOpaque(
  false
)

val quick =
  card()

quick.add(
  label(
    "QUICK ACTIONS",
    16,
    true
  ),
  BorderLayout.NORTH
)

val actionBox =
  new JPanel()

actionBox.setOpaque(
  false
)

actionBox.setLayout(
  new BoxLayout(
    actionBox,
    BoxLayout.Y_AXIS
  )
)

val transferButton =
  new JButton(
    "TRANSFER MONEY"
  )

val getMoneyButton =
  new JButton(
    "GET MONEY / MESSAGE YADNESH"
  )

val messagesButton =
  new JButton(
    "OPEN MESSAGES"
  )

val settingsButton =
  new JButton(
    "DATA & SETTINGS"
  )

styleButton(
  transferButton,
  true
)

styleButton(
  getMoneyButton,
  false
)

styleButton(
  messagesButton,
  false
)

styleButton(
  settingsButton,
  false
)

actionBox.add(
  transferButton
)

actionBox.add(
  Box.createVerticalStrut(
    10
  )
)

actionBox.add(
  getMoneyButton
)

actionBox.add(
  Box.createVerticalStrut(
    10
  )
)

actionBox.add(
  messagesButton
)

actionBox.add(
  Box.createVerticalStrut(
    10
  )
)

actionBox.add(
  settingsButton
)

quick.add(
  actionBox,
  BorderLayout.CENTER
)

transferButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      showDashboardTab(
        "TRANSFER MONEY"
      )
    }
  }
)

getMoneyButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      showDashboardTab(
        "GET MONEY"
      )
    }
  }
)

messagesButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      showDashboardTab(
        "MESSAGES"
      )
    }
  }
)

settingsButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      showDashboardTab(
        "DATA & SETTINGS"
      )
    }
  }
)

val activity =
  card()

activity.add(
  label(
    "RECENT ACTIVITY",
    16,
    true
  ),
  BorderLayout.NORTH
)

val area =
  new JTextArea()

area.setEditable(
  false
)

area.setLineWrap(
  true
)

area.setWrapStyleWord(
  true
)

area.setFont(
  new Font(
    "Monospaced",
    Font.PLAIN,
    12
  )
)

area.setForeground(
  darkText
)

area.setBackground(
  Color.WHITE
)

val recent =
  store
    .messagesFor(
      account.id
    )
    .take(15)

if (
  recent.isEmpty
) {

  area.setText(
    "No activity yet.\n\n" +
    "Send a transfer or use GET MONEY to request money from Yadnesh Shahare."
  )

} else {

  val text =
    new StringBuilder

  recent.foreach { message =>

    val otherId =
      if (
        message.toId ==
          account.id
      ) {
        message.fromId
      } else {
        message.toId
      }

    val otherName =
      store
        .findById(
          otherId
        )
        .map(
          _.name
        )
        .getOrElse(
          "Unknown"
        )

    text.append(
      message.kind
    )

    text.append(
      " ? "
    )

    text.append(
      otherName
    )

    text.append(
      "\n"
    )

    if (
      message.amount >
        BigDecimal(0)
    ) {

      text.append(
        "Amount: "
      )

      text.append(
        BankUtil.money(
          message.amount
        )
      )

      text.append(
        "\n"
      )
    }

    text.append(
      message.text
    )

    text.append(
      "\n"
    )

    text.append(
      BankUtil.timeText(
        message.createdAt
      )
    )

    text.append(
      "\n----------------------------------------\n"
    )
  }

  area.setText(
    text.toString
  )
}

activity.add(
  new JScrollPane(
    area
  ),
  BorderLayout.CENTER
)

bottom.add(
  quick
)

bottom.add(
  activity
)

root.add(
  bottom,
  BorderLayout.CENTER
)

root

}

def buildTransfer(
account: BankAccount
): JPanel = {

val root =
  new JPanel(
    new BorderLayout(
      12,
      12
    )
  )

root.setBackground(
  pageBackground
)

root.setBorder(
  BorderFactory.createEmptyBorder(
    15,
    15,
    15,
    15
  )
)

val left =
  card()

left.setPreferredSize(
  new Dimension(
    320,
    0
  )
)

left.add(
  label(
    "AVAILABLE ACCOUNTS",
    16,
    true
  ),
  BorderLayout.NORTH
)

val directory =
  new JTextArea()

directory.setEditable(
  false
)

directory.setFont(
  new Font(
    "SansSerif",
    Font.PLAIN,
    12
  )
)

directory.setForeground(
  darkText
)

directory.setBackground(
  Color.WHITE
)

val directoryText =
  new StringBuilder

store.accounts
  .filterNot(
    _.id == account.id
  )
  .foreach { other =>

    directoryText.append(
      other.name
    )

    directoryText.append(
      "\n"
    )

    directoryText.append(
      if (other.unlimited) {
        "UNLIMITED RUPEES"
      } else {
        "BELOW ?1,00,000"
      }
    )

    directoryText.append(
      "\nAccount: "
    )

    directoryText.append(
      BankUtil.shortId(
        other.id
      )
    )

    directoryText.append(
      "\nPassword: HIDDEN\n\n"
    )
  }

directory.setText(
  directoryText.toString
)

left.add(
  new JScrollPane(
    directory
  ),
  BorderLayout.CENTER
)

val right =
  card()

right.add(
  label(
    "TRANSFER MONEY",
    19,
    true
  ),
  BorderLayout.NORTH
)

val form =
  new JPanel(
    new GridBagLayout()
  )

form.setOpaque(
  false
)

val recipientField =
  new JTextField()

val amountField =
  new JTextField()

val noteField =
  new JTextField(
    "Bank transfer"
  )

styleField(
  recipientField
)

styleField(
  amountField
)

styleField(
  noteField
)

val sendButton =
  new JButton(
    "SEND TRANSFER"
  )

styleButton(
  sendButton,
  true
)

val statusLabel =
  label(
    " ",
    12,
    false
  )

statusLabel.setForeground(
  red
)

val sourceLabel =
  label(
    if (account.unlimited) {
      "UNLIMITED RUPEES"
    } else {
      account.shownBalance
    },
    14,
    true
  )

if (
  account.unlimited
) {
  sourceLabel.setForeground(
    gold
  )
}

var row =
  0

def addRow(
  titleText: String,
  component: java.awt.Component
): Unit = {

  val a =
    new GridBagConstraints()

  a.gridx = 0
  a.gridy = row
  a.weightx = 0.0
  a.anchor =
    GridBagConstraints.WEST

  a.insets =
    new Insets(
      7,
      0,
      7,
      12
    )

  form.add(
    label(
      titleText,
      12,
      true
    ),
    a
  )

  val b =
    new GridBagConstraints()

  b.gridx = 1
  b.gridy = row
  b.weightx = 1.0
  b.fill =
    GridBagConstraints.HORIZONTAL

  b.insets =
    new Insets(
      7,
      0,
      7,
      0
    )

  form.add(
    component,
    b
  )

  row += 1
}

addRow(
  "FROM",
  sourceLabel
)

addRow(
  "RECIPIENT",
  recipientField
)

addRow(
  "AMOUNT ?",
  amountField
)

addRow(
  "NOTE",
  noteField
)

addRow(
  "ACTION",
  sendButton
)

addRow(
  "STATUS",
  statusLabel
)

val help =
  new JTextArea(
    "All accounts can transfer money.\n\n" +
    "Yadnesh Shahare and Bhavesh Shahare are unlimited.\n" +
    "Every other account must remain below ?1,00,000 after a transfer."
  )

help.setEditable(
  false
)

help.setLineWrap(
  true
)

help.setWrapStyleWord(
  true
)

help.setFont(
  new Font(
    "SansSerif",
    Font.PLAIN,
    12
  )
)

help.setForeground(
  mutedText
)

help.setBackground(
  Color.WHITE
)

right.add(
  form,
  BorderLayout.CENTER
)

right.add(
  help,
  BorderLayout.SOUTH
)

sendButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      val recipientName =
        BankUtil.cleanName(
          recipientField.getText
        )

      val amountOption =
        BankUtil.parseAmount(
          amountField.getText
        )

      if (
        recipientName.isEmpty
      ) {

        statusLabel.setText(
          "Enter recipient name."
        )

      } else if (
        recipientName.equalsIgnoreCase(
          account.name
        )
      ) {

        statusLabel.setText(
          "You cannot transfer money to yourself."
        )

      } else {

        amountOption match {

          case None =>

            statusLabel.setText(
              "Enter a positive valid amount."
            )

          case Some(amount) =>

            store.findByName(
              recipientName
            ) match {

              case None =>

                statusLabel.setText(
                  "Recipient account not found."
                )

              case Some(recipient) =>

                val recipientWouldBe =
                  recipient.balance +
                    amount

                if (
                  !account.unlimited &&
                  amount > account.balance
                ) {

                  statusLabel.setText(
                    "Insufficient balance."
                  )

                } else if (
                  !recipient.unlimited &&
                  recipientWouldBe >=
                    BankUtil.personalLimit
                ) {

                  statusLabel.setText(
                    "Transfer rejected: recipient must stay below ?1,00,000."
                  )

                } else {

                  if (
                    !account.unlimited
                  ) {

                    account.balance =
                      account.balance -
                        amount
                  }

                  if (
                    !recipient.unlimited
                  ) {

                    recipient.balance =
                      recipient.balance +
                        amount
                  }

                  val noteText =
                    if (
                      noteField
                        .getText
                        .trim
                        .isEmpty
                    ) {
                      "Bank transfer"
                    } else {
                      noteField
                        .getText
                        .trim
                    }

                  val now =
                    System.currentTimeMillis()

                  store.addMessage(
                    new BankMessage(
                      UUID.randomUUID().toString,
                      recipient.id,
                      account.id,
                      "TRANSFER",
                      amount,
                      noteText,
                      now,
                      false,
                      true
                    )
                  )

                  store.addMessage(
                    new BankMessage(
                      UUID.randomUUID().toString,
                      account.id,
                      recipient.id,
                      "TRANSFER",
                      amount,
                      "Received from " +
                        account.name +
                        " ? " +
                        noteText,
                      now,
                      false,
                      true
                    )
                  )

                  store.save()

                  JOptionPane.showMessageDialog(
                    frame,
                    "Transfer completed.\n\n" +
                      "To: " +
                      recipient.name +
                      "\nAmount: " +
                      BankUtil.money(
                        amount
                      ),
                    "Transfer Successful",
                    JOptionPane.INFORMATION_MESSAGE
                  )

                  showDashboard()
                }
            }
        }
      }

      statusLabel.setForeground(
        red
      )
    }
  }
)

val split =
  new JPanel(
    new GridLayout(
      1,
      2,
      12,
      0
    )
  )

split.setOpaque(
  false
)

split.add(
  left
)

split.add(
  right
)

root.add(
  split,
  BorderLayout.CENTER
)

root

}

def buildGetMoney(
account: BankAccount
): JPanel = {

val root =
  new JPanel(
    new GridBagLayout()
  )

root.setBackground(
  pageBackground
)

root.setBorder(
  BorderFactory.createEmptyBorder(
    15,
    15,
    15,
    15
  )
)

val panel =
  card()

panel.setPreferredSize(
  new Dimension(
    820,
    530
  )
)

val form =
  new JPanel(
    new GridBagLayout()
  )

form.setOpaque(
  false
)

val heading =
  label(
    "GET MONEY",
    21,
    true
  )

val description =
  new JTextArea(
    "Need money?\n\n" +
    "Enter the amount and your message. A request will be sent to Yadnesh Shahare. " +
    "Yadnesh can approve or reject it from the MESSAGES tab."
  )

description.setEditable(
  false
)

description.setLineWrap(
  true
)

description.setWrapStyleWord(
  true
)

description.setFont(
  new Font(
    "SansSerif",
    Font.PLAIN,
    13
  )
)

description.setForeground(
  mutedText
)

description.setBackground(
  Color.WHITE
)

description.setBorder(
  BorderFactory.createEmptyBorder(
    4,
    0,
    12,
    0
  )
)

val amountField =
  new JTextField()

styleField(
  amountField
)

val messageArea =
  new JTextArea()

messageArea.setRows(
  6
)

messageArea.setLineWrap(
  true
)

messageArea.setWrapStyleWord(
  true
)

messageArea.setFont(
  new Font(
    "SansSerif",
    Font.PLAIN,
    13
  )
)

messageArea.setBorder(
  BorderFactory.createCompoundBorder(
    BorderFactory.createLineBorder(
      new Color(
        201,
        209,
        220
      )
    ),
    BorderFactory.createEmptyBorder(
      8,
      10,
      8,
      10
    )
  )
)

val requestButton =
  new JButton(
    "SEND MESSAGE TO YADNESH SHAHARE"
  )

styleButton(
  requestButton,
  true
)

val statusLabel =
  label(
    " ",
    12,
    false
  )

statusLabel.setForeground(
  red
)

var row =
  0

def addRow(
  titleText: String,
  component: java.awt.Component
): Unit = {

  val a =
    new GridBagConstraints()

  a.gridx = 0
  a.gridy = row
  a.weightx = 0.0
  a.anchor =
    GridBagConstraints.WEST

  a.insets =
    new Insets(
      7,
      0,
      7,
      12
    )

  form.add(
    label(
      titleText,
      12,
      true
    ),
    a
  )

  val b =
    new GridBagConstraints()

  b.gridx = 1
  b.gridy = row
  b.weightx = 1.0
  b.fill =
    GridBagConstraints.HORIZONTAL

  b.insets =
    new Insets(
      7,
      0,
      7,
      0
    )

  form.add(
    component,
    b
  )

  row += 1
}

val h =
  new GridBagConstraints()

h.gridx = 0
h.gridy = row
h.gridwidth = 2
h.weightx = 1.0
h.fill =
  GridBagConstraints.HORIZONTAL

form.add(
  heading,
  h
)

row += 1

val d =
  new GridBagConstraints()

d.gridx = 0
d.gridy = row
d.gridwidth = 2
d.weightx = 1.0
d.fill =
  GridBagConstraints.HORIZONTAL

form.add(
  description,
  d
)

row += 1

addRow(
  "REQUEST FROM",
  label(
    "Yadnesh Shahare",
    14,
    true
  )
)

addRow(
  "AMOUNT ?",
  amountField
)

addRow(
  "MESSAGE",
  new JScrollPane(
    messageArea
  )
)

addRow(
  "ACTION",
  requestButton
)

addRow(
  "STATUS",
  statusLabel
)

panel.add(
  form,
  BorderLayout.CENTER
)

root.add(
  panel
)

requestButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      if (
        BankUtil.isYadnesh(account)
      ) {

        statusLabel.setText(
          "Yadnesh cannot request money from himself."
        )

      } else {

        BankUtil.parseAmount(
          amountField.getText
        ) match {

          case None =>

            statusLabel.setText(
              "Enter a positive amount."
            )

          case Some(amount) =>

            val targetBalance =
              account.balance +
                amount

            if (
              targetBalance >=
                BankUtil.personalLimit
            ) {

              statusLabel.setText(
                "Request is too large: your balance must remain below ?1,00,000."
              )

            } else {

              store.findYadnesh() match {

                case None =>

                  statusLabel.setText(
                    "Yadnesh account not found."
                  )

                case Some(yadnesh) =>

                  val messageText =
                    if (
                      messageArea
                        .getText
                        .trim
                        .isEmpty
                    ) {
                      "I need money."
                    } else {
                      messageArea
                        .getText
                        .trim
                    }

                  store.addMessage(
                    new BankMessage(
                      UUID.randomUUID().toString,
                      yadnesh.id,
                      account.id,
                      "REQUEST",
                      amount,
                      messageText,
                      System.currentTimeMillis(),
                      false,
                      false
                    )
                  )

                  store.save()

                  JOptionPane.showMessageDialog(
                    frame,
                    "Request sent to Yadnesh Shahare for " +
                      BankUtil.money(
                        amount
                      ),
                    "Request Sent",
                    JOptionPane.INFORMATION_MESSAGE
                  )

                  amountField.setText(
                    ""
                  )

                  messageArea.setText(
                    ""
                  )

                  showDashboard()
              }
            }
        }
      }

      statusLabel.setForeground(
        red
      )
    }
  }
)

root

}

def buildMessages(
account: BankAccount
): JPanel = {

val root =
  new JPanel(
    new BorderLayout(
      10,
      10
    )
  )

root.setBackground(
  pageBackground
)

root.setBorder(
  BorderFactory.createEmptyBorder(
    15,
    15,
    15,
    15
  )
)

val top =
  new JPanel(
    new BorderLayout()
  )

top.setOpaque(
  false
)

val titlePanel =
  new JPanel()

titlePanel.setOpaque(
  false
)

titlePanel.setLayout(
  new BoxLayout(
    titlePanel,
    BoxLayout.Y_AXIS
  )
)

titlePanel.add(
  label(
    "MESSAGES & REQUESTS",
    20,
    true
  )
)

titlePanel.add(
  Box.createVerticalStrut(
    3
  )
)

titlePanel.add(
  muted(
    "Transfers and money requests appear here. Passwords are never shown."
  )
)

top.add(
  titlePanel,
  BorderLayout.WEST
)

val markReadButton =
  new JButton(
    "MARK ALL READ"
  )

styleButton(
  markReadButton,
  false
)

top.add(
  markReadButton,
  BorderLayout.EAST
)

root.add(
  top,
  BorderLayout.NORTH
)

val list =
  new JPanel()

list.setBackground(
  pageBackground
)

list.setLayout(
  new BoxLayout(
    list,
    BoxLayout.Y_AXIS
  )
)

val items =
  store.messagesFor(
    account.id
  )

if (
  items.isEmpty
) {

  val empty =
    card()

  empty.add(
    label(
      "No messages yet.",
      14,
      false
    ),
    BorderLayout.CENTER
  )

  list.add(
    empty
  )

} else {

  items.foreach { message =>

    val panel =
      card()

    val heading =
      new JPanel(
        new BorderLayout()
      )

    heading.setOpaque(
      false
    )

    val otherId =
      if (
        message.toId ==
          account.id
      ) {
        message.fromId
      } else {
        message.toId
      }

    val otherName =
      store
        .findById(
          otherId
        )
        .map(
          _.name
        )
        .getOrElse(
          "Unknown"
        )

    val direction =
      if (
        message.toId ==
          account.id
      ) {
        "FROM: " +
          otherName
      } else {
        "TO: " +
          otherName
      }

    heading.add(
      label(
        message.kind +
          " ? " +
          direction,
        14,
        true
      ),
      BorderLayout.CENTER
    )

    val date =
      label(
        BankUtil.timeText(
          message.createdAt
        ),
        11,
        false
      )

    date.setForeground(
      mutedText
    )

    heading.add(
      date,
      BorderLayout.EAST
    )

    panel.add(
      heading,
      BorderLayout.NORTH
    )

    val body =
      new JTextArea()

    body.setEditable(
      false
    )

    body.setLineWrap(
      true
    )

    body.setWrapStyleWord(
      true
    )

    body.setFont(
      new Font(
        "SansSerif",
        Font.PLAIN,
        13
      )
    )

    body.setForeground(
      darkText
    )

    body.setBackground(
      Color.WHITE
    )

    body.setBorder(
      BorderFactory.createEmptyBorder(
        8,
        0,
        8,
        0
      )
    )

    val amountText =
      if (
        message.amount >
          BigDecimal(0)
      ) {
        "Amount: " +
          BankUtil.money(
            message.amount
          ) +
          "\n"
      } else {
        ""
      }

    val statusText =
      if (
        message.kind ==
          "REQUEST"
      ) {

        if (
          message.handled
        ) {
          "\nStatus: HANDLED"
        } else {
          "\nStatus: WAITING FOR YADNESH"
        }

      } else {
        ""
      }

    body.setText(
      amountText +
        message.text +
        statusText
    )

    panel.add(
      body,
      BorderLayout.CENTER
    )

    val actions =
      new JPanel(
        new FlowLayout(
          FlowLayout.LEFT,
          8,
          0
        )
      )

    actions.setOpaque(
      false
    )

    if (
      message.toId ==
        account.id &&
      !message.read
    ) {

      val readButton =
        new JButton(
          "MARK READ"
        )

      styleButton(
        readButton,
        false
      )

      readButton.addActionListener(
        new ActionListener {

          override def actionPerformed(
            e: ActionEvent
          ): Unit = {

            message.read =
              true

            store.save()

            showDashboardTab(
              "MESSAGES"
            )
          }
        }
      )

      actions.add(
        readButton
      )
    }

    if (
      BankUtil.isYadnesh(account) &&
      message.toId ==
        account.id &&
      message.kind ==
        "REQUEST" &&
      !message.handled
    ) {

      val approveButton =
        new JButton(
          "APPROVE"
        )

      styleButton(
        approveButton,
        true
      )

      approveButton.addActionListener(
        new ActionListener {

          override def actionPerformed(
            e: ActionEvent
          ): Unit = {

            store.findById(
              message.fromId
            ) match {

              case None =>

                JOptionPane.showMessageDialog(
                  frame,
                  "Requesting account no longer exists.",
                  "Error",
                  JOptionPane.ERROR_MESSAGE
                )

              case Some(requester) =>

                val futureBalance =
                  requester.balance +
                    message.amount

                if (
                  futureBalance >=
                    BankUtil.personalLimit &&
                  !requester.unlimited
                ) {

                  JOptionPane.showMessageDialog(
                    frame,
                    "Cannot approve: recipient must remain below ?1,00,000.",
                    "Request Limit",
                    JOptionPane.WARNING_MESSAGE
                  )

                } else {

                  if (
                    !account.unlimited &&
                    message.amount >
                      account.balance
                  ) {

                    JOptionPane.showMessageDialog(
                      frame,
                      "Insufficient Yadnesh balance.",
                      "Error",
                      JOptionPane.ERROR_MESSAGE
                    )

                  } else {

                    if (
                      !account.unlimited
                    ) {

                      account.balance =
                        account.balance -
                          message.amount
                    }

                    if (
                      !requester.unlimited
                    ) {

                      requester.balance =
                        requester.balance +
                          message.amount
                    }

                    message.handled =
                      true

                    message.read =
                      true

                    store.addMessage(
                      new BankMessage(
                        UUID.randomUUID().toString,
                        requester.id,
                        account.id,
                        "TRANSFER",
                        message.amount,
                        "Yadnesh Shahare approved your money request.",
                        System.currentTimeMillis(),
                        false,
                        true
                      )
                    )

                    store.save()

                    JOptionPane.showMessageDialog(
                      frame,
                      "Approved " +
                        BankUtil.money(
                          message.amount
                        ) +
                        " for " +
                        requester.name +
                        ".",
                      "Request Approved",
                      JOptionPane.INFORMATION_MESSAGE
                    )

                    showDashboardTab(
                      "MESSAGES"
                    )
                  }
                }
            }
          }
        }
      )

      val rejectButton =
        new JButton(
          "REJECT"
        )

      styleButton(
        rejectButton,
        false
      )

      rejectButton.addActionListener(
        new ActionListener {

          override def actionPerformed(
            e: ActionEvent
          ): Unit = {

            message.handled =
              true

            message.read =
              true

            store.findById(
              message.fromId
            ).foreach { requester =>

              store.addMessage(
                new BankMessage(
                  UUID.randomUUID().toString,
                  requester.id,
                  account.id,
                  "REQUEST-UPDATE",
                  BigDecimal(0),
                  "Yadnesh Shahare rejected your money request.",
                  System.currentTimeMillis(),
                  false,
                  true
                )
              )
            }

            store.save()

            showDashboardTab(
              "MESSAGES"
            )
          }
        }
      )

      actions.add(
        approveButton
      )

      actions.add(
        rejectButton
      )
    }

    if (
      actions.getComponentCount > 0
    ) {

      panel.add(
        actions,
        BorderLayout.SOUTH
      )
    }

    list.add(
      panel
    )

    list.add(
      Box.createVerticalStrut(
        9
      )
    )
  }
}

markReadButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      store.messages
        .filter(
          _.toId ==
            account.id
        )
        .foreach(
          _.read = true
        )

      store.save()

      showDashboardTab(
        "MESSAGES"
      )
    }
  }
)

root.add(
  new JScrollPane(
    list
  ),
  BorderLayout.CENTER
)

root

}

def buildDirectory(
account: BankAccount
): JPanel = {

val root =
  new JPanel(
    new BorderLayout(
      10,
      10
    )
  )

root.setBackground(
  pageBackground
)

root.setBorder(
  BorderFactory.createEmptyBorder(
    15,
    15,
    15,
    15
  )
)

val panel =
  card()

panel.add(
  label(
    "BANK ACCOUNTS",
    19,
    true
  ),
  BorderLayout.NORTH
)

val area =
  new JTextArea()

area.setEditable(
  false
)

area.setFont(
  new Font(
    "Monospaced",
    Font.PLAIN,
    12
  )
)

area.setForeground(
  darkText
)

area.setBackground(
  Color.WHITE
)

val text =
  new StringBuilder

store.accounts.foreach { other =>

  text.append(
    "NAME       : "
  )

  text.append(
    other.name
  )

  text.append(
    "\n"
  )

  text.append(
    "ACCOUNT ID : "
  )

  text.append(
    other.id
  )

  text.append(
    "\n"
  )

  text.append(
    "TYPE       : "
  )

  text.append(
    if (other.unlimited) {
      "FOUNDER ? UNLIMITED"
    } else if (other.founder) {
      "FOUNDER ? BELOW ?1,00,000"
    } else {
      "PERSONAL ? BELOW ?1,00,000"
    }
  )

  text.append(
    "\n"
  )

  text.append(
    "BALANCE    : "
  )

  text.append(
    other.shownBalance
  )

  text.append(
    "\n"
  )

  text.append(
    "PASSWORD   : HIDDEN"
  )

  text.append(
    "\n"
  )

  if (
    other.id ==
      account.id
  ) {

    text.append(
      "LOGGED IN  : YES\n"
    )
  }

  text.append(
    "------------------------------------------------------------\n"
  )
}

area.setText(
  text.toString
)

panel.add(
  new JScrollPane(
    area
  ),
  BorderLayout.CENTER
)

root.add(
  panel,
  BorderLayout.CENTER
)

val note =
  card()

val noteArea =
  new JTextArea(
    "PASSWORD SECURITY\n\n" +
    "No password is shown in the bank directory.\n" +
    "Yadnesh Shahare's password is especially protected and is never displayed."
  )

noteArea.setEditable(
  false
)

noteArea.setLineWrap(
  true
)

noteArea.setWrapStyleWord(
  true
)

noteArea.setFont(
  new Font(
    "SansSerif",
    Font.PLAIN,
    12
  )
)

noteArea.setForeground(
  mutedText
)

noteArea.setBackground(
  Color.WHITE
)

note.add(
  noteArea
)

root.add(
  note,
  BorderLayout.SOUTH
)

root

}

def buildSettings(
account: BankAccount
): JPanel = {

val root =
  new JPanel(
    new GridBagLayout()
  )

root.setBackground(
  pageBackground
)

root.setBorder(
  BorderFactory.createEmptyBorder(
    15,
    15,
    15,
    15
  )
)

val panel =
  card()

panel.setPreferredSize(
  new Dimension(
    850,
    610
  )
)

val content =
  new JPanel()

content.setOpaque(
  false
)

content.setLayout(
  new BoxLayout(
    content,
    BoxLayout.Y_AXIS
  )
)

content.add(
  label(
    "DATA & SETTINGS",
    21,
    true
  )
)

content.add(
  Box.createVerticalStrut(
    7
  )
)

val info =
  new JTextArea(
    "LOCAL DATABASE\n\n" +
    store.dataFile.getAbsolutePath +
    "\n\n" +
    "Accounts, balances, transfers, requests and hashed passwords are stored locally."
  )

info.setEditable(
  false
)

info.setLineWrap(
  true
)

info.setWrapStyleWord(
  true
)

info.setFont(
  new Font(
    "SansSerif",
    Font.PLAIN,
    12
  )
)

info.setForeground(
  darkText
)

info.setBackground(
  new Color(
    249,
    250,
    252
  )
)

info.setBorder(
  BorderFactory.createCompoundBorder(
    BorderFactory.createLineBorder(
      new Color(
        226,
        230,
        236
      )
    ),
    BorderFactory.createEmptyBorder(
      10,
      12,
      10,
      12
    )
  )
)

content.add(
  info
)

content.add(
  Box.createVerticalStrut(
    12
  )
)

val exportButton =
  new JButton(
    "EXPORT BANK DATA"
  )

val importButton =
  new JButton(
    "IMPORT BANK DATA"
  )

val saveButton =
  new JButton(
    "SAVE NOW"
  )

val passwordButton =
  new JButton(
    "CHANGE MY PASSWORD"
  )

val closeButton =
  new JButton(
    "CLOSE MY PERSONAL ACCOUNT"
  )

styleButton(
  exportButton,
  true
)

styleButton(
  importButton,
  false
)

styleButton(
  saveButton,
  false
)

styleButton(
  passwordButton,
  false
)

styleButton(
  closeButton,
  false
)

val buttons =
  new JPanel(
    new GridLayout(
      5,
      1,
      0,
      9
    )
  )

buttons.setOpaque(
  false
)

buttons.add(
  exportButton
)

buttons.add(
  importButton
)

buttons.add(
  saveButton
)

buttons.add(
  passwordButton
)

buttons.add(
  closeButton
)

content.add(
  buttons
)

content.add(
  Box.createVerticalStrut(
    15
  )
)

val security =
  new JTextArea(
    "SECURITY\n\n" +
    "? Password fields are hidden.\n" +
    "? No password is displayed in the UI.\n" +
    "? Passwords are stored as SHA-256 hashes.\n" +
    "? Yadnesh Shahare and Bhavesh Shahare are the only unlimited accounts.\n" +
    "? Every other account is kept below ?1,00,000."
  )

security.setEditable(
  false
)

security.setLineWrap(
  true
)

security.setWrapStyleWord(
  true
)

security.setFont(
  new Font(
    "SansSerif",
    Font.PLAIN,
    12
  )
)

security.setForeground(
  mutedText
)

security.setBackground(
  Color.WHITE
)

content.add(
  security
)

content.add(
  Box.createVerticalStrut(
    15
  )
)

val multiPc =
  new JTextArea(
    "MULTI-PC\n\n" +
    "This version is local/offline. To move the same bank database to another PC, export the bank-data file and import it on the other PC.\n\n" +
    "This synchronizes the saved database; it does not provide simultaneous internet banking."
  )

multiPc.setEditable(
  false
)

multiPc.setLineWrap(
  true
)

multiPc.setWrapStyleWord(
  true
)

multiPc.setFont(
  new Font(
    "SansSerif",
    Font.PLAIN,
    12
  )
)

multiPc.setForeground(
  mutedText
)

multiPc.setBackground(
  Color.WHITE
)

content.add(
  multiPc
)

content.add(
  Box.createVerticalStrut(
    14
  )
)

val logged =
  label(
    "Logged in: " +
      account.name +
      " ? " +
      account.shownBalance,
    13,
    true
  )

if (
  account.unlimited
) {
  logged.setForeground(
    gold
  )
}

content.add(
  logged
)

panel.add(
  content,
  BorderLayout.CENTER
)

root.add(
  panel
)

exportButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      val chooser =
        new JFileChooser()

      chooser.setDialogTitle(
        "Export Yadnesh Bank Data"
      )

      chooser.setSelectedFile(
        new File(
          "bank-data.ydb"
        )
      )

      if (
        chooser.showSaveDialog(
          frame
        ) ==
          JFileChooser.APPROVE_OPTION
      ) {

        val file =
          chooser.getSelectedFile

        if (
          store.exportTo(
            file
          )
        ) {

          JOptionPane.showMessageDialog(
            frame,
            "Bank data exported successfully.",
            "Export Complete",
            JOptionPane.INFORMATION_MESSAGE
          )

        } else {

          JOptionPane.showMessageDialog(
            frame,
            store.status,
            "Export Error",
            JOptionPane.ERROR_MESSAGE
          )
        }
      }
    }
  }
)

importButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      val chooser =
        new JFileChooser()

      chooser.setDialogTitle(
        "Import Yadnesh Bank Data"
      )

      if (
        chooser.showOpenDialog(
          frame
        ) ==
          JFileChooser.APPROVE_OPTION
      ) {

        val file =
          chooser.getSelectedFile

        val confirm =
          JOptionPane.showConfirmDialog(
            frame,
            "Import will replace the current local database on this PC.\nContinue?",
            "Confirm Import",
            JOptionPane.YES_NO_OPTION,
            JOptionPane.WARNING_MESSAGE
          )

        if (
          confirm ==
            JOptionPane.YES_OPTION
        ) {

          if (
            store.importFrom(
              file
            )
          ) {

            currentAccount match {

              case Some(oldAccount) =>

                store.findById(
                  oldAccount.id
                ) match {

                  case Some(newAccount) =>

                    currentAccount =
                      Some(newAccount)

                    JOptionPane.showMessageDialog(
                      frame,
                      "Bank database imported successfully.",
                      "Import Complete",
                      JOptionPane.INFORMATION_MESSAGE
                    )

                    showDashboard()

                  case None =>

                    currentAccount =
                      None

                    showLogin(
                      "Imported database does not contain your old account."
                    )
                }

              case None =>

                showLogin(
                  ""
                )
            }

          } else {

            JOptionPane.showMessageDialog(
              frame,
              store.status,
              "Import Error",
              JOptionPane.ERROR_MESSAGE
            )
          }
        }
      }
    }
  }
)

saveButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      if (
        store.save()
      ) {

        JOptionPane.showMessageDialog(
          frame,
          "Bank database saved successfully.",
          "Saved",
          JOptionPane.INFORMATION_MESSAGE
        )

      } else {

        JOptionPane.showMessageDialog(
          frame,
          store.status,
          "Save Warning",
          JOptionPane.WARNING_MESSAGE
        )
      }
    }
  }
)

passwordButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      val oldField =
        new JPasswordField()

      val newField =
        new JPasswordField()

      val confirmField =
        new JPasswordField()

      styleField(
        oldField
      )

      styleField(
        newField
      )

      styleField(
        confirmField
      )

      val form =
        new JPanel(
          new GridLayout(
            3,
            2,
            8,
            8
          )
        )

      form.add(
        label(
          "CURRENT PASSWORD",
          12,
          true
        )
      )

      form.add(
        oldField
      )

      form.add(
        label(
          "NEW PASSWORD",
          12,
          true
        )
      )

      form.add(
        newField
      )

      form.add(
        label(
          "CONFIRM NEW",
          12,
          true
        )
      )

      form.add(
        confirmField
      )

      val result =
        JOptionPane.showConfirmDialog(
          frame,
          form,
          "Change Password",
          JOptionPane.OK_CANCEL_OPTION,
          JOptionPane.PLAIN_MESSAGE
        )

      if (
        result ==
          JOptionPane.OK_OPTION
      ) {

        val oldPassword =
          new String(
            oldField.getPassword
          )

        val newPassword =
          new String(
            newField.getPassword
          )

        val confirmPassword =
          new String(
            confirmField.getPassword
          )

        if (
          !BankUtil.verifyPassword(
            oldPassword,
            account.passwordHash
          )
        ) {

          JOptionPane.showMessageDialog(
            frame,
            "Current password is incorrect.",
            "Password Error",
            JOptionPane.ERROR_MESSAGE
          )

        } else if (
          newPassword.length < 4
        ) {

          JOptionPane.showMessageDialog(
            frame,
            "New password must contain at least 4 characters.",
            "Password Error",
            JOptionPane.ERROR_MESSAGE
          )

        } else if (
          newPassword !=
            confirmPassword
        ) {

          JOptionPane.showMessageDialog(
            frame,
            "New passwords do not match.",
            "Password Error",
            JOptionPane.ERROR_MESSAGE
          )

        } else {

          account.passwordHash =
            BankUtil.hashPassword(
              newPassword
            )

          store.save()

          JOptionPane.showMessageDialog(
            frame,
            "Password changed successfully.",
            "Password Updated",
            JOptionPane.INFORMATION_MESSAGE
          )

          showDashboard()
        }
      }
    }
  }
)

closeButton.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      if (
        account.founder
      ) {

        JOptionPane.showMessageDialog(
          frame,
          "Founder accounts cannot be closed here.",
          "Founder Account",
          JOptionPane.INFORMATION_MESSAGE
        )

      } else {

        val confirm =
          JOptionPane.showConfirmDialog(
            frame,
            "Close " +
              account.name +
              "?\n\nThis deletes the local personal account.",
            "Close Account",
            JOptionPane.YES_NO_OPTION,
            JOptionPane.WARNING_MESSAGE
          )

        if (
          confirm ==
            JOptionPane.YES_OPTION
        ) {

          store.accounts -=
            account

          val oldMessages =
            store.messages
              .filter(
                message =>
                  message.toId ==
                    account.id ||
                  message.fromId ==
                    account.id
              )
              .toSeq

          oldMessages.foreach(
            message =>
              store.messages -=
                message
          )

          store.save()

          currentAccount =
            None

          showLogin(
            "Personal account closed."
          )
        }
      }
    }
  }
)

root

}

def showDashboardTab(
tabName: String
): Unit = {

showDashboard()

SwingUtilities.invokeLater(
  new Runnable {

    override def run(): Unit = {

      val content =
        frame.getContentPane

      if (
        content.isInstanceOf[
          JPanel
        ]
      ) {

        val root =
          content.asInstanceOf[
            JPanel
          ]

        val components =
          root.getComponents

        var tabs:
          JTabbedPane =
          null

        var i =
          0

        while (
          i <
            components.length
        ) {

          if (
            components(i)
              .isInstanceOf[
                JTabbedPane
              ]
          ) {

            tabs =
              components(i)
                .asInstanceOf[
                  JTabbedPane
                ]
          }

          i += 1
        }

        if (
          tabs != null
        ) {

          var index =
            0

          var selected =
            -1

          while (
            index <
              tabs.getTabCount &&
            selected ==
              -1
          ) {

            if (
              tabs
                .getTitleAt(
                  index
                )
                .startsWith(
                  tabName
                )
            ) {

              selected =
                index
            }

            index += 1
          }

          if (
            selected >= 0
          ) {

            tabs.setSelectedIndex(
              selected
            )
          }
        }
      }
    }
  }
)

}
}

BankApp.start()