Code Sketch


graphic_7325* w
By: Mhalsakant School
Category: Art
import javax.swing._
import java.awt._
import java.awt.event._
import java.net.{URL, URLEncoder}
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import scala.io.Source
import scala.util.matching.Regex

// =====================================================
// 1. PASSWORD CHECK (SHA-256)
// =====================================================

def makeHash(text: String): String = {
  val md = MessageDigest.getInstance("SHA-256")
  val bytes = md.digest(text.getBytes(StandardCharsets.UTF_8))
  bytes.map("%02x".format(_)).mkString
}

// Password is hidden using character codes.
// Actual password: weather7313
val passwordChars = Array(
  119, 101, 97, 116, 104, 101, 114, 55, 51, 49, 51
)

val hiddenPassword =
  passwordChars.map(_.toChar).mkString

val correctPasswordHash =
  makeHash(hiddenPassword)

// =====================================================
// 2. LOGIN SCREEN
// =====================================================

val loginFrame = new JFrame("Weather App - Login")
loginFrame.setSize(450, 280)
loginFrame.setLayout(new BorderLayout())
loginFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
loginFrame.setLocationRelativeTo(null)

val loginPanel = new JPanel()
loginPanel.setLayout(new GridLayout(5, 1, 8, 8))

val loginTitle = new JLabel("LIVE WEATHER APP", SwingConstants.CENTER)
loginTitle.setFont(new Font("Arial", Font.BOLD, 22))

// Password is NOT displayed here
val passwordLabel = new JLabel("Enter Password", SwingConstants.CENTER)
passwordLabel.setFont(new Font("Arial", Font.PLAIN, 15))

val passwordBox = new JPasswordField()
passwordBox.setFont(new Font("Arial", Font.PLAIN, 18))

val loginButton = new JButton("LOGIN")
loginButton.setFont(new Font("Arial", Font.BOLD, 16))

val loginStatus = new JLabel("", SwingConstants.CENTER)
loginStatus.setForeground(Color.RED)

loginPanel.add(loginTitle)
loginPanel.add(passwordLabel)
loginPanel.add(passwordBox)
loginPanel.add(loginButton)
loginPanel.add(loginStatus)

loginFrame.add(loginPanel, BorderLayout.CENTER)

// =====================================================
// 3. MASTER PRE-DEFINED COORDINATES DATABASE
// =====================================================

