Code Sketch


chakra rashi yoiiiiiiiiiiiiiiiii
By: Mhalsakant School
Category: Programming
import javax.swing._
import javax.swing.border.EmptyBorder
import java.awt._
import java.awt.event._
import java.text.DecimalFormat
import scala.math._

/*
  ================================================================
  KUNDALI PURAN - VEDIC KUNDALI GUI
  ---------------------------------------------------------------
  Standalone Scala Swing application.
  Default:
    Shailesh Shahare
    09/04/1973
    07:30 AM
    Ambedkar Ward No. 3, Sadak Arjuni, Gondia, Maharashtra
    IST +05:30

  IMPORTANT:
  This standalone version uses astronomical approximation formulas.
  Professional ephemeris accuracy requires Swiss Ephemeris or
  another dedicated ephemeris engine.
  ================================================================
*/

class KundaliPuranFrame extends JFrame {

  // ==============================================================
  // COLORS
  // ==============================================================

  private val bg = new Color(14, 18, 30)
  private val panelBg = new Color(23, 29, 46)
  private val panel2 = new Color(30, 38, 60)
  private val gold = new Color(245, 190, 70)
  private val gold2 = new Color(255, 215, 120)
  private val white = new Color(242, 245, 250)
  private val muted = new Color(175, 185, 205)
  private val green = new Color(100, 220, 150)
  private val red = new Color(240, 105, 105)
  private val blue = new Color(110, 180, 255)

  private val df = new DecimalFormat("0.000")
  private val df2 = new DecimalFormat("0.00")

  // ==============================================================
  // INPUT FIELDS
  // ==============================================================

  private val firstName = new JTextField("Shailesh", 12)
  private val lastName = new JTextField("Shahare", 12)

  private val day = new JTextField("09", 4)
  private val month = new JTextField("04", 4)
  private val year = new JTextField("1973", 6)

  private val hour = new JTextField("07", 4)
  private val minute = new JTextField("30", 4)

  private val ampm = new JComboBox[String](
    Array("AM", "PM")
  )

  private val place = new JTextField(
    "Ambedkar Ward No. 3, Sadak Arjuni, Gondia, Maharashtra",
    25
  )

  private val latitude = new JTextField("21.090017", 10)
  private val longitude = new JTextField("80.163083", 10)
  private val timezone = new JTextField("5.5", 6)

  private val calculateButton = new JButton("  ?  CALCULATE KUNDALI  ?  ")
  private val clearButton = new JButton("Clear")

  private val statusLabel = new JLabel("Ready")
  private val resultArea = new JTextArea()

  // ==============================================================
  // SIGN DATA
  // ==============================================================

  private val signs = Array(
    "Mesha (Aries)",
    "Vrishabha (Taurus)",
    "Mithuna (Gemini)",
    "Karka (Cancer)",
    "Simha (Leo)",
    "Kanya (Virgo)",
    "Tula (Libra)",
    "Vrishchika (Scorpio)",
    "Dhanu (Sagittarius)",
    "Makara (Capricorn)",
    "Kumbha (Aquarius)",
    "Meena (Pisces)"
  )

  private val signShort = Array(
    "Mesha",
    "Vrishabha",
    "Mithuna",
    "Karka",
    "Simha",
    "Kanya",
    "Tula",
    "Vrishchika",
    "Dhanu",
    "Makara",
    "Kumbha",
    "Meena"
  )

  private val nakshatras = Array(
    "Ashwini",
    "Bharani",
    "Krittika",
    "Rohini",
    "Mrigashira",
    "Ardra",
    "Punarvasu",
    "Pushya",
    "Ashlesha",
    "Magha",
    "Purva Phalguni",
    "Uttara Phalguni",
    "Hasta",
    "Chitra",
    "Swati",
    "Vishakha",
    "Anuradha",
    "Jyeshtha",
    "Mula",
    "Purva Ashadha",
    "Uttara Ashadha",
    "Shravana",
    "Dhanishtha",
    "Shatabhisha",
    "Purva Bhadrapada",
    "Uttara Bhadrapada",
    "Revati"
  )

  private val nakLords = Array(
    "Ketu",
    "Venus",
    "Sun",
    "Moon",
    "Mars",
    "Rahu",
    "Jupiter",
    "Saturn",
    "Mercury",
    "Ketu",
    "Venus",
    "Sun",
    "Moon",
    "Mars",
    "Rahu",
    "Jupiter",
    "Saturn",
    "Mercury",
    "Ketu",
    "Venus",
    "Sun",
    "Moon",
    "Mars",
    "Rahu",
    "Jupiter",
    "Saturn",
    "Mercury"
  )

  // ==============================================================
  // PLANET NAMES
  // ==============================================================

  private val planetNames = Array(
    "Sun",
    "Moon",
    "Mars",
    "Mercury",
    "Jupiter",
    "Venus",
    "Saturn",
    "Rahu",
    "Ketu"
  )

  // ==============================================================
  // SETUP
  // ==============================================================

  setTitle("Kundali Puran - Vedic Kundali")
  setSize(1180, 820)
  setMinimumSize(new Dimension(1000, 700))
  setLocationRelativeTo(null)
  setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)

  private val root = new JPanel(new BorderLayout(10, 10))
  root.setBackground(bg)
  root.setBorder(new EmptyBorder(12, 12, 12, 12))

  setContentPane(root)

  buildGUI()

  // ==============================================================
  // GUI
  // ==============================================================

  private def buildGUI(): Unit = {

    val header = new JPanel(new BorderLayout())
    header.setBackground(panelBg)
    header.setBorder(new EmptyBorder(14, 18, 14, 18))

    val title = new JLabel("?  KUNDALI PURAN  ?")
    title.setForeground(gold2)
    title.setFont(new Font("Serif", Font.BOLD, 28))

    val subtitle = new JLabel(
      "Vedic / Sidereal ? Lahiri-style calculation ? Birth Chart"
    )
    subtitle.setForeground(muted)
    subtitle.setFont(new Font("SansSerif", Font.PLAIN, 13))

    val titleBox = new JPanel()
    titleBox.setOpaque(false)
    titleBox.setLayout(new BoxLayout(titleBox, BoxLayout.Y_AXIS))
    titleBox.add(title)
    titleBox.add(Box.createVerticalStrut(4))
    titleBox.add(subtitle)

    header.add(titleBox, BorderLayout.WEST)

    root.add(header, BorderLayout.NORTH)

    val center = new JPanel(new BorderLayout(10, 10))
    center.setOpaque(false)

    val inputPanel = createInputPanel()
    center.add(inputPanel, BorderLayout.NORTH)

    resultArea.setEditable(false)
    resultArea.setFont(new Font("Monospaced", Font.PLAIN, 13))
    resultArea.setForeground(white)
    resultArea.setBackground(new Color(9, 12, 21))
    resultArea.setCaretColor(white)
    resultArea.setLineWrap(false)

    val scroll = new JScrollPane(resultArea)
    scroll.setBorder(BorderFactory.createLineBorder(new Color(65, 75, 100)))
    center.add(scroll, BorderLayout.CENTER)

    root.add(center, BorderLayout.CENTER)

    val bottom = new JPanel(new BorderLayout())
    bottom.setBackground(panelBg)
    bottom.setBorder(new EmptyBorder(8, 12, 8, 12))

    statusLabel.setForeground(green)
    statusLabel.setFont(new Font("SansSerif", Font.BOLD, 12))

    bottom.add(statusLabel, BorderLayout.WEST)

    val note = new JLabel(
      "  Vedic/Sidereal ? IST ? Lahiri-style ? Standalone astronomical approximation"
    )
    note.setForeground(muted)
    note.setFont(new Font("SansSerif", Font.PLAIN, 11))

    bottom.add(note, BorderLayout.EAST)

    root.add(bottom, BorderLayout.SOUTH)

    calculateButton.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        calculate()
      }
    })

    clearButton.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        resultArea.setText("")
        statusLabel.setText("Cleared")
        statusLabel.setForeground(muted)
      }
    })

    // Press Enter to calculate.
    firstName.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = calculate()
    })

    // Default AM.
    ampm.setSelectedItem("AM")

    // Calculate automatically after GUI opens.
    SwingUtilities.invokeLater(new Runnable {
      override def run(): Unit = calculate()
    })
  }

  private def createInputPanel(): JPanel = {

    val outer = new JPanel(new BorderLayout())
    outer.setBackground(panelBg)
    outer.setBorder(new EmptyBorder(12, 12, 12, 12))

    val grid = new JPanel(new GridBagLayout())
    grid.setOpaque(false)

    val c = new GridBagConstraints()
    c.insets = new Insets(5, 5, 5, 5)
    c.fill = GridBagConstraints.HORIZONTAL
    c.anchor = GridBagConstraints.WEST

    addLabel(grid, c, "First Name", 0, 0)
    addField(grid, c, firstName, 1, 0)

    addLabel(grid, c, "Surname", 2, 0)
    addField(grid, c, lastName, 3, 0)

    addLabel(grid, c, "Date", 0, 1)

    val datePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 2, 0))
    datePanel.setOpaque(false)
    datePanel.add(day)
    datePanel.add(new JLabel("/"))
    datePanel.add(month)
    datePanel.add(new JLabel("/"))
    datePanel.add(year)

    styleField(day)
    styleField(month)
    styleField(year)

    addComponent(grid, c, datePanel, 1, 1)

    addLabel(grid, c, "Time", 2, 1)

    val timePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 2, 0))
    timePanel.setOpaque(false)
    timePanel.add(hour)
    timePanel.add(new JLabel(":"))
    timePanel.add(minute)
    timePanel.add(ampm)

    styleField(hour)
    styleField(minute)
    styleCombo(ampm)

    addComponent(grid, c, timePanel, 3, 1)

    addLabel(grid, c, "Birth Place", 0, 2)
    addField(grid, c, place, 1, 2)
    c.gridwidth = 3
    c.weightx = 1.0
    grid.add(place, position(c, 1, 2, 3, 1))
    c.gridwidth = 1

    addLabel(grid, c, "Latitude", 0, 3)
    addField(grid, c, latitude, 1, 3)

    addLabel(grid, c, "Longitude", 2, 3)
    addField(grid, c, longitude, 3, 3)

    addLabel(grid, c, "Timezone", 0, 4)
    addField(grid, c, timezone, 1, 4)

    val systemLabel = new JLabel(
      "Calculation: Vedic / Sidereal ? Lahiri-style ? North Indian convention"
    )
    systemLabel.setForeground(gold2)
    systemLabel.setFont(new Font("SansSerif", Font.BOLD, 12))

    c.gridx = 2
    c.gridy = 4
    c.gridwidth = 2
    grid.add(systemLabel, c)
    c.gridwidth = 1

    val buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 2))
    buttonPanel.setOpaque(false)

    calculateButton.setBackground(new Color(80, 60, 20))
    calculateButton.setForeground(gold2)
    calculateButton.setFont(new Font("SansSerif", Font.BOLD, 13))
    calculateButton.setFocusPainted(false)

    clearButton.setBackground(panel2)
    clearButton.setForeground(white)
    clearButton.setFocusPainted(false)

    buttonPanel.add(clearButton)
    buttonPanel.add(calculateButton)

    outer.add(grid, BorderLayout.CENTER)
    outer.add(buttonPanel, BorderLayout.SOUTH)

    outer
  }

  private def addLabel(
    panel: JPanel,
    c: GridBagConstraints,
    text: String,
    x: Int,
    y: Int
  ): Unit = {

    val l = new JLabel(text)
    l.setForeground(muted)
    l.setFont(new Font("SansSerif", Font.BOLD, 12))

    panel.add(l, position(c, x, y, 1, 1))
  }

  private def addField(
    panel: JPanel,
    c: GridBagConstraints,
    field: JTextField,
    x: Int,
    y: Int
  ): Unit = {

    styleField(field)
    panel.add(field, position(c, x, y, 1, 1))
  }

  private def addComponent(
    panel: JPanel,
    c: GridBagConstraints,
    component: Component,
    x: Int,
    y: Int
  ): Unit = {
    panel.add(component, position(c, x, y, 1, 1))
  }

  private def position(
    c0: GridBagConstraints,
    x: Int,
    y: Int,
    w: Int,
    h: Int
  ): GridBagConstraints = {

    val c = c0.clone().asInstanceOf[GridBagConstraints]
    c.gridx = x
    c.gridy = y
    c.gridwidth = w
    c.gridheight = h
    c.weightx = if (w > 1) 1.0 else 0.0
    c.weighty = 0.0
    c.fill = GridBagConstraints.HORIZONTAL
    c
  }

  private def styleField(f: JTextField): Unit = {
    f.setBackground(new Color(11, 15, 27))
    f.setForeground(white)
    f.setCaretColor(white)
    f.setFont(new Font("SansSerif", Font.PLAIN, 12))
    f.setBorder(
      BorderFactory.createCompoundBorder(
        BorderFactory.createLineBorder(new Color(65, 75, 100)),
        new EmptyBorder(5, 7, 5, 7)
      )
    )
  }

  private def styleCombo(c: JComboBox[String]): Unit = {
    c.setBackground(new Color(11, 15, 27))
    c.setForeground(white)
    c.setFont(new Font("SansSerif", Font.PLAIN, 12))
  }

  // ==============================================================
  // SAFE PARSING
  // ==============================================================

  private def readInt(f: JTextField, name: String): Int = {
    try {
      f.getText.trim.toInt
    } catch {
      case _: Throwable =>
        throw new IllegalArgumentException(
          "Invalid " + name + "."
        )
    }
  }

  private def readDouble(f: JTextField, name: String): Double = {
    try {
      f.getText.trim.toDouble
    } catch {
      case _: Throwable =>
        throw new IllegalArgumentException(
          "Invalid " + name + "."
        )
    }
  }

  // ==============================================================
  // MAIN CALCULATION
  // ==============================================================

  private def calculate(): Unit = {

    try {

      statusLabel.setText("Calculating...")
      statusLabel.setForeground(gold2)

      val d = readInt(day, "day")
      val m = readInt(month, "month")
      val y = readInt(year, "year")

      val h0 = readInt(hour, "hour")
      val min = readInt(minute, "minute")

      val lat = readDouble(latitude, "latitude")
      val lon = readDouble(longitude, "longitude")
      val tz = readDouble(timezone, "timezone")

      if (d < 1 || d > 31)
        throw new IllegalArgumentException("Day must be 1-31.")

      if (m < 1 || m > 12)
        throw new IllegalArgumentException("Month must be 1-12.")

      if (h0 < 1 || h0 > 12)
        throw new IllegalArgumentException("Hour must be 1-12.")

      if (min < 0 || min > 59)
        throw new IllegalArgumentException("Minute must be 0-59.")

      if (lat < -90 || lat > 90)
        throw new IllegalArgumentException("Latitude must be between -90 and +90.")

      if (lon < -180 || lon > 180)
        throw new IllegalArgumentException("Longitude must be between -180 and +180.")

      val isPM = ampm.getSelectedItem.toString == "PM"

      var hour24 = h0 % 12
      if (isPM) hour24 += 12

      val localDecimalHour =
        hour24.toDouble + min.toDouble / 60.0

      val utcDecimalHour =
        localDecimalHour - tz

      val jd =
        julianDay(y, m, d, utcDecimalHour)

      val days =
        jd - 2451543.5

      // ------------------------------------------------------------
      // Planetary longitudes
      // ------------------------------------------------------------

      val tropicalSun = sunLongitude(days)
      val tropicalMoon = moonLongitude(days)

      val tropicalMercury =
        planetLongitude(days, 48.3313, 3.24587, 0.387098,
          7.0047, 0.00000005,
          29.1241, 1.01444E-5,
          0.387098, 0.0,
          168.0, 4.0923344368)

      val tropicalVenus =
        planetLongitude(days, 76.6799, 2.46590E-5, 0.72333,
          3.3946, 2.75E-8,
          54.8910, 1.38374E-5,
          0.72333, 0.0,
          48.0052, 1.6021302244)

      val tropicalMars =
        planetLongitude(days, 49.5574, 2.11081E-5, 1.523688,
          1.8497, 1.78E-8,
          286.5016, 2.92961E-5,
          1.523688, 0.0,
          18.6021, 0.5240207766)

      val tropicalJupiter =
        planetLongitude(days, 100.4542, 2.76854E-5, 5.20256,
          1.3030, -1.557E-7,
          273.8777, 1.64505E-5,
          5.20256, 0.0,
          19.8950, 0.0830853001)

      val tropicalSaturn =
        planetLongitude(days, 113.6634, 2.38980E-5, 9.55475,
          2.4886, -1.081E-7,
          339.3939, 2.97661E-5,
          9.55475, 0.0,
          316.9670, 0.0334442282)

      val tropicalRahu =
        normalize360(125.04452 - 0.0529538083 * days)

      val tropicalKetu =
        normalize360(tropicalRahu + 180.0)

      // ------------------------------------------------------------
      // Approximate Lahiri ayanamsha
      // ------------------------------------------------------------

      val ayanamsha = lahiriAyanamsha(jd)

      val sun = sidereal(tropicalSun, ayanamsha)
      val moon = sidereal(tropicalMoon, ayanamsha)
      val mercury = sidereal(tropicalMercury, ayanamsha)
      val venus = sidereal(tropicalVenus, ayanamsha)
      val mars = sidereal(tropicalMars, ayanamsha)
      val jupiter = sidereal(tropicalJupiter, ayanamsha)
      val saturn = sidereal(tropicalSaturn, ayanamsha)
      val rahu = sidereal(tropicalRahu, ayanamsha)
      val ketu = sidereal(tropicalKetu, ayanamsha)

      // ------------------------------------------------------------
      // Ascendant
      // ------------------------------------------------------------

      val lst = localSiderealTime(jd, lon)
      val asc = calculateAscendant(
        jd,
        lat,
        lst,
        ayanamsha
      )

      // ------------------------------------------------------------
      // Moon Rashi / Nakshatra
      // ------------------------------------------------------------

      val moonInfo = zodiacInfo(moon)
      val ascInfo = zodiacInfo(asc)

      // ------------------------------------------------------------
      // D9
      // ------------------------------------------------------------

      val navMoon = navamshaSign(moon)
      val navAsc = navamshaSign(asc)

      // ------------------------------------------------------------
      // Vimshottari
      // ------------------------------------------------------------

      val moonNakIndex =
        floor(normalize360(moon) / (360.0 / 27.0)).toInt

      val moonNakName =
        nakshatras(clamp(moonNakIndex, 0, 26))

      val moonNakLord =
        nakLords(clamp(moonNakIndex, 0, 26))

      val dashaReport =
        vimshottariReport(
          moon,
          moonNakIndex
        )

      // ------------------------------------------------------------
      // Planet table
      // ------------------------------------------------------------

      val planets = Array(
        ("Surya / Sun", sun),
        ("Chandra / Moon", moon),
        ("Mangal / Mars", mars),
        ("Budha / Mercury", mercury),
        ("Guru / Jupiter", jupiter),
        ("Shukra / Venus", venus),
        ("Shani / Saturn", saturn),
        ("Rahu", rahu),
        ("Ketu", ketu)
      )

      val sb = new StringBuilder

      sb.append("\n")
      sb.append("??????????????????????????????????????????????????????????????\n")
      sb.append("                    KUNDALI PURAN\n")
      sb.append("??????????????????????????????????????????????????????????????\n")
      sb.append("                     ???? ??????\n")
      sb.append("??????????????????????????????????????????????????????????????\n\n")

      sb.append("PERSONAL INFORMATION\n")
      sb.append("??????????????????????????????????????????????????????????????\n")
      sb.append("Name          : ")
      sb.append(firstName.getText.trim)
      sb.append(" ")
      sb.append(lastName.getText.trim)
      sb.append("\n")

      sb.append("Birth Date    : ")
      sb.append(f"$d%02d/$m%02d/$y%04d")
      sb.append("\n")

      sb.append("Birth Time    : ")
      sb.append(f"$h0%02d:$min%02d ${ampm.getSelectedItem}")
      sb.append("\n")

      sb.append("Birth Place   : ")
      sb.append(place.getText.trim)
      sb.append("\n")

      sb.append("Latitude      : ")
      sb.append(df2.format(lat))
      sb.append("\n")

      sb.append("Longitude     : ")
      sb.append(df2.format(lon))
      sb.append("\n")

      sb.append("Timezone      : UTC")
      sb.append(if (tz >= 0) "+" else "")
      sb.append(df2.format(tz))
      sb.append("\n")

      sb.append("Julian Day    : ")
      sb.append(df.format(jd))
      sb.append("\n")

      sb.append("\n")
      sb.append("??????????????????????????????????????????????????????????????\n")
      sb.append("                    MAIN RESULT\n")
      sb.append("??????????????????????????????????????????????????????????????\n")

      sb.append("CHANDRA RASHI  : ")
      sb.append(moonInfo.sign)
      sb.append("\n")

      sb.append("Moon Degree    : ")
      sb.append(formatDMS(moon))
      sb.append("\n")

      sb.append("NAKSHATRA      : ")
      sb.append(moonInfo.nakshatra)
      sb.append("\n")

      sb.append("NAKSHATRA PADA : ")
      sb.append(moonInfo.pada)
      sb.append("\n")

      sb.append("NAKSHATRA LORD : ")
      sb.append(moonInfo.nakLord)
      sb.append("\n")

      sb.append("LAGNA          : ")
      sb.append(ascInfo.sign)
      sb.append("\n")

      sb.append("Lagna Degree   : ")
      sb.append(formatDMS(asc))
      sb.append("\n")

      sb.append("LAGNA NAKSH.   : ")
      sb.append(ascInfo.nakshatra)
      sb.append(" Pada ")
      sb.append(ascInfo.pada)
      sb.append("\n")

      sb.append("AYANAMSHA      : ")
      sb.append(df2.format(ayanamsha))
      sb.append("�  (Lahiri-style approximation)")
      sb.append("\n")

      sb.append("\n")
      sb.append("??????????????????????????????????????????????????????????????\n")
      sb.append("                    PLANETARY POSITIONS\n")
      sb.append("??????????????????????????????????????????????????????????????\n")

      sb.append(
        String.format(
          "%-20s %-20s %-17s %-8s\n",
          "Planet",
          "Sidereal Sign",
          "Degree",
          "House"
        )
      )

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

      planets.foreach {
        case (name, lon0) =>

          val info = zodiacInfo(lon0)
          val house = houseFromAsc(asc, lon0)

          sb.append(
            String.format(
              "%-20s %-20s %-17s %-8s\n",
              name,
              info.sign,
              formatDMS(lon0),
              house.toString
            )
          )
      }

      sb.append("\n")
      sb.append("??????????????????????????????????????????????????????????????\n")
      sb.append("                     NAKSHATRA DETAILS\n")
      sb.append("??????????????????????????????????????????????????????????????\n")

      sb.append("Moon Nakshatra : ")
      sb.append(moonInfo.nakshatra)
      sb.append("\n")

      sb.append("Pada           : ")
      sb.append(moonInfo.pada)
      sb.append("\n")

      sb.append("Nakshatra Lord : ")
      sb.append(moonInfo.nakLord)
      sb.append("\n")

      sb.append("Moon Position  : ")
      sb.append(formatDMS(moon))
      sb.append("\n")

      sb.append("\n")
      sb.append("??????????????????????????????????????????????????????????????\n")
      sb.append("                     NAVAMSHA / D9\n")
      sb.append("??????????????????????????????????????????????????????????????\n")

      sb.append("D9 Lagna       : ")
      sb.append(navAsc)
      sb.append("\n")

      sb.append("D9 Moon        : ")
      sb.append(navMoon)
      sb.append("\n")

      sb.append("\n")
      sb.append("??????????????????????????????????????????????????????????????\n")
      sb.append("                     12 HOUSE OVERVIEW\n")
      sb.append("??????????????????????????????????????????????????????????????\n")

      for (i <- 1 to 12) {
        val houseSign =
          signShort(
            ((signIndex(asc) + i - 1) % 12 + 12) % 12
          )

        val occupantNames =
          planets
            .filter {
              case (_, p) =>
                houseFromAsc(asc, p) == i
            }
            .map(_._1)
            .mkString(", ")

        sb.append(
          f"House $i%2d  : ${houseSign}%-14s"
        )

        if (occupantNames.isEmpty)
          sb.append("  No listed planet")
        else
          sb.append("  " + occupantNames)

        sb.append("\n")
      }

      sb.append("\n")
      sb.append("??????????????????????????????????????????????????????????????\n")
      sb.append("                    VIMSHOTTARI DASHA\n")
      sb.append("??????????????????????????????????????????????????????????????\n")

      sb.append(dashaReport)

      sb.append("\n")
      sb.append("??????????????????????????????????????????????????????????????\n")
      sb.append("                     SPIRITUAL SECTION\n")
      sb.append("??????????????????????????????????????????????????????????????\n")

      sb.append("Moon Rashi       : ")
      sb.append(moonInfo.sign)
      sb.append("\n")

      sb.append("Nakshatra        : ")
      sb.append(moonInfo.nakshatra)
      sb.append("\n")

      sb.append("Nakshatra Lord   : ")
      sb.append(moonInfo.nakLord)
      sb.append("\n")

      sb.append("\n")
      sb.append("Traditional deity associations can vary by lineage,\n")
      sb.append("sampradaya and astrologer's method. This program does\n")
      sb.append("not claim that one particular deity is objectively\n")
      sb.append("assigned by astronomy.\n")

      sb.append("\n")
      sb.append("??????????????????????????????????????????????????????????????\n")
      sb.append("                         IMPORTANT\n")
      sb.append("??????????????????????????????????????????????????????????????\n")

      sb.append(
        "This is a standalone astronomical approximation.\n"
      )

      sb.append(
        "For professional-grade exact planetary positions,\n"
      )

      sb.append(
        "use a dedicated ephemeris such as Swiss Ephemeris.\n"
      )

      sb.append(
        "Small differences in planetary degrees, Lagna,\n"
      )

      sb.append(
        "Nakshatra or Dasha timing can occur between methods.\n"
      )

      sb.append(
        "Astrological interpretations are traditional beliefs,\n"
      )

      sb.append(
        "not scientifically established predictions.\n"
      )

      resultArea.setText(sb.toString())
      resultArea.setCaretPosition(0)

      statusLabel.setText(
        "? Calculation complete ? " + moonInfo.sign
      )
      statusLabel.setForeground(green)

    } catch {
      case ex: Throwable =>
        statusLabel.setText("Error")
        statusLabel.setForeground(red)

        JOptionPane.showMessageDialog(
          this,
          ex.getMessage,
          "Input Error",
          JOptionPane.ERROR_MESSAGE
        )
    }
  }

  // ==============================================================
  // JULIAN DAY
  // ==============================================================

  private def julianDay(
    year: Int,
    month: Int,
    day: Int,
    hour: Double
  ): Double = {

    var y = year
    var m = month

    if (m <= 2) {
      y -= 1
      m += 12
    }

    val a = floor(y / 100.0)
    val b = 2.0 - a + floor(a / 4.0)

    floor(365.25 * (y + 4716.0)) +
      floor(30.6001 * (m + 1.0)) +
      day +
      b -
      1524.5 +
      hour / 24.0
  }

  // ==============================================================
  // BASIC ASTRONOMICAL FUNCTIONS
  // ==============================================================

  private def rev(x: Double): Double = {
    var r = x % 360.0
    if (r < 0) r += 360.0
    r
  }

  private def normalize360(x: Double): Double = rev(x)

  private def sinD(x: Double): Double =
    sin(toRadians(x))

  private def cosD(x: Double): Double =
    cos(toRadians(x))

  private def tanD(x: Double): Double =
    tan(toRadians(x))

  private def atan2D(y: Double, x: Double): Double =
    toDegrees(atan2(y, x))

  // ==============================================================
  // SUN
  // ==============================================================

  private def sunLongitude(days: Double): Double = {

    val w =
      normalize360(282.9404 + 4.70935E-5 * days)

    val e =
      0.016709 - 1.151E-9 * days

    val m =
      normalize360(356.0470 + 0.9856002585 * days)

    val mr = toRadians(m)

    val E =
      m +
        toDegrees(
          e * sin(mr) *
            (1.0 + e * cos(mr))
        )

    val x =
      cosD(E) - e

    val y =
      sqrt(1.0 - e * e) * sinD(E)

    val v =
      atan2D(y, x)

    normalize360(v + w)
  }

  // ==============================================================
  // MOON
  // ==============================================================
  // Compact low-order lunar approximation.
  // Intended mainly for determining the zodiac region and
  // nakshatra-level result, not professional ephemeris work.

  private def moonLongitude(days: Double): Double = {

    val N =
      normalize360(125.1228 - 0.0529538083 * days)

    val i = 5.1454

    val w =
      normalize360(318.0634 + 0.1643573223 * days)

    val a = 60.2666

    val e = 0.054900

    val M =
      normalize360(115.3654 + 13.0649929509 * days)

    val E =
      eccentricAnomaly(M, e)

    val xv =
      a * (cosD(E) - e)

    val yv =
      a *
        (sqrt(1.0 - e * e) * sinD(E))

    val v =
      atan2D(yv, xv)

    val r =
      sqrt(xv * xv + yv * yv)

    val xh =
      r *
        (cosD(N) * cosD(v + w) -
          sinD(N) * sinD(v + w) * cosD(i))

    val yh =
      r *
        (sinD(N) * cosD(v + w) +
          cosD(N) * sinD(v + w) * cosD(i))

    val zh =
      r *
        (sinD(v + w) * sinD(i))

    var lon =
      atan2D(yh, xh)

    // Solar perturbations improve the rough lunar position.
    val sunLon = sunLongitude(days)

    val Ms =
      normalize360(
        356.0470 + 0.9856002585 * days
      )

    val Ls =
      normalize360(
        sunLon
      )

    val Lm =
      normalize360(
        N + w + M
      )

    val D =
      normalize360(Lm - Ls)

    val F =
      normalize360(Lm - N)

    lon +=
      -1.274 * sinD(M - 2.0 * D) +
      0.658 * sinD(2.0 * D) -
      0.186 * sinD(Ms) -
      0.059 * sinD(2.0 * M - 2.0 * D) -
      0.057 * sinD(M - 2.0 * D + Ms) +
      0.053 * sinD(M + 2.0 * D) +
      0.046 * sinD(2.0 * D - Ms) +
      0.041 * sinD(M - Ms) -
      0.035 * sinD(D) -
      0.031 * sinD(M + Ms) -
      0.015 * sinD(2.0 * F - 2.0 * D) +
      0.011 * sinD(M - 4.0 * D)

    normalize360(lon)
  }

  private def eccentricAnomaly(
    Mdeg: Double,
    e: Double
  ): Double = {

    val M = toRadians(Mdeg)

    var E = M

    var i = 0

    while (i < 10) {

      val delta =
        (E - e * sin(E) - M) /
          (1.0 - e * cos(E))

      E -= delta

      i += 1
    }

    toDegrees(E)
  }

  // ==============================================================
  // GENERIC PLANET APPROXIMATION
  // ==============================================================

  private def planetLongitude(
    days: Double,
    N0: Double,
    Nd: Double,
    a0: Double,
    i0: Double,
    id: Double,
    w0: Double,
    wd: Double,
    aDummy: Double,
    eDummy: Double,
    M0: Double,
    Md: Double
  ): Double = {

    val N = normalize360(N0 + Nd * days)
    val i = i0 + id * days
    val w = normalize360(w0 + wd * days)
    val a = a0

    val e =
      if (a < 1.0) 0.03
      else if (a < 2.0) 0.09
      else if (a < 7.0) 0.05
      else 0.05

    val M =
      normalize360(M0 + Md * days)

    val E =
      eccentricAnomaly(M, e)

    val xv =
      a * (cosD(E) - e)

    val yv =
      a *
        sqrt(1.0 - e * e) *
        sinD(E)

    val v =
      atan2D(yv, xv)

    val r =
      sqrt(xv * xv + yv * yv)

    val xh =
      r *
        (cosD(N) * cosD(v + w) -
          sinD(N) * sinD(v + w) * cosD(i))

    val yh =
      r *
        (sinD(N) * cosD(v + w) +
          cosD(N) * sinD(v + w) * cosD(i))

    normalize360(
      atan2D(yh, xh)
    )
  }

  // ==============================================================
  // LAHIRI-STYLE AYANAMSHA
  // ==============================================================

  private def lahiriAyanamsha(jd: Double): Double = {

    val T =
      (jd - 2451545.0) / 36525.0

    // Compact approximation around modern epochs.
    // Used only as a standalone approximation.
    normalize360(
      23.85675 +
        1.39656 * T +
        0.000308 * T * T
    )
  }

  private def sidereal(
    tropical: Double,
    ayan: Double
  ): Double = {

    normalize360(
      tropical - ayan
    )
  }

  // ==============================================================
  // SIDEREAL TIME
  // ==============================================================

  private def localSiderealTime(
    jd: Double,
    longitude: Double
  ): Double = {

    val T =
      (jd - 2451545.0) / 36525.0

    val gmst =
      280.46061837 +
        360.98564736629 *
          (jd - 2451545.0) +
        0.000387933 * T * T -
        T * T * T / 38710000.0

    normalize360(
      gmst + longitude
    )
  }

  // ==============================================================
  // ASCENDANT
  // ==============================================================

  private def calculateAscendant(
    jd: Double,
    latitude: Double,
    lst: Double,
    ayanamsha: Double
  ): Double = {

    val T =
      (jd - 2451545.0) / 36525.0

    val eps =
      23.439291 -
        0.0130042 * T

    val theta =
      toRadians(lst)

    val phi =
      toRadians(latitude)

    val e =
      toRadians(eps)

    val y =
      -cos(theta)

    val x =
      sin(theta) * cos(e) +
        tan(phi) * sin(e)

    var ascTropical =
      atan2D(y, x)

    ascTropical =
      normalize360(
        ascTropical
      )

    normalize360(
      ascTropical - ayanamsha
    )
  }

  // ==============================================================
  // ZODIAC INFORMATION
  // ==============================================================

  case class ZodiacInfo(
    sign: String,
    nakshatra: String,
    pada: Int,
    nakLord: String,
    degreeInSign: Double
  )

  private def zodiacInfo(lon: Double): ZodiacInfo = {

    val x = normalize360(lon)

    val signIndex =
      floor(x / 30.0).toInt

    val degreeInSign =
      x - signIndex * 30.0

    val nakSize =
      360.0 / 27.0

    val nakIndex =
      floor(x / nakSize).toInt

    val safeNak =
      clamp(nakIndex, 0, 26)

    val insideNak =
      x - safeNak * nakSize

    val pada =
      clamp(
        floor(
          insideNak /
            (nakSize / 4.0)
        ).toInt + 1,
        1,
        4
      )

    ZodiacInfo(
      signs(
        clamp(signIndex, 0, 11)
      ),
      nakshatras(safeNak),
      pada,
      nakLords(safeNak),
      degreeInSign
    )
  }

  private def signIndex(lon: Double): Int =
    floor(
      normalize360(lon) / 30.0
    ).toInt % 12

  private def clamp(
    x: Int,
    lo: Int,
    hi: Int
  ): Int = {
    max(lo, min(hi, x))
  }

  // ==============================================================
  // HOUSE
  // ==============================================================

  private def houseFromAsc(
    asc: Double,
    planet: Double
  ): Int = {

    val diff =
      normalize360(
        planet - asc
      )

    floor(diff / 30.0).toInt + 1
  }

  // ==============================================================
  // NAVAMSHA
  // ==============================================================

  private def navamshaSign(
    longitude: Double
  ): String = {

    val sign =
      signIndex(longitude)

    val degree =
      normalize360(longitude) -
        sign * 30.0

    val nav =
      floor(
        degree / (30.0 / 9.0)
      ).toInt

    // Movable: starts from same sign
    // Fixed: starts from 9th sign
    // Dual: starts from 5th sign

    val start =
      if (
        sign == 0 ||
        sign == 3 ||
        sign == 6 ||
        sign == 9
      ) {
        sign
      } else if (
        sign == 1 ||
        sign == 4 ||
        sign == 7 ||
        sign == 10
      ) {
        (sign + 8) % 12
      } else {
        (sign + 4) % 12
      }

    val n =
      (start + nav) % 12

    signShort(n)
  }

  // ==============================================================
  // VIMSHOTTARI
  // ==============================================================

  private val dashaYears =
    Map(
      "Ketu" -> 7.0,
      "Venus" -> 20.0,
      "Sun" -> 6.0,
      "Moon" -> 10.0,
      "Mars" -> 7.0,
      "Rahu" -> 18.0,
      "Jupiter" -> 16.0,
      "Saturn" -> 19.0,
      "Mercury" -> 17.0
    )

  private val dashaOrder =
    Array(
      "Ketu",
      "Venus",
      "Sun",
      "Moon",
      "Mars",
      "Rahu",
      "Jupiter",
      "Saturn",
      "Mercury"
    )

  private def vimshottariReport(
    moon: Double,
    nakIndex: Int
  ): String = {

    val lord =
      nakLords(
        clamp(nakIndex, 0, 26)
      )

    val totalNak =
      360.0 / 27.0

    val nakStart =
      nakIndex * totalNak

    val travelled =
      normalize360(moon) - nakStart

    val fraction =
      max(
        0.0,
        min(
          1.0,
          travelled / totalNak
        )
      )

    val lordYears =
      dashaYears(lord)

    val remainingYears =
      lordYears * (1.0 - fraction)

    val sb = new StringBuilder

    sb.append(
      "Starting Mahadasha : "
    )

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

    sb.append(
      "Approx. balance    : "
    )

    sb.append(
      df2.format(remainingYears)
    )

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

    sb.append(
      "Mahadasha sequence:\n"
    )

    val startIndex =
      dashaOrder.indexOf(lord)

    for (i <- 0 until 9) {

      val name =
        dashaOrder(
          (startIndex + i) %
            dashaOrder.length
        )

      sb.append(
        f"${i + 1}%2d. ${name}%-10s "
      )

      sb.append(
        df2.format(
          dashaYears(name)
        )
      )

      sb.append(" years\n")
    }

    sb.append("\n")
    sb.append(
      "Note: exact calendar dates for Mahadasha/\n"
    )
    sb.append(
      "Antardasha require a high-precision ephemeris\n"
    )
    sb.append(
      "and exact Moon longitude.\n"
    )

    sb.toString
  }

  // ==============================================================
  // DEGREE FORMAT
  // ==============================================================

  private def formatDMS(
    longitude: Double
  ): String = {

    val x =
      normalize360(longitude)

    val sign =
      floor(x / 30.0).toInt

    val deg =
      x - sign * 30.0

    val d =
      floor(deg).toInt

    val minutesFloat =
      (deg - d) * 60.0

    val min =
      floor(minutesFloat).toInt

    val sec =
      floor(
        (minutesFloat - min) * 60.0
      ).toInt

    f"${signShort(sign)} $d%02d� $min%02d' $sec%02d\""
  }
}


// ================================================================
// START APPLICATION
// ================================================================
// Direct startup is intentional so it works in worksheet/Kojo-like
// environments where object Main.main is not automatically invoked.

SwingUtilities.invokeLater(
  new Runnable {
    override def run(): Unit = {
      val frame =
        new KundaliPuranFrame()

      frame.setVisible(true)
    }
  }
)