Code Sketch


GRAPHIC_7325 TR
By: Mhalsakant School
Category: Art
import javax.swing.JFrame
import javax.swing.JPanel
import javax.swing.JLabel
import javax.swing.JButton
import javax.swing.JTextField
import javax.swing.JPasswordField
import javax.swing.JTextArea
import javax.swing.JScrollPane
import javax.swing.JTable
import javax.swing.JSplitPane
import javax.swing.BorderFactory
import javax.swing.WindowConstants
import javax.swing.SwingConstants
import javax.swing.JOptionPane
import javax.swing.DefaultListModel
import javax.swing.JList
import javax.swing.ListSelectionModel
import javax.swing.table.DefaultTableModel
import javax.swing.Timer
import javax.swing.border.EmptyBorder
import javax.swing.border.LineBorder

import java.awt.Color
import java.awt.Font
import java.awt.Dimension
import java.awt.BorderLayout
import java.awt.FlowLayout
import java.awt.GridLayout
import java.awt.CardLayout
import java.awt.Component
import java.awt.Cursor
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.RenderingHints
import java.awt.BasicStroke
import java.awt.geom.Path2D
import java.awt.event.ActionEvent

import java.text.SimpleDateFormat
import java.util.Date

// ============================================================
// CONFIG
// ============================================================

object AppConfig {

  val AppTitle =
    "INDIA RTO & LIVE TRAIN MAP SUITE"

  val Width = 1250
  val Height = 800

  // Login password
  private val LoginPassword = "INDIA@RTO7313"

  def checkPassword(value: String): Boolean =
    value.trim == LoginPassword

  val Dark =
    new Color(24, 43, 73)

  val Blue =
    new Color(41, 128, 185)

  val Green =
    new Color(39, 174, 96)

  val Red =
    new Color(192, 57, 43)

  val Orange =
    new Color(230, 126, 34)

  val Background =
    new Color(245, 248, 252)

  val Text =
    new Color(33, 33, 33)

  val Secondary =
    new Color(110, 110, 110)

  val White =
    new Color(255, 255, 255)
}

// ============================================================
// RTO DATA
// ============================================================

object RTODatabase {

  val states: Map[String, String] = Map(
    "AP" -> "Andhra Pradesh",
    "AR" -> "Arunachal Pradesh",
    "AS" -> "Assam",
    "BR" -> "Bihar",
    "CG" -> "Chhattisgarh",
    "GA" -> "Goa",
    "GJ" -> "Gujarat",
    "HR" -> "Haryana",
    "HP" -> "Himachal Pradesh",
    "JH" -> "Jharkhand",
    "KA" -> "Karnataka",
    "KL" -> "Kerala",
    "MP" -> "Madhya Pradesh",
    "MH" -> "Maharashtra",
    "MN" -> "Manipur",
    "ML" -> "Meghalaya",
    "MZ" -> "Mizoram",
    "NL" -> "Nagaland",
    "OD" -> "Odisha",
    "PB" -> "Punjab",
    "RJ" -> "Rajasthan",
    "SK" -> "Sikkim",
    "TN" -> "Tamil Nadu",
    "TS" -> "Telangana",
    "TR" -> "Tripura",
    "UK" -> "Uttarakhand",
    "UP" -> "Uttar Pradesh",
    "WB" -> "West Bengal",
    "AN" -> "Andaman & Nicobar Islands",
    "CH" -> "Chandigarh",
    "DL" -> "Delhi",
    "JK" -> "Jammu & Kashmir",
    "LA" -> "Ladakh",
    "LD" -> "Lakshadweep",
    "PY" -> "Puducherry"
  )

  val rtos: Map[String, String] = Map(

    "MH01" -> "Mumbai Central",
    "MH02" -> "Mumbai West",
    "MH03" -> "Mumbai East",
    "MH04" -> "Thane",
    "MH05" -> "Kalyan",
    "MH06" -> "Raigad",
    "MH07" -> "Sindhudurg",
    "MH08" -> "Ratnagiri",
    "MH09" -> "Kolhapur",
    "MH10" -> "Sangli",
    "MH11" -> "Satara",
    "MH12" -> "Pune City",
    "MH13" -> "Solapur",
    "MH14" -> "Pimpri-Chinchwad",
    "MH15" -> "Nashik",
    "MH16" -> "Ahmednagar",
    "MH17" -> "Shrirampur",
    "MH18" -> "Dhule",
    "MH19" -> "Jalgaon",
    "MH20" -> "Chhatrapati Sambhajinagar",
    "MH21" -> "Jalna",
    "MH22" -> "Parbhani",
    "MH23" -> "Beed",
    "MH24" -> "Latur",
    "MH25" -> "Dharashiv",
    "MH26" -> "Nanded",
    "MH27" -> "Amravati",
    "MH28" -> "Buldhana",
    "MH29" -> "Yavatmal",
    "MH30" -> "Akola",
    "MH31" -> "Nagpur Urban",
    "MH32" -> "Wardha",
    "MH33" -> "Gadchiroli",
    "MH34" -> "Chandrapur",
    "MH35" -> "Gondia",
    "MH36" -> "Bhandara",
    "MH37" -> "Washim",
    "MH38" -> "Hingoli",
    "MH39" -> "Nandurbar",
    "MH40" -> "Nagpur Rural",
    "MH41" -> "Malegaon",
    "MH42" -> "Baramati",
    "MH43" -> "Navi Mumbai",
    "MH44" -> "Ambejogai",
    "MH45" -> "Akluj",
    "MH46" -> "Panvel",
    "MH47" -> "Borivali",
    "MH48" -> "Vasai-Virar",
    "MH49" -> "Nagpur East",
    "MH50" -> "Karad",

    "DL01" -> "Civil Lines",
    "DL02" -> "IP Depot",
    "DL03" -> "Sheikh Sarai",
    "DL04" -> "Janakpuri",

    "KA01" -> "Bengaluru Central",
    "KA02" -> "Bengaluru West",
    "KA03" -> "Bengaluru East",
    "KA09" -> "Mysuru",

    "GJ01" -> "Ahmedabad",
    "GJ03" -> "Rajkot",
    "GJ05" -> "Surat",
    "GJ06" -> "Vadodara",

    "UP14" -> "Ghaziabad",
    "UP16" -> "Noida",
    "UP32" -> "Lucknow",
    "UP78" -> "Kanpur",

    "MP04" -> "Bhopal",
    "MP09" -> "Indore",
    "MP20" -> "Jabalpur"
  )

  val all: Map[String, String] =
    states ++ rtos

  def find(query: String): Seq[(String, String)] = {

    val q =
      query
        .trim
        .toUpperCase

    all.toSeq
      .filter {
        case (code, name) =>
          code.contains(q) ||
          name.toUpperCase.contains(q)
      }
      .sortBy(_._1)
  }
}

// ============================================================
// TRAIN MODEL
// ============================================================

case class TrainInfo(
  number: String,
  name: String,
  source: String,
  destination: String,
  currentStation: String,
  nextStation: String,
  status: String,
  latitude: Double,
  longitude: Double,
  delayMinutes: Int
)

// ============================================================
// DEMO TRAIN DATABASE
// ============================================================

object TrainDatabase {

  val trains: Seq[TrainInfo] = Seq(

    TrainInfo(
      "12110",
      "Panchavati Express",
      "Manmad",
      "Mumbai CSMT",
      "Nashik Road",
      "Kasara",
      "Running",
      19.9975,
      73.7898,
      4
    ),

    TrainInfo(
      "12123",
      "Deccan Queen",
      "Mumbai CSMT",
      "Pune",
      "Karjat",
      "Lonavala",
      "Running",
      18.9100,
      73.3000,
      2
    ),

    TrainInfo(
      "11010",
      "Sinhagad Express",
      "Pune",
      "Mumbai CSMT",
      "Lonavala",
      "Kalyan",
      "Running",
      18.7500,
      73.4100,
      0
    ),

    TrainInfo(
      "11020",
      "Konark Express",
      "Mumbai",
      "Bhubaneswar",
      "Pune",
      "Solapur",
      "Running",
      18.5204,
      73.8567,
      7
    ),

    TrainInfo(
      "12220",
      "Duronto Express",
      "Secunderabad",
      "Mumbai",
      "Hyderabad",
      "Vikarabad",
      "Running",
      17.3850,
      78.4867,
      3
    ),

    TrainInfo(
      "12952",
      "Mumbai Rajdhani",
      "Mumbai",
      "New Delhi",
      "Mumbai Central",
      "Surat",
      "Running",
      19.0760,
      72.8777,
      1
    ),

    TrainInfo(
      "12009",
      "Shatabdi Express",
      "Mumbai Central",
      "Ahmedabad",
      "Borivali",
      "Surat",
      "Running",
      19.2183,
      72.9781,
      5
    )
  )

  def search(query: String): Seq[TrainInfo] = {

    val q =
      query
        .trim
        .toUpperCase

    trains.filter { t =>

      t.number.toUpperCase.contains(q) ||
      t.name.toUpperCase.contains(q) ||
      t.source.toUpperCase.contains(q) ||
      t.destination.toUpperCase.contains(q) ||
      t.currentStation.toUpperCase.contains(q)
    }
  }
}

// ============================================================
// TRAIN MAP
// ============================================================

class TrainMapPanel extends JPanel {

  private var trainNumber = ""
  private var trainName = ""
  private var latitude = 19.0760
  private var longitude = 72.8777

  setDoubleBuffered(true)
  setBackground(AppConfig.Background)

  def updateTrain(
    number: String,
    name: String,
    lat: Double,
    lon: Double
  ): Unit = {

    trainNumber = number
    trainName = name
    latitude = lat
    longitude = lon

    repaint()
  }

  def clearTrain(): Unit = {

    trainNumber = ""
    trainName = ""

    repaint()
  }

  override def paintComponent(
    g: Graphics
  ): Unit = {

    super.paintComponent(g)

    val g2 =
      g.asInstanceOf[Graphics2D]

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

    val w = getWidth
    val h = getHeight

    // Background

    g2.setColor(
      new Color(235, 240, 246)
    )

    g2.fillRect(
      0,
      0,
      w,
      h
    )

    // Grid

    g2.setColor(
      new Color(220, 226, 234)
    )

    var x = 0

    while (x < w) {

      g2.drawLine(
        x,
        0,
        x,
        h
      )

      x += 40
    }

    var y = 0

    while (y < h) {

      g2.drawLine(
        0,
        y,
        w,
        y
      )

      y += 40
    }

    // Header

    g2.setColor(
      AppConfig.Dark
    )

    g2.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        20
      )
    )

    g2.drawString(
      "LIVE TRAIN LOCATION MAP",
      25,
      35
    )

    g2.setFont(
      new Font(
        "Arial",
        Font.PLAIN,
        12
      )
    )

    g2.setColor(
      AppConfig.Secondary
    )

    g2.drawString(
      "Train position visualization",
      25,
      55
    )

    // India-like map area

    val shape =
      new Path2D.Double()

    shape.moveTo(
      w * 0.34,
      h * 0.15
    )

    shape.lineTo(
      w * 0.52,
      h * 0.18
    )

    shape.lineTo(
      w * 0.68,
      h * 0.30
    )

    shape.lineTo(
      w * 0.72,
      h * 0.48
    )

    shape.lineTo(
      w * 0.60,
      h * 0.62
    )

    shape.lineTo(
      w * 0.48,
      h * 0.88
    )

    shape.lineTo(
      w * 0.38,
      h * 0.75
    )

    shape.lineTo(
      w * 0.25,
      h * 0.55
    )

    shape.lineTo(
      w * 0.28,
      h * 0.32
    )

    shape.closePath()

    g2.setColor(
      new Color(224, 238, 224)
    )

    g2.fill(shape)

    g2.setColor(
      new Color(100, 145, 105)
    )

    g2.setStroke(
      new BasicStroke(2.0f)
    )

    g2.draw(shape)

    // Railway route

    g2.setColor(
      new Color(52, 73, 94)
    )

    g2.setStroke(
      new BasicStroke(
        3.0f
      )
    )

    g2.drawLine(
      (w * 0.30).toInt,
      (h * 0.68).toInt,
      (w * 0.66).toInt,
      (h * 0.38).toInt
    )

    g2.drawLine(
      (w * 0.38).toInt,
      (h * 0.55).toInt,
      (w * 0.58).toInt,
      (h * 0.72).toInt
    )

    if (trainNumber.nonEmpty) {

      // Convert approximate coordinates to map position

      val minLat = 8.0
      val maxLat = 35.0

      val minLon = 68.0
      val maxLon = 90.0

      val px =
        120 +
        (((longitude - minLon) /
          (maxLon - minLon)) *
          (w - 240)).toInt

      val py =
        90 +
        (((maxLat - latitude) /
          (maxLat - minLat)) *
          (h - 170)).toInt

      // Glow

      g2.setColor(
        new Color(
          231,
          76,
          60,
          80
        )
      )

      g2.fillOval(
        px - 22,
        py - 22,
        44,
        44
      )

      // Train marker

      g2.setColor(
        AppConfig.Red
      )

      g2.fillOval(
        px - 10,
        py - 10,
        20,
        20
      )

      g2.setColor(
        Color.WHITE
      )

      g2.fillOval(
        px - 4,
        py - 4,
        8,
        8
      )

      // Label

      val label =
        trainNumber +
        " - " +
        trainName

      g2.setFont(
        new Font(
          "Arial",
          Font.BOLD,
          13
        )
      )

      val fm =
        g2.getFontMetrics

      val tw =
        fm.stringWidth(label)

      val bx =
        px + 15

      val by =
        py - 15

      g2.setColor(
        AppConfig.Dark
      )

      g2.fillRoundRect(
        bx,
        by,
        tw + 18,
        30,
        8,
        8
      )

      g2.setColor(
        Color.WHITE
      )

      g2.drawString(
        label,
        bx + 9,
        by + 20
      )
    }

    // Map status

    g2.setColor(
      Color.WHITE
    )

    g2.fillRoundRect(
      20,
      h - 65,
      320,
      42,
      8,
      8
    )

    g2.setColor(
      new Color(
        205,
        210,
        218
      )
    )

    g2.drawRoundRect(
      20,
      h - 65,
      320,
      42,
      8,
      8
    )

    g2.setColor(
      AppConfig.Green
    )

    g2.fillOval(
      34,
      h - 52,
      12,
      12
    )

    g2.setColor(
      AppConfig.Text
    )

    g2.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        12
      )
    )

    if (trainNumber.nonEmpty) {

      g2.drawString(
        "LIVE TRACKING ACTIVE",
        55,
        h - 37
      )

    } else {

      g2.drawString(
        "Select a train to display location",
        55,
        h - 37
      )
    }
  }
}

// ============================================================
// RTO MAP
// ============================================================

class RTOMapPanel extends JPanel {

  private var selectedCode = ""
  private var selectedName = ""

  setDoubleBuffered(true)

  def select(
    code: String,
    name: String
  ): Unit = {

    selectedCode = code
    selectedName = name

    repaint()
  }

  def clear(): Unit = {

    selectedCode = ""
    selectedName = ""

    repaint()
  }

  override def paintComponent(
    g: Graphics
  ): Unit = {

    super.paintComponent(g)

    val g2 =
      g.asInstanceOf[Graphics2D]

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

    val w = getWidth
    val h = getHeight

    g2.setColor(
      new Color(235, 240, 246)
    )

    g2.fillRect(
      0,
      0,
      w,
      h
    )

    g2.setColor(
      new Color(220, 226, 234)
    )

    var x = 0

    while (x < w) {

      g2.drawLine(
        x,
        0,
        x,
        h
      )

      x += 40
    }

    var y = 0

    while (y < h) {

      g2.drawLine(
        0,
        y,
        w,
        y
      )

      y += 40
    }

    g2.setColor(
      AppConfig.Dark
    )

    g2.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        20
      )
    )

    g2.drawString(
      "INDIA RTO MAP",
      25,
      35
    )

    val india =
      new Path2D.Double()

    india.moveTo(
      w * 0.35,
      h * 0.12
    )

    india.lineTo(
      w * 0.52,
      h * 0.18
    )

    india.lineTo(
      w * 0.70,
      h * 0.30
    )

    india.lineTo(
      w * 0.73,
      h * 0.48
    )

    india.lineTo(
      w * 0.61,
      h * 0.61
    )

    india.lineTo(
      w * 0.48,
      h * 0.90
    )

    india.lineTo(
      w * 0.39,
      h * 0.75
    )

    india.lineTo(
      w * 0.25,
      h * 0.55
    )

    india.lineTo(
      w * 0.28,
      h * 0.32
    )

    india.closePath()

    g2.setColor(
      new Color(226, 240, 225)
    )

    g2.fill(india)

    g2.setColor(
      new Color(90, 140, 95)
    )

    g2.draw(india)

    if (selectedCode.nonEmpty) {

      val seed =
        selectedCode.hashCode.abs

      val px =
        150 + (seed % math.max(1, w - 300))

      val py =
        100 + ((seed / 7) % math.max(1, h - 180))

      g2.setColor(
        new Color(
          231,
          76,
          60,
          90
        )
      )

      g2.fillOval(
        px - 18,
        py - 18,
        36,
        36
      )

      g2.setColor(
        AppConfig.Red
      )

      g2.fillOval(
        px - 8,
        py - 8,
        16,
        16
      )

      g2.setColor(
        Color.WHITE
      )

      g2.fillOval(
        px - 3,
        py - 3,
        6,
        6
      )

      g2.setColor(
        AppConfig.Dark
      )

      g2.setFont(
        new Font(
          "Arial",
          Font.BOLD,
          13
        )
      )

      g2.fillRoundRect(
        px + 14,
        py - 18,
        220,
        34,
        8,
        8
      )

      g2.setColor(
        Color.WHITE
      )

      g2.drawString(
        selectedCode +
        " : " +
        selectedName,
        px + 24,
        py + 4
      )
    }
  }
}

// ============================================================
// MAIN APPLICATION
// ============================================================

class RTOTrainApplication {

  private val frame =
    new JFrame(
      AppConfig.AppTitle
    )

  private val cards =
    new JPanel(
      new CardLayout()
    )

  private val searchField =
    new JTextField(15)

  private val trainSearchField =
    new JTextField(15)

  private val rtoOutput =
    new JTextArea()

  private val trainOutput =
    new JTextArea()

  private val status =
    new JLabel("Ready")

  private val rtoMap =
    new RTOMapPanel()

  private val trainMap =
    new TrainMapPanel()

  private var selectedTrain:
    Option[TrainInfo] =
    None

  private var movementStep =
    0

  private val trainTimer =
    new Timer(
      3000,
      (_: ActionEvent) =>
        updateTrainMovement()
    )

  // ==========================================================
  // START
  // ==========================================================

  def start(): Unit = {

    frame.setSize(
      AppConfig.Width,
      AppConfig.Height
    )

    frame.setLocationRelativeTo(null)

    frame.setDefaultCloseOperation(
      WindowConstants.EXIT_ON_CLOSE
    )

    showLogin()

    frame.setVisible(true)
  }

  // ==========================================================
  // LOGIN
  // ==========================================================

  private def showLogin(): Unit = {

    trainTimer.stop()

    val root =
      new JPanel(
        new BorderLayout()
      )

    root.setBackground(
      AppConfig.Background
    )

    val header =
      new JPanel(
        new FlowLayout(
          FlowLayout.CENTER,
          20,
          25
        )
      )

    header.setBackground(
      AppConfig.Dark
    )

    val title =
      new JLabel(
        "INDIA RTO & LIVE TRAIN MAP SUITE"
      )

    title.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        24
      )
    )

    title.setForeground(
      Color.WHITE
    )

    header.add(title)

    root.add(
      header,
      BorderLayout.NORTH
    )

    val center =
      new JPanel(
        new GridLayout(
          1,
          1
        )
      )

    center.setOpaque(false)

    val card =
      new JPanel()

    card.setLayout(
      new javax.swing.BoxLayout(
        card,
        javax.swing.BoxLayout.Y_AXIS
      )
    )

    card.setBackground(
      Color.WHITE
    )

    card.setBorder(
      BorderFactory.createCompoundBorder(
        new LineBorder(
          new Color(215, 222, 232),
          1,
          true
        ),
        new EmptyBorder(
          35,
          50,
          35,
          50
        )
      )
    )

    val lock =
      new JLabel(
        "SECURE LOGIN"
      )

    lock.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        26
      )
    )

    lock.setForeground(
      AppConfig.Dark
    )

    lock.setAlignmentX(
      Component.CENTER_ALIGNMENT
    )

    val info =
      new JLabel(
        "Enter password to continue"
      )

    info.setFont(
      new Font(
        "Arial",
        Font.PLAIN,
        14
      )
    )

    info.setForeground(
      AppConfig.Secondary
    )

    info.setAlignmentX(
      Component.CENTER_ALIGNMENT
    )

    val password =
      new JPasswordField(18)

    password.setFont(
      new Font(
        "Arial",
        Font.PLAIN,
        18
      )
    )

    password.setMaximumSize(
      new Dimension(
        300,
        42
      )
    )

    password.setPreferredSize(
      new Dimension(
        300,
        42
      )
    )

    password.setHorizontalAlignment(
      SwingConstants.CENTER
    )

    // Password is hidden

    password.setEchoChar('*')

    password.setAlignmentX(
      Component.CENTER_ALIGNMENT
    )

    val error =
      new JLabel(" ")

    error.setForeground(
      AppConfig.Red
    )

    error.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        13
      )
    )

    error.setAlignmentX(
      Component.CENTER_ALIGNMENT
    )

    val login =
      new JButton("LOGIN")

    styleButton(
      login,
      AppConfig.Blue,
      Color.WHITE
    )

    login.setMaximumSize(
      new Dimension(
        300,
        42
      )
    )

    login.setAlignmentX(
      Component.CENTER_ALIGNMENT
    )

    card.add(lock)

    card.add(
      javax.swing.Box.createVerticalStrut(12)
    )

    card.add(info)

    card.add(
      javax.swing.Box.createVerticalStrut(20)
    )

    card.add(password)

    card.add(
      javax.swing.Box.createVerticalStrut(8)
    )

    card.add(error)

    card.add(
      javax.swing.Box.createVerticalStrut(12)
    )

    card.add(login)

    center.add(card)

    root.add(
      center,
      BorderLayout.CENTER
    )

    def authenticate(): Unit = {

      val entered =
        new String(
          password.getPassword
        )

      if (
        AppConfig.checkPassword(entered)
      ) {

        password.setText("")

        showMainApplication()

      } else {

        error.setText(
          "Incorrect password"
        )

        password.setText("")

        password.requestFocus()
      }
    }

    login.addActionListener(
      (_: ActionEvent) =>
        authenticate()
    )

    password.addActionListener(
      (_: ActionEvent) =>
        authenticate()
    )

    frame.setContentPane(root)

    frame.revalidate()
    frame.repaint()

    password.requestFocus()
  }

  // ==========================================================
  // MAIN
  // ==========================================================

  private def showMainApplication(): Unit = {

    val root =
      new JPanel(
        new BorderLayout()
      )

    root.setBackground(
      AppConfig.Background
    )

    root.add(
      createTopBar(),
      BorderLayout.NORTH
    )

    cards.removeAll()

    cards.add(
      createRTOPage(),
      "RTO"
    )

    cards.add(
      createTrainPage(),
      "TRAIN"
    )

    root.add(
      cards,
      BorderLayout.CENTER
    )

    root.add(
      createStatusBar(),
      BorderLayout.SOUTH
    )

    frame.setContentPane(root)

    frame.revalidate()
    frame.repaint()

    showRTOPage()
  }

  // ==========================================================
  // TOP BAR
  // ==========================================================

  private def createTopBar(): JPanel = {

    val bar =
      new JPanel(
        new BorderLayout()
      )

    bar.setBackground(
      AppConfig.Dark
    )

    bar.setBorder(
      new EmptyBorder(
        10,
        18,
        10,
        18
      )
    )

    val brand =
      new JPanel(
        new GridLayout(
          2,
          1
        )
      )

    brand.setOpaque(false)

    val title =
      new JLabel(
        "ALL INDIA RTO & LIVE TRAIN SYSTEM"
      )

    title.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        19
      )
    )

    title.setForeground(
      Color.WHITE
    )

    val subtitle =
      new JLabel(
        "RTO Directory + Train Location Dashboard"
      )

    subtitle.setForeground(
      new Color(
        190,
        200,
        210
      )
    )

    brand.add(title)
    brand.add(subtitle)

    bar.add(
      brand,
      BorderLayout.WEST
    )

    val buttons =
      new JPanel(
        new FlowLayout(
          FlowLayout.RIGHT,
          7,
          5
        )
      )

    buttons.setOpaque(false)

    val rtoButton =
      new JButton(
        "RTO DIRECTORY"
      )

    val trainButton =
      new JButton(
        "LIVE TRAIN"
      )

    val logoutButton =
      new JButton(
        "LOGOUT"
      )

    styleButton(
      rtoButton,
      AppConfig.Blue,
      Color.WHITE
    )

    styleButton(
      trainButton,
      AppConfig.Green,
      Color.WHITE
    )

    styleButton(
      logoutButton,
      AppConfig.Red,
      Color.WHITE
    )

    buttons.add(rtoButton)
    buttons.add(trainButton)
    buttons.add(logoutButton)

    bar.add(
      buttons,
      BorderLayout.EAST
    )

    rtoButton.addActionListener(
      (_: ActionEvent) =>
        showRTOPage()
    )

    trainButton.addActionListener(
      (_: ActionEvent) =>
        showTrainPage()
    )

    logoutButton.addActionListener(
      (_: ActionEvent) =>
        showLogin()
    )

    bar
  }

  // ==========================================================
  // BUTTON STYLE
  // ==========================================================

  private def styleButton(
    button: JButton,
    background: Color,
    foreground: Color
  ): Unit = {

    button.setBackground(
      background
    )

    button.setForeground(
      foreground
    )

    button.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        12
      )
    )

    button.setFocusPainted(false)

    button.setBorder(
      new EmptyBorder(
        8,
        13,
        8,
        13
      )
    )

    button.setCursor(
      new Cursor(
        Cursor.HAND_CURSOR
      )
    )
  }

  // ==========================================================
  // RTO PAGE
  // ==========================================================

  private def createRTOPage(): JPanel = {

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

    page.setBackground(
      AppConfig.Background
    )

    page.setBorder(
      new EmptyBorder(
        12,
        15,
        12,
        15
      )
    )

    val searchPanel =
      new JPanel(
        new FlowLayout(
          FlowLayout.LEFT,
          8,
          5
        )
      )

    searchPanel.setBackground(
      Color.WHITE
    )

    val label =
      new JLabel(
        "Search RTO:"
      )

    label.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        14
      )
    )

    searchField.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        15
      )
    )

    searchField.setPreferredSize(
      new Dimension(
        180,
        36
      )
    )

    val search =
      new JButton(
        "SEARCH"
      )

    val clear =
      new JButton(
        "CLEAR"
      )

    styleButton(
      search,
      AppConfig.Blue,
      Color.WHITE
    )

    styleButton(
      clear,
      new Color(
        127,
        140,
        141
      ),
      Color.WHITE
    )

    searchPanel.add(label)
    searchPanel.add(searchField)
    searchPanel.add(search)
    searchPanel.add(clear)

    page.add(
      searchPanel,
      BorderLayout.NORTH
    )

    rtoOutput.setEditable(false)

    rtoOutput.setFont(
      new Font(
        "Monospaced",
        Font.PLAIN,
        13
      )
    )

    rtoOutput.setLineWrap(true)

    rtoOutput.setWrapStyleWord(true)

    val outputScroll =
      new JScrollPane(
        rtoOutput
      )

    val split =
      new JSplitPane(
        javax.swing.JSplitPane.HORIZONTAL_SPLIT,
        outputScroll,
        rtoMap
      )

    split.setResizeWeight(
      0.45
    )

    page.add(
      split,
      BorderLayout.CENTER
    )

    search.addActionListener(
      (_: ActionEvent) =>
        searchRTO()
    )

    searchField.addActionListener(
      (_: ActionEvent) =>
        searchRTO()
    )

    clear.addActionListener(
      (_: ActionEvent) => {

        searchField.setText("")

        rtoOutput.setText(
          "Enter RTO code or city.\n\n" +
          "Examples:\n" +
          "MH12\n" +
          "MH14\n" +
          "MH\n" +
          "Pune\n" +
          "GJ\n" +
          "DL01"
        )

        rtoMap.clear()
      }
    )

    rtoOutput.setText(
      "RTO DIRECTORY\n\n" +
      "Enter a State, RTO code or city.\n\n" +
      "Examples:\n" +
      "MH12 = Pune City\n" +
      "MH14 = Pimpri-Chinchwad\n" +
      "MH01 = Mumbai Central\n" +
      "GJ01 = Ahmedabad\n" +
      "KA01 = Bengaluru Central"
    )

    page
  }

  // ==========================================================
  // RTO SEARCH
  // ==========================================================

  private def searchRTO(): Unit = {

    val q =
      searchField.getText
        .trim
        .toUpperCase

    if (q.isEmpty) {

      status.setText(
        "Enter RTO code or city."
      )

      status.setForeground(
        AppConfig.Red
      )

      return
    }

    val exact =
      RTODatabase.all.get(q)

    exact match {

      case Some(name) =>

        rtoOutput.setText(
          "==================================================\n" +
          "                 RTO SEARCH RESULT\n" +
          "==================================================\n\n" +
          "CODE      : " +
          q +
          "\n" +
          "LOCATION  : " +
          name +
          "\n\n" +
          "Status    : Record Found\n" +
          "Map       : Location pinned\n\n" +
          "=================================================="
        )

        rtoMap.select(
          q,
          name
        )

        status.setText(
          "RTO Found: " +
          q +
          " -> " +
          name
        )

        status.setForeground(
          AppConfig.Green
        )

      case None =>

        val results =
          RTODatabase.find(q)

        if (results.nonEmpty) {

          val sb =
            new StringBuilder()

          sb.append(
            "PARTIAL SEARCH RESULTS\n\n"
          )

          results.foreach {
            case (code, name) =>

              sb.append(
                code +
                " -> " +
                name +
                "\n"
              )
          }

          rtoOutput.setText(
            sb.toString()
          )

          rtoMap.clear()

          status.setText(
            results.size +
            " RTO records found."
          )

          status.setForeground(
            AppConfig.Blue
          )

        } else {

          rtoOutput.setText(
            "No RTO record found for:\n\n" +
            q +
            "\n\nTry MH12, MH14, Pune, GJ01, DL01 etc."
          )

          rtoMap.clear()

          status.setText(
            "RTO not found."
          )

          status.setForeground(
            AppConfig.Red
          )
        }
    }
  }

  // ==========================================================
  // TRAIN PAGE
  // ==========================================================

  private def createTrainPage(): JPanel = {

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

    page.setBackground(
      AppConfig.Background
    )

    page.setBorder(
      new EmptyBorder(
        12,
        15,
        12,
        15
      )
    )

    val searchPanel =
      new JPanel(
        new FlowLayout(
          FlowLayout.LEFT,
          8,
          5
        )
      )

    searchPanel.setBackground(
      Color.WHITE
    )

    val label =
      new JLabel(
        "Train Number / Name:"
      )

    label.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        14
      )
    )

    trainSearchField.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        15
      )
    )

    trainSearchField.setPreferredSize(
      new Dimension(
        190,
        36
      )
    )

    val search =
      new JButton(
        "TRACK TRAIN"
      )

    val all =
      new JButton(
        "ALL TRAINS"
      )

    val stop =
      new JButton(
        "STOP LIVE"
      )

    styleButton(
      search,
      AppConfig.Green,
      Color.WHITE
    )

    styleButton(
      all,
      AppConfig.Blue,
      Color.WHITE
    )

    styleButton(
      stop,
      AppConfig.Red,
      Color.WHITE
    )

    searchPanel.add(label)
    searchPanel.add(trainSearchField)
    searchPanel.add(search)
    searchPanel.add(all)
    searchPanel.add(stop)

    page.add(
      searchPanel,
      BorderLayout.NORTH
    )

    trainOutput.setEditable(false)

    trainOutput.setFont(
      new Font(
        "Monospaced",
        Font.PLAIN,
        13
      )
    )

    trainOutput.setLineWrap(true)

    trainOutput.setWrapStyleWord(true)

    val outputScroll =
      new JScrollPane(
        trainOutput
      )

    val split =
      new JSplitPane(
        javax.swing.JSplitPane.HORIZONTAL_SPLIT,
        outputScroll,
        trainMap
      )

    split.setResizeWeight(
      0.42
    )

    page.add(
      split,
      BorderLayout.CENTER
    )

    search.addActionListener(
      (_: ActionEvent) =>
        trackTrain()
    )

    trainSearchField.addActionListener(
      (_: ActionEvent) =>
        trackTrain()
    )

    all.addActionListener(
      (_: ActionEvent) =>
        showAllTrains()
    )

    stop.addActionListener(
      (_: ActionEvent) => {

        trainTimer.stop()

        status.setText(
          "Live train simulation stopped."
        )

        status.setForeground(
          AppConfig.Red
        )
      }
    )

    trainOutput.setText(
      "LIVE TRAIN TRACKING\n\n" +
      "Enter train number or name.\n\n" +
      "Examples:\n" +
      "12110\n" +
      "12123\n" +
      "11010\n" +
      "Panchavati\n" +
      "Deccan Queen\n\n" +
      "Select a train and press TRACK TRAIN."
    )

    page
  }

  // ==========================================================
  // TRAIN SEARCH
  // ==========================================================

  private def trackTrain(): Unit = {

    val q =
      trainSearchField.getText.trim

    if (q.isEmpty) {

      status.setText(
        "Enter train number or name."
      )

      status.setForeground(
        AppConfig.Red
      )

      return
    }

    val results =
      TrainDatabase.search(q)

    if (results.nonEmpty) {

      val train =
        results.head

      selectedTrain =
        Some(train)

      movementStep = 0

      displayTrain(train)

      trainTimer.start()

      status.setText(
        "Tracking train " +
        train.number
      )

      status.setForeground(
        AppConfig.Green
      )

    } else {

      trainOutput.setText(
        "Train not found.\n\n" +
        "Available demo trains:\n\n" +
        TrainDatabase.trains
          .map(
            t =>
              t.number +
              " - " +
              t.name
          )
          .mkString("\n")
      )

      trainMap.clearTrain()

      trainTimer.stop()

      status.setText(
        "Train not found."
      )

      status.setForeground(
        AppConfig.Red
      )
    }
  }

  // ==========================================================
  // DISPLAY TRAIN
  // ==========================================================

  private def displayTrain(
    train: TrainInfo
  ): Unit = {

    val now =
      new SimpleDateFormat(
        "HH:mm:ss"
      ).format(
        new Date()
      )

    trainOutput.setText(
      "============================================================\n" +
      "                    LIVE TRAIN STATUS\n" +
      "============================================================\n\n" +
      "Train No       : " +
      train.number +
      "\n" +
      "Train Name     : " +
      train.name +
      "\n" +
      "Route          : " +
      train.source +
      " -> " +
      train.destination +
      "\n" +
      "Current        : " +
      train.currentStation +
      "\n" +
      "Next Station   : " +
      train.nextStation +
      "\n" +
      "Status         : " +
      train.status +
      "\n" +
      "Delay          : " +
      train.delayMinutes +
      " minutes\n" +
      "Latitude       : " +
      "%.5f".format(train.latitude) +
      "\n" +
      "Longitude      : " +
      "%.5f".format(train.longitude) +
      "\n" +
      "Updated        : " +
      now +
      "\n\n" +
      "------------------------------------------------------------\n" +
      "LIVE LOCATION SIMULATION ACTIVE\n" +
      "------------------------------------------------------------\n" +
      "Map marker automatically updates every 3 seconds.\n\n" +
      "Note: This application currently uses demo coordinates.\n" +
      "Connect a real railway tracking API for actual GPS data.\n" +
      "============================================================"
    )

    trainMap.updateTrain(
      train.number,
      train.name,
      train.latitude,
      train.longitude
    )
  }

  // ==========================================================
  // SIMULATED MOVEMENT
  // ==========================================================

  private def updateTrainMovement(): Unit = {

    selectedTrain match {

      case Some(original) =>

        movementStep += 1

        val latChange =
          movementStep * 0.002

        val lonChange =
          movementStep * 0.0015

        val moved =
          original.copy(
            latitude =
              original.latitude +
              latChange,
            longitude =
              original.longitude +
              lonChange
          )

        displayTrain(moved)

      case None =>
        trainTimer.stop()
    }
  }

  // ==========================================================
  // ALL TRAINS
  // ==========================================================

  private def showAllTrains(): Unit = {

    val sb =
      new StringBuilder()

    sb.append(
      "============================================================\n"
    )

    sb.append(
      "                  AVAILABLE TRAINS\n"
    )

    sb.append(
      "============================================================\n\n"
    )

    TrainDatabase.trains.foreach { t =>

      sb.append(
        t.number +
        "  |  " +
        t.name +
        "  |  " +
        t.source +
        " -> " +
        t.destination +
        "\n"
      )
    }

    sb.append(
      "\n============================================================"
    )

    trainOutput.setText(
      sb.toString()
    )

    trainTimer.stop()

    status.setText(
      TrainDatabase.trains.size +
      " demo trains available."
    )

    status.setForeground(
      AppConfig.Blue
    )
  }

  // ==========================================================
  // STATUS BAR
  // ==========================================================

  private def createStatusBar(): JPanel = {

    val bar =
      new JPanel(
        new BorderLayout()
      )

    bar.setBackground(
      Color.WHITE
    )

    bar.setBorder(
      new EmptyBorder(
        7,
        18,
        7,
        18
      )
    )

    status.setFont(
      new Font(
        "Arial",
        Font.BOLD,
        12
      )
    )

    bar.add(
      status,
      BorderLayout.WEST
    )

    val info =
      new JLabel(
        "RTO Records: " +
        RTODatabase.all.size +
        " | Demo Trains: " +
        TrainDatabase.trains.size
      )

    info.setFont(
      new Font(
        "Arial",
        Font.PLAIN,
        11
      )
    )

    info.setForeground(
      AppConfig.Secondary
    )

    bar.add(
      info,
      BorderLayout.EAST
    )

    bar
  }

  // ==========================================================
  // PAGE SWITCHING
  // ==========================================================

  private def showRTOPage(): Unit = {

    val layout =
      cards.getLayout
        .asInstanceOf[CardLayout]

    layout.show(
      cards,
      "RTO"
    )

    trainTimer.stop()

    status.setText(
      "RTO Directory active."
    )

    status.setForeground(
      AppConfig.Blue
    )
  }

  private def showTrainPage(): Unit = {

    val layout =
      cards.getLayout
        .asInstanceOf[CardLayout]

    layout.show(
      cards,
      "TRAIN"
    )

    status.setText(
      "Live Train page active."
    )

    status.setForeground(
      AppConfig.Green
    )
  }
}

// ============================================================
// APPLICATION START
// ============================================================

val application =
  new RTOTrainApplication()

application.start()