val locationMap: Map[String, (String, String, String, String)] = Map(

  // ---------------------------------------------------
  // ALL INDIA STATES & UNION TERRITORIES
  // ---------------------------------------------------

  "andhra pradesh" ->
    ("16.5417", "80.5158", "Andhra Pradesh (Amaravati)", "India"),

  "arunachal pradesh" ->
    ("27.1004", "93.6166", "Arunachal Pradesh (Itanagar)", "India"),

  "assam" ->
    ("26.1445", "91.7362", "Assam (Guwahati)", "India"),

  "bihar" ->
    ("25.5941", "85.1376", "Bihar (Patna)", "India"),

  "chhattisgarh" ->
    ("21.2514", "81.6296", "Chhattisgarh (Raipur)", "India"),

  "goa" ->
    ("15.4909", "73.8278", "Goa (Panaji)", "India"),

  "gujarat" ->
    ("23.2156", "72.6369", "Gujarat (Gandhinagar)", "India"),

  "haryana" ->
    ("30.7333", "76.7794", "Haryana (Chandigarh)", "India"),

  "himachal pradesh" ->
    ("31.1048", "77.1734", "Himachal Pradesh (Shimla)", "India"),

  "jharkhand" ->
    ("23.3441", "85.3096", "Jharkhand (Ranchi)", "India"),

  "karnataka" ->
    ("12.9716", "77.5946", "Karnataka (Bengaluru)", "India"),

  "kerala" ->
    ("8.5241", "76.9366", "Kerala (Thiruvananthapuram)", "India"),

  "madhya pradesh" ->
    ("23.2599", "77.4126", "Madhya Pradesh (Bhopal)", "India"),

  "maharashtra" ->
    ("18.9388", "72.8354", "Maharashtra (Mumbai)", "India"),

  "manipur" ->
    ("24.8170", "93.9368", "Manipur (Imphal)", "India"),

  "meghalaya" ->
    ("25.5788", "91.8933", "Meghalaya (Shillong)", "India"),

  "mizoram" ->
    ("23.7271", "92.7176", "Mizoram (Aizawl)", "India"),

  "nagaland" ->
    ("25.6751", "94.1086", "Nagaland (Kohima)", "India"),

  "odisha" ->
    ("20.2961", "85.8245", "Odisha (Bhubaneswar)", "India"),

  "punjab" ->
    ("30.7333", "76.7794", "Punjab (Chandigarh)", "India"),

  "rajasthan" ->
    ("26.9124", "75.7873", "Rajasthan (Jaipur)", "India"),

  "sikkim" ->
    ("27.3389", "88.6065", "Sikkim (Gangtok)", "India"),

  "tamil nadu" ->
    ("13.0827", "80.2707", "Tamil Nadu (Chennai)", "India"),

  "telangana" ->
    ("17.3850", "78.4867", "Telangana (Hyderabad)", "India"),

  "tripura" ->
    ("23.8315", "91.2868", "Tripura (Agartala)", "India"),

  "uttar pradesh" ->
    ("26.8467", "80.9462", "Uttar Pradesh (Lucknow)", "India"),

  "uttarakhand" ->
    ("30.3165", "78.0322", "Uttarakhand (Dehradun)", "India"),

  "west bengal" ->
    ("22.5726", "88.3639", "West Bengal (Kolkata)", "India"),

  "delhi" ->
    ("28.6139", "77.2090", "Delhi (National Capital)", "India"),

  "jammu and kashmir" ->
    ("34.0837", "74.7973", "Jammu & Kashmir (Srinagar)", "India"),

  "ladakh" ->
    ("34.1526", "77.5771", "Ladakh (Leh)", "India"),

  "chandigarh" ->
    ("30.7333", "76.7794", "Chandigarh", "India"),

  "puducherry" ->
    ("11.9416", "79.8083", "Puducherry", "India"),

  "andaman and nicobar islands" ->
    ("11.6234", "92.7265", "Andaman & Nicobar (Port Blair)", "India"),

  "dadra and nagar haveli and daman and diu" ->
    ("20.4283", "72.8397", "Daman and Diu", "India"),

  "lakshadweep" ->
    ("10.5667", "72.6417", "Lakshadweep (Kavaratti)", "India"),

  // ---------------------------------------------------
  // MAHARASHTRA DISTRICTS
  // ---------------------------------------------------

  "ahmednagar" ->
    ("19.0952", "74.7496", "Ahilyanagar (Ahmednagar)", "Maharashtra"),

  "ahilyanagar" ->
    ("19.0952", "74.7496", "Ahilyanagar (Ahmednagar)", "Maharashtra"),

  "akola" ->
    ("20.7002", "77.0082", "Akola", "Maharashtra"),

  "amravati" ->
    ("20.9320", "77.7523", "Amravati", "Maharashtra"),

  "bhandara" ->
    ("21.1667", "79.6500", "Bhandara", "Maharashtra"),

  "beed" ->
    ("18.9891", "75.7601", "Beed", "Maharashtra"),

  "buldhana" ->
    ("20.5312", "76.1847", "Buldhana", "Maharashtra"),

  "chandrapur" ->
    ("19.9615", "79.2961", "Chandrapur", "Maharashtra"),

  "chhatrapati sambhajinagar" ->
    ("19.8762", "75.3433", "Chhatrapati Sambhajinagar", "Maharashtra"),

  "sambhajinagar" ->
    ("19.8762", "75.3433", "Chhatrapati Sambhajinagar", "Maharashtra"),

  "dharashiv" ->
    ("18.1853", "76.0419", "Dharashiv (Osmanabad)", "Maharashtra"),

  "dhule" ->
    ("20.9042", "74.7749", "Dhule", "Maharashtra"),

  "gadchiroli" ->
    ("20.1849", "79.9948", "Gadchiroli", "Maharashtra"),

  "gondia" ->
    ("21.4624", "80.1961", "Gondia", "Maharashtra"),

  "hingoli" ->
    ("19.7196", "77.1478", "Hingoli", "Maharashtra"),

  "jalgaon" ->
    ("21.0077", "75.5626", "Jalgaon", "Maharashtra"),

  "jalna" ->
    ("19.8410", "75.8864", "Jalna", "Maharashtra"),

  "kolhapur" ->
    ("16.7050", "74.2433", "Kolhapur", "Maharashtra"),

  "latur" ->
    ("18.4088", "76.5604", "Latur", "Maharashtra"),

  "mumbai city" ->
    ("18.9388", "72.8354", "Mumbai City", "Maharashtra"),

  "mumbai suburban" ->
    ("19.0760", "72.8777", "Mumbai Suburban", "Maharashtra"),

  "nagpur" ->
    ("21.1458", "79.0882", "Nagpur", "Maharashtra"),

  "nanded" ->
    ("19.1383", "77.3210", "Nanded", "Maharashtra"),

  "nandurbar" ->
    ("21.3697", "74.2417", "Nandurbar", "Maharashtra"),

  "nashik" ->
    ("19.9975", "73.7898", "Nashik", "Maharashtra"),

  "palghar" ->
    ("19.6967", "72.7655", "Palghar", "Maharashtra"),

  "parbhani" ->
    ("19.2611", "76.7767", "Parbhani", "Maharashtra"),

  "pune" ->
    ("18.5204", "73.8567", "Pune", "Maharashtra"),

  "raigad" ->
    ("18.6414", "72.8722", "Raigad (Alibag)", "Maharashtra"),

  "ratnagiri" ->
    ("16.9902", "73.3120", "Ratnagiri", "Maharashtra"),

  "sangli" ->
    ("16.8524", "74.5815", "Sangli", "Maharashtra"),

  "satara" ->
    ("17.6805", "74.0183", "Satara", "Maharashtra"),

  "sindhudurg" ->
    ("16.0354", "73.6892", "Sindhudurg (Kudal)", "Maharashtra"),

  "solapur" ->
    ("17.6599", "75.9064", "Solapur", "Maharashtra"),

  "thane" ->
    ("19.2183", "72.9781", "Thane", "Maharashtra"),

  "wardha" ->
    ("20.7453", "78.6022", "Wardha", "Maharashtra"),

  "washim" ->
    ("20.1114", "77.1352", "Washim", "Maharashtra"),

  "yavatmal" ->
    ("20.3888", "78.1204", "Yavatmal", "Maharashtra"),

  // ---------------------------------------------------
  // PUNE & PCMC
  // ---------------------------------------------------

  "akurdi" ->
    ("18.6475", "73.7711", "Akurdi (PCMC)", "Pune, Maharashtra"),

  "nigdi" ->
    ("18.6544", "73.7661", "Nigdi (PCMC)", "Pune, Maharashtra"),

  "bhosari" ->
    ("18.6275", "73.8447", "Bhosari (PCMC)", "Pune, Maharashtra"),

  "pimpri" ->
    ("18.6298", "73.7997", "Pimpri (PCMC)", "Pune, Maharashtra"),

  "chinchwad" ->
    ("18.6298", "73.7997", "Chinchwad (PCMC)", "Pune, Maharashtra"),

  "wakad" ->
    ("18.5987", "73.7684", "Wakad (PCMC)", "Pune, Maharashtra"),

  "hinjawadi" ->
    ("18.5913", "73.7389", "Hinjawadi IT Park", "Pune, Maharashtra"),

  "baner" ->
    ("18.5590", "73.7868", "Baner", "Pune, Maharashtra"),

  "kothrud" ->
    ("18.5074", "73.8077", "Kothrud", "Pune, Maharashtra"),

  "hadapsar" ->
    ("18.5089", "73.9259", "Hadapsar", "Pune, Maharashtra"),

  "katraj" ->
    ("18.4489", "73.8586", "Katraj", "Pune, Maharashtra"),

  "saswad" ->
    ("18.3439", "74.0294", "Saswad (Purandar)", "Pune, Maharashtra"),

  "purandar" ->
    ("18.3439", "74.0294", "Purandar (Saswad)", "Pune, Maharashtra"),

  "jejuri" ->
    ("18.2766", "74.1593", "Jejuri (Purandar)", "Pune, Maharashtra"),

  "bhor" ->
    ("18.1467", "73.8447", "Bhor", "Pune, Maharashtra"),

  "velhe" ->
    ("18.2970", "73.6358", "Velhe", "Pune, Maharashtra"),

  "panshet" ->
    ("18.3753", "73.6133", "Panshet (Velhe)", "Pune, Maharashtra"),

  "baramati" ->
    ("18.1517", "74.5770", "Baramati", "Pune, Maharashtra"),

  "indapur" ->
    ("18.1147", "75.0292", "Indapur", "Pune, Maharashtra"),

  "bhigwan" ->
    ("18.2917", "74.7708", "Bhigwan (Indapur)", "Pune, Maharashtra"),

  "daund" ->
    ("18.4633", "74.5828", "Daund", "Pune, Maharashtra"),

  "shirur" ->
    ("18.8278", "74.3778", "Shirur", "Pune, Maharashtra"),

  "shikrapur" ->
    ("18.7067", "74.1239", "Shikrapur (Shirur)", "Pune, Maharashtra"),

  "junnar" ->
    ("19.2067", "73.8767", "Junnar", "Pune, Maharashtra"),

  "narayangaon" ->
    ("19.1256", "73.9781", "Narayangaon (Junnar)", "Pune, Maharashtra"),

  "otur" ->
    ("19.2611", "73.9167", "Otur (Junnar)", "Pune, Maharashtra"),

  "khed" ->
    ("18.8500", "73.8833", "Khed (Rajgurunagar)", "Pune, Maharashtra"),

  "rajgurunagar" ->
    ("18.8500", "73.8833", "Rajgurunagar (Khed)", "Pune, Maharashtra"),

  "chakan" ->
    ("18.7606", "73.8576", "Chakan (Khed)", "Pune, Maharashtra"),

  "alandi" ->
    ("18.6775", "73.8967", "Alandi (Khed)", "Pune, Maharashtra"),

  "ambegaon" ->
    ("19.0003", "73.9388", "Ambegaon (Manchar)", "Pune, Maharashtra"),

  "manchar" ->
    ("19.0003", "73.9388", "Manchar (Ambegaon)", "Pune, Maharashtra"),

  "ghodegaon" ->
    ("19.0436", "73.8247", "Ghodegaon (Ambegaon)", "Pune, Maharashtra"),

  "maval" ->
    ("18.7557", "73.4091", "Maval", "Pune, Maharashtra"),

  "talegaon dabhade" ->
    ("18.7342", "73.6761", "Talegaon Dabhade (Maval)", "Pune, Maharashtra"),

  "lonavala" ->
    ("18.7557", "73.4091", "Lonavala (Maval)", "Pune, Maharashtra"),

  "khandala" ->
    ("18.7614", "73.3742", "Khandala (Maval)", "Pune, Maharashtra"),

  "dehu" ->
    ("18.7183", "73.7681", "Dehu (Haveli)", "Pune, Maharashtra"),

  "mulshi" ->
    ("18.5286", "73.5186", "Mulshi (Paud)", "Pune, Maharashtra"),

  "paud" ->
    ("18.5286", "73.6186", "Paud (Mulshi)", "Pune, Maharashtra"),

  "pirangut" ->
    ("18.5122", "73.6811", "Pirangut (Mulshi)", "Pune, Maharashtra"),

  "lavasa" ->
    ("18.4092", "73.5075", "Lavasa (Mulshi)", "Pune, Maharashtra"),

  "haveli" ->
    ("18.5204", "73.8567", "Haveli", "Pune, Maharashtra"),

  "khadakwasla" ->
    ("18.4358", "73.7661", "Khadakwasla (Haveli)", "Pune, Maharashtra"),

  "uruli kanchan" ->
    ("18.4842", "74.1331", "Uruli Kanchan (Haveli)", "Pune, Maharashtra"),

  "wagholi" ->
    ("18.5808", "73.9786", "Wagholi (Haveli)", "Pune, Maharashtra")
)

// =====================================================
// 4. MAIN WEATHER APP
// =====================================================

def startWeatherApp(): Unit = {

  loginFrame.setVisible(false)

  val frame =
    new JFrame("Live Weather App - India, Maharashtra, Pune & PCMC")

  frame.setSize(1120, 760)
  frame.setLayout(new BorderLayout())
  frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
  frame.setLocationRelativeTo(null)

  // ---------------------------------------------------
  // TOP PANEL
  // ---------------------------------------------------

  val mainTopPanel = new JPanel()
  mainTopPanel.setLayout(
    new BoxLayout(mainTopPanel, BoxLayout.Y_AXIS)
  )

  val searchInputPanel =
    new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 8))

  val cityBox = new JTextField(22)
  cityBox.setFont(new Font("Arial", Font.PLAIN, 16))

  val searchButton =
    new JButton("SEARCH WEATHER")

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

  val logoutButton =
    new JButton("LOGOUT")

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

  logoutButton.setBackground(
    new Color(220, 53, 69)
  )

  logoutButton.setForeground(Color.WHITE)
  logoutButton.setFocusPainted(false)

  searchInputPanel.add(
    new JLabel("Search Location:")
  )

  searchInputPanel.add(cityBox)
  searchInputPanel.add(searchButton)
  searchInputPanel.add(logoutButton)

  // ---------------------------------------------------
  // DROPDOWNS
  // ---------------------------------------------------

  val allIndiaStates = Array(
    "-- Select India State / UT --",
    "Andhra Pradesh",
    "Arunachal Pradesh",
    "Assam",
    "Bihar",
    "Chhattisgarh",
    "Goa",
    "Gujarat",
    "Haryana",
    "Himachal Pradesh",
    "Jharkhand",
    "Karnataka",
    "Kerala",
    "Madhya Pradesh",
    "Maharashtra",
    "Manipur",
    "Meghalaya",
    "Mizoram",
    "Nagaland",
    "Odisha",
    "Punjab",
    "Rajasthan",
    "Sikkim",
    "Tamil Nadu",
    "Telangana",
    "Tripura",
    "Uttar Pradesh",
    "Uttarakhand",
    "West Bengal",
    "Delhi",
    "Jammu and Kashmir",
    "Ladakh",
    "Chandigarh",
    "Puducherry",
    "Andaman and Nicobar Islands",
    "Dadra and Nagar Haveli and Daman and Diu",
    "Lakshadweep"
  )

  val maharashtraDistricts = Array(
    "-- Select Maharashtra District --",
    "Ahmednagar",
    "Akola",
    "Amravati",
    "Bhandara",
    "Beed",
    "Buldhana",
    "Chandrapur",
    "Chhatrapati Sambhajinagar",
    "Dharashiv",
    "Dhule",
    "Gadchiroli",
    "Gondia",
    "Hingoli",
    "Jalgaon",
    "Jalna",
    "Kolhapur",
    "Latur",
    "Mumbai City",
    "Mumbai Suburban",
    "Nagpur",
    "Nanded",
    "Nandurbar",
    "Nashik",
    "Palghar",
    "Parbhani",
    "Pune",
    "Raigad",
    "Ratnagiri",
    "Sangli",
    "Satara",
    "Sindhudurg",
    "Solapur",
    "Thane",
    "Wardha",
    "Washim",
    "Yavatmal"
  )

  val puneLocations = Array(
    "-- Select Pune & PCMC Area --",
    "Akurdi",
    "Nigdi",
    "Pimpri",
    "Chinchwad",
    "Bhosari",
    "Wakad",
    "Hinjawadi",
    "Saswad",
    "Purandar",
    "Jejuri",
    "Bhor",
    "Velhe",
    "Panshet",
    "Pune",
    "Kothrud",
    "Hadapsar",
    "Katraj",
    "Baner",
    "Haveli",
    "Khadakwasla",
    "Uruli Kanchan",
    "Wagholi",
    "Baramati",
    "Shirur",
    "Shikrapur",
    "Junnar",
    "Narayangaon",
    "Otur",
    "Khed",
    "Rajgurunagar",
    "Chakan",
    "Alandi",
    "Ambegaon",
    "Manchar",
    "Ghodegaon",
    "Maval",
    "Talegaon Dabhade",
    "Lonavala",
    "Khandala",
    "Dehu",
    "Mulshi",
    "Paud",
    "Pirangut",
    "Lavasa",
    "Daund",
    "Indapur",
    "Bhigwan"
  )

  val indiaDropdown =
    new JComboBox[String](allIndiaStates)

  val mahaDropdown =
    new JComboBox[String](maharashtraDistricts)

  val puneDropdown =
    new JComboBox[String](puneLocations)

  val quickSelectPanel =
    new JPanel(
      new FlowLayout(
        FlowLayout.CENTER,
        8,
        6
      )
    )

  quickSelectPanel.add(
    new JLabel("India States:")
  )

  quickSelectPanel.add(indiaDropdown)

  quickSelectPanel.add(
    new JLabel("Maharashtra (36 Districts):")
  )

  quickSelectPanel.add(mahaDropdown)

  quickSelectPanel.add(
    new JLabel("Pune & PCMC:")
  )

  quickSelectPanel.add(puneDropdown)

  mainTopPanel.add(searchInputPanel)
  mainTopPanel.add(quickSelectPanel)

  // ---------------------------------------------------
  // RESULT AREA
  // ---------------------------------------------------

  val result = new JTextArea()

  result.setFont(
    new Font(
      "Monospaced",
      Font.PLAIN,
      16
    )
  )

  result.setEditable(false)

  result.setMargin(
    new Insets(12, 12, 12, 12)
  )

  val scroll =
    new JScrollPane(result)

  // ---------------------------------------------------
  // HISTORY
  // ---------------------------------------------------

  val historyPanel =
    new JPanel(new BorderLayout())

  val historyTitle =
    new JLabel(
      "SEARCH HISTORY",
      SwingConstants.CENTER
    )

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

  val historyList =
    new DefaultListModel[String]()

  val history =
    new JList[String](historyList)

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

  val clearHistoryButton =
    new JButton("CLEAR HISTORY")

  historyPanel.add(
    historyTitle,
    BorderLayout.NORTH
  )

  historyPanel.add(
    new JScrollPane(history),
    BorderLayout.CENTER
  )

  historyPanel.add(
    clearHistoryButton,
    BorderLayout.SOUTH
  )

  historyPanel.setPreferredSize(
    new Dimension(210, 0)
  )

  // ---------------------------------------------------
  // STATUS
  // ---------------------------------------------------

  val status =
    new JLabel(
      " Ready. Akurdi, Nigdi, PCMC, Pune & Maharashtra districts loaded.",
      SwingConstants.LEFT
    )

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

  frame.add(
    mainTopPanel,
    BorderLayout.NORTH
  )

  frame.add(
    scroll,
    BorderLayout.CENTER
  )

  frame.add(
    historyPanel,
    BorderLayout.EAST
  )

  frame.add(
    status,
    BorderLayout.SOUTH
  )

  // ===================================================
  // JSON HELPERS
  // ===================================================

  def getNumber(
      json: String,
      key: String
  ): String = {

    val pattern =
      (
        "\"" +
        Regex.quote(key) +
        "\"\\s*:\\s*(-?[0-9]+(?:\\.[0-9]+)?)"
      ).r

    pattern.findFirstMatchIn(json) match {

      case Some(m) =>
        m.group(1)

      case None =>
        "0.0"
    }
  }

  def getString(
      json: String,
      key: String
  ): String = {

    val pattern =
      (
        "\"" +
        Regex.quote(key) +
        "\"\\s*:\\s*\"([^\"]*)\""
      ).r

    pattern.findFirstMatchIn(json) match {

      case Some(m) =>
        m.group(1)

      case None =>
        "N/A"
    }
  }

  // ===================================================
  // WEATHER CONDITION
  // ===================================================

  def weatherCondition(
      code: String
  ): (String, String) = {

    code match {

      case "0" =>
        ("[SUN]", "Clear Sky")

      case "1" | "2" =>
        ("[PARTLY CLOUDY]", "Partly Cloudy")

      case "3" =>
        ("[CLOUD]", "Overcast / Cloudy")

      case "45" | "48" =>
        ("[FOG]", "Foggy")

      case "51" | "53" | "55" =>
        ("[DRIZZLE]", "Drizzle")

      case "61" | "63" | "65" =>
        ("[RAIN]", "Rain")

      case "71" | "73" | "75" =>
        ("[SNOW]", "Snow")

      case "80" | "81" | "82" =>
        ("[SHOWERS]", "Rain Showers")

      case "95" =>
        ("[STORM]", "Thunderstorm")

      case "96" | "99" =>
        ("[HAIL]", "Hailstorm")

      case _ =>
        ("[WEATHER]", "Normal / Moderate")
    }
  }

  // ===================================================
  // ONLINE GEOCODING
  // ===================================================

  def fetchOnlineCoordinates(
      query: String
  ): (String, String, String, String) = {

    try {

      val encoded =
        URLEncoder.encode(
          query,
          StandardCharsets.UTF_8.toString
        )

      val url =
        s"https://geocoding-api.open-meteo.com/v1/search" +
        s"?name=$encoded" +
        s"&count=1" +
        s"&language=en" +
        s"&format=json" +
        s"&countryCode=IN"

      val source =
        Source.fromURL(
          new URL(url),
          "UTF-8"
        )

      val json =
        try source.mkString
        finally source.close()

      val lat =
        getNumber(
          json,
          "latitude"
        )

      val lon =
        getNumber(
          json,
          "longitude"
        )

      val name =
        getString(
          json,
          "name"
        )

      val admin =
        getString(
          json,
          "admin1"
        )

      if (
        lat == "0.0" ||
        lon == "0.0"
      ) {

        ("N/A", "N/A", "N/A", "N/A")

      } else {

        (
          lat,
          lon,
          name,
          admin
        )
      }

    } catch {

      case _: Exception =>
        ("N/A", "N/A", "N/A", "N/A")
    }
  }

  // ===================================================
  // SEARCH ENGINE
  // ===================================================

  def searchWeather(
      targetPlace: String
  ): Unit = {

    val raw =
      targetPlace.trim

    if (
      raw.isEmpty ||
      raw.startsWith("--")
    ) {

      JOptionPane.showMessageDialog(
        frame,
        "Please enter or select a valid place.",
        "Input Required",
        JOptionPane.WARNING_MESSAGE
      )

      return
    }

    result.setText(
      s"Searching weather details for '$raw'..."
    )

    status.setText(
      s"Fetching live data for $raw..."
    )

    searchButton.setEnabled(false)

    val worker =
      new Thread(
        new Runnable {

          override def run(): Unit = {

            try {

              val cleanKey =
                raw.toLowerCase.trim

              var lat = "N/A"
              var lon = "N/A"

              var displayName =
                raw

              var regionName =
                "India"

              // -----------------------------------------
              // PREDEFINED LOCATION
              // -----------------------------------------

              if (
                locationMap.contains(cleanKey)
              ) {

                val data =
                  locationMap(cleanKey)

                lat = data._1
                lon = data._2
                displayName = data._3
                regionName = data._4

              } else {

                // ---------------------------------------
                // ONLINE SEARCH
                // ---------------------------------------

                var online =
                  fetchOnlineCoordinates(raw)

                if (
                  online._1 == "N/A"
                ) {

                  online =
                    fetchOnlineCoordinates(
                      s"$raw, PCMC"
                    )
                }

                if (
                  online._1 == "N/A"
                ) {

                  online =
                    fetchOnlineCoordinates(
                      s"$raw, Pune"
                    )
                }

                if (
                  online._1 == "N/A"
                ) {

                  online =
                    fetchOnlineCoordinates(
                      s"$raw, Maharashtra"
                    )
                }

                if (
                  online._1 != "N/A"
                ) {

                  lat = online._1
                  lon = online._2

                  displayName =
                    s"$raw (${online._3})"

                  regionName =
                    if (
                      online._4 != "N/A"
                    ) {

                      s"${online._4}, India"

                    } else {

                      "India"
                    }
                }
              }

              // -----------------------------------------
              // LOCATION NOT FOUND
              // -----------------------------------------

              if (
                lat == "N/A" ||
                lon == "N/A"
              ) {

                SwingUtilities.invokeLater(
                  new Runnable {

                    override def run(): Unit = {

                      result.setText(
                        s"LOCATION NOT FOUND: '$raw'\n\n" +
                        "Please choose directly from the dropdowns above."
                      )

                      status.setText(
                        "Location not found"
                      )

                      searchButton.setEnabled(true)
                    }
                  }
                )

                return
              }

              // -----------------------------------------
              // WEATHER API
              // -----------------------------------------

              val weatherUrl =
                s"https://api.open-meteo.com/v1/forecast" +
                s"?latitude=$lat" +
                s"&longitude=$lon" +
                s"&current=" +
                s"temperature_2m," +
                s"relative_humidity_2m," +
                s"rain," +
                s"weather_code," +
                s"wind_speed_10m" +
                s"&temperature_unit=celsius" +
                s"&wind_speed_unit=kmh" +
                s"&timezone=auto"

              val weatherSource =
                Source.fromURL(
                  new URL(weatherUrl),
                  "UTF-8"
                )

              val weatherJson =
                try weatherSource.mkString
                finally weatherSource.close()

              val temp =
                getNumber(
                  weatherJson,
                  "temperature_2m"
                )

              val humidity =
                getNumber(
                  weatherJson,
                  "relative_humidity_2m"
                )

              val rain =
                getNumber(
                  weatherJson,
                  "rain"
                )

              val wind =
                getNumber(
                  weatherJson,
                  "wind_speed_10m"
                )

              val code =
                getNumber(
                  weatherJson,
                  "weather_code"
                )

              val time =
                getString(
                  weatherJson,
                  "time"
                )

              val (
                symbol,
                condition
              ) =
                weatherCondition(code)

              // -----------------------------------------
              // DISPLAY RESULT
              // -----------------------------------------

              SwingUtilities.invokeLater(
                new Runnable {

                  override def run(): Unit = {

                    if (
                      !historyList.contains(displayName)
                    ) {

                      historyList.addElement(
                        displayName
                      )
                    }

                    val report =
                      s"""=====================================================
                         |                LIVE WEATHER REPORT
                         |=====================================================
                         | Status       : $symbol $condition
                         | Location     : $displayName
                         | State/Region : $regionName
                         | Coordinates  : Lat $lat, Lon $lon
                         |-----------------------------------------------------
                         | Temperature  : $temp �C
                         | Humidity     : $humidity %
                         | Rain         : $rain mm
                         | Wind Speed   : $wind km/h
                         |-----------------------------------------------------
                         | Last Updated : $time
                         | Live API     : Open-Meteo Weather Service
                         |====================================================="""
                        .stripMargin

                    result.setText(
                      report
                    )

                    status.setText(
                      s"Weather loaded successfully for $displayName"
                    )

                    searchButton.setEnabled(true)
                  }
                }
              )

            } catch {

              case _: Exception =>

                SwingUtilities.invokeLater(
                  new Runnable {

                    override def run(): Unit = {

                      result.setText(
                        "INTERNET / SERVER ERROR\n\n" +
                        "Please check your internet connection and try again."
                      )

                      status.setText(
                        "Network error"
                      )

                      searchButton.setEnabled(true)
                    }
                  }
                )
            }
          }
        }
      )

    worker.start()
  }

  // ===================================================
  // LOGOUT ACTION
  // ===================================================

  logoutButton.addActionListener(
    new ActionListener {

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

        val confirm =
          JOptionPane.showConfirmDialog(
            frame,
            "Are you sure you want to logout?",
            "Confirm Logout",
            JOptionPane.YES_NO_OPTION
          )

        if (
          confirm == JOptionPane.YES_OPTION
        ) {

          frame.dispose()

          passwordBox.setText("")

          loginStatus.setText(
            "Logged out successfully!"
          )

          loginStatus.setForeground(
            new Color(0, 128, 0)
          )

          loginFrame.setVisible(true)

          passwordBox.requestFocus()
        }
      }
    }
  )

  // ===================================================
  // SEARCH BUTTON
  // ===================================================

  searchButton.addActionListener(
    new ActionListener {

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

        searchWeather(
          cityBox.getText
        )
      }
    }
  )

  // ===================================================
  // ENTER KEY SEARCH
  // ===================================================

  cityBox.addActionListener(
    new ActionListener {

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

        searchWeather(
          cityBox.getText
        )
      }
    }
  )

  // ===================================================
  // INDIA DROPDOWN
  // ===================================================

  indiaDropdown.addActionListener(
    new ActionListener {

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

        val item =
          indiaDropdown
            .getSelectedItem
            .toString

        if (
          !item.startsWith("--")
        ) {

          cityBox.setText(item)

          searchWeather(item)
        }
      }
    }
  )

  // ===================================================
  // MAHARASHTRA DROPDOWN
  // ===================================================

  mahaDropdown.addActionListener(
    new ActionListener {

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

        val item =
          mahaDropdown
            .getSelectedItem
            .toString

        if (
          !item.startsWith("--")
        ) {

          cityBox.setText(item)

          searchWeather(item)
        }
      }
    }
  )

  // ===================================================
  // PUNE / PCMC DROPDOWN
  // ===================================================

  puneDropdown.addActionListener(
    new ActionListener {

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

        val item =
          puneDropdown
            .getSelectedItem
            .toString

        if (
          !item.startsWith("--")
        ) {

          cityBox.setText(item)

          searchWeather(item)
        }
      }
    }
  )

  // ===================================================
  // HISTORY CLICK
  // ===================================================

  history.addMouseListener(
    new MouseAdapter {

      override def mouseClicked(
          e: MouseEvent
      ): Unit = {

        val item =
          history.getSelectedValue

        if (
          item != null &&
          !item.isEmpty
        ) {

          val cleanName =
            item
              .split("\\(")(0)
              .trim

          cityBox.setText(
            cleanName
          )

          searchWeather(
            cleanName
          )
        }
      }
    }
  )

  // ===================================================
  // CLEAR HISTORY
  // ===================================================

  clearHistoryButton.addActionListener(
    new ActionListener {

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

        historyList.clear()

        status.setText(
          "History cleared"
        )
      }
    }
  )

  // ===================================================
  // SHOW WEATHER WINDOW
  // ===================================================

  frame.setVisible(true)

  cityBox.requestFocus()
}

// =====================================================
// 5. LOGIN ACTIONS
// =====================================================

def checkPassword(): Unit = {

  val entered =
    new String(
      passwordBox.getPassword
    )

  if (
    makeHash(entered) ==
    correctPasswordHash
  ) {

    loginStatus.setText("")

    startWeatherApp()

  } else {

    loginStatus.setForeground(
      Color.RED
    )

    // Password is NOT shown in error message
    loginStatus.setText(
      "Wrong password!"
    )

    passwordBox.setText("")
  }
}

// =====================================================
// LOGIN BUTTON
// =====================================================

loginButton.addActionListener(
  new ActionListener {

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

      checkPassword()
    }
  }
)

// =====================================================
// PASSWORD ENTER KEY
// =====================================================

passwordBox.addActionListener(
  new ActionListener {

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

      checkPassword()
    }
  }
)

// =====================================================
// START LOGIN SCREEN
// =====================================================

loginFrame.setVisible(true)
passwordBox.requestFocus()