content: /*
   ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
   ~                                                                               ~
   ~ The MIT License (MIT)                                                         ~
   ~                                                                               ~
   ~ Copyright (c) 2015-2025 miaixz.org and other contributors.                    ~
   ~                                                                               ~
   ~ Permission is hereby granted, free of charge, to any person obtaining a copy  ~
   ~ of this software and associated documentation files (the "Software"), to deal ~
   ~ in the Software without restriction, including without limitation the rights  ~
   ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell     ~
   ~ copies of the Software, and to permit persons to whom the Software is         ~
   ~ furnished to do so, subject to the following conditions:                      ~
   ~                                                                               ~
   ~ The above copyright notice and this permission notice shall be included in    ~
   ~ all copies or substantial portions of the Software.                           ~
   ~                                                                               ~
   ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR    ~
   ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,      ~
   ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE   ~
   ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER        ~
   ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ~
   ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN     ~
   ~ THE SOFTWARE.                                                                 ~
   ~                                                                               ~
   ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
  */
  package org.miaixz.bus.core.center.date.culture.lunar;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import org.miaixz.bus.core.center.date.culture.Galaxy;
  import org.miaixz.bus.core.center.date.culture.Loops;
  import org.miaixz.bus.core.center.date.culture.cn.Direction;
  import org.miaixz.bus.core.center.date.culture.cn.JulianDay;
  import org.miaixz.bus.core.center.date.culture.cn.fetus.FetusMonth;
  import org.miaixz.bus.core.center.date.culture.cn.ren.MinorRen;
  import org.miaixz.bus.core.center.date.culture.cn.sixty.EarthBranch;
  import org.miaixz.bus.core.center.date.culture.cn.sixty.HeavenStem;
  import org.miaixz.bus.core.center.date.culture.cn.sixty.SixtyCycle;
  import org.miaixz.bus.core.center.date.culture.cn.star.nine.NineStar;
  import org.miaixz.bus.core.center.date.culture.solar.SolarTerms;
  
  /**
   * 农历月
   *
   * @author Kimi Liu
   * @since Java 17+
   */
  public class LunarMonth extends Loops {
  
      public static final String[] NAMES = { "正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "腊月" };
      /**
       * 缓存
       */
      private static final Map<String, Object[]> cache = new HashMap<>();
      /**
       * 农历年
       */
      protected LunarYear year;
  
      /**
       * 月
       */
      protected int month;
  
      /**
       * 是否闰月
       */
      protected boolean leap;
  
      /**
       * 天数
       */
      protected int dayCount;
  
      /**
       * 位于当年的索引，0-12
       */
      protected int indexInYear;
  
      /**
       * 初一的儒略日
       */
      protected JulianDay firstJulianDay;
  
      /**
       * 从缓存初始化
       *
       * @param cache 缓存[农历年(int)，农历月(int,闰月为负)，天数(int)，位于当年的索引(int)，初一的儒略日(double)]
       */
      protected LunarMonth(Object[] cache) {
          int m = (int) cache[1];
          this.year = LunarYear.fromYear((int) cache[0]);
          this.month = Math.abs(m);
          this.leap = m < 0;
          this.dayCount = (int) cache[2];
          this.indexInYear = (int) cache[3];
          this.firstJulianDay = JulianDay.fromJulianDay((double) cache[4]);
      }
  
      /**
       * 从农历年月初始化
       *
       * @param year  农历年
       * @param month 农历月，闰月为负
       */
      public LunarMonth(int year, int month) {
          LunarYear currentYear = LunarYear.fromYear(year);
          int currentLeapMonth = currentYear.getLeapMonth();
          if (month == 0 || month > 12 || month < -12) {
              throw new IllegalArgumentException(String.format("illegal lunar month: %d", month));
          }
          boolean leap = month < 0;
          int m = Math.abs(month);
          if (leap && m != currentLeapMonth) {
              throw new IllegalArgumentException(String.format("illegal leap month %d in lunar year %d", m, year));
          }
  
          // 冬至
          double dongZhiJd = SolarTerms.fromIndex(year, 0).getCursoryJulianDay();
  
          // 冬至前的初一，今年首朔的日月黄经差
          double w = Galaxy.calcShuo(dongZhiJd);
          if (w > dongZhiJd) {
              w -= 29.53;
          }
  
          // 正常情况正月初一为第3个朔日，但有些特殊的
          int offset = 2;
          if (year > 8 && year < 24) {
              offset = 1;
          } else if (LunarYear.fromYear(year - 1).getLeapMonth() > 10 && year != 239 && year != 240) {
              offset = 3;
          }
  
          // 位于当年的索引
          int index = m - 1;
          if (leap || (currentLeapMonth > 0 && m > currentLeapMonth)) {
              index += 1;
          }
          indexInYear = index;
  
          // 本月初一
          w += 29.5306 * (offset + index);
          double firstDay = Galaxy.calcShuo(w);
          firstJulianDay = JulianDay.fromJulianDay(JulianDay.J2000 + firstDay);
          // 本月天数 = 下月初一 - 本月初一
          dayCount = (int) (Galaxy.calcShuo(w + 29.5306) - firstDay);
          this.year = currentYear;
          this.month = m;
          this.leap = leap;
      }
  
      /**
       * 从农历年月初始化
       *
       * @param year  农历年
       * @param month 农历月，闰月为负
       * @return 农历月
       */
      public static LunarMonth fromYm(int year, int month) {
          LunarMonth m;
          String key = String.format("%d%d", year, month);
          Object[] c = cache.get(key);
          if (null != c) {
              m = new LunarMonth(c);
          } else {
              m = new LunarMonth(year, month);
              cache.put(
                      key,
                      new Object[] { m.getYear(), m.getMonthWithLeap(), m.getDayCount(), m.getIndexInYear(),
                              m.getFirstJulianDay().getDay() });
          }
          return m;
      }
  
      /**
       * 农历年
       *
       * @return 农历年
       */
      public LunarYear getLunarYear() {
          return year;
      }
  
      /**
       * 年
       *
       * @return 年
       */
      public int getYear() {
          return year.getYear();
      }
  
      /**
       * 月
       *
       * @return 月
       */
      public int getMonth() {
          return month;
      }
  
      /**
       * 月
       *
       * @return 月，当月为闰月时，返回负数
       */
      public int getMonthWithLeap() {
          return leap ? -month : month;
      }
  
      /**
       * 天数(大月30天，小月29天)
       *
       * @return 天数
       */
      public int getDayCount() {
          return dayCount;
      }
  
      /**
       * 位于当年的索引(0-12)
       *
       * @return 索引
       */
      public int getIndexInYear() {
          return indexInYear;
      }
  
      /**
       * 农历季节
       *
       * @return 农历季节
       */
      public LunarSeason getSeason() {
          return LunarSeason.fromIndex(month - 1);
      }
  
      /**
       * 初一的儒略日
       *
       * @return 儒略日
       */
      public JulianDay getFirstJulianDay() {
          return firstJulianDay;
      }
  
      /**
       * 是否闰月
       *
       * @return true/false
       */
      public boolean isLeap() {
          return leap;
      }
  
      /**
       * 周数
       *
       * @param start 起始星期，1234560分别代表星期一至星期天
       * @return 周数
       */
      public int getWeekCount(int start) {
          return (int) Math.ceil((indexOf(firstJulianDay.getWeek().getIndex() - start, 7) + getDayCount()) / 7D);
      }
  
      /**
       * 依据国家标准《农历的编算和颁行》GB/T 33661-2017中农历月的命名方法。
       *
       * @return 名称
       */
      public String getName() {
          return (leap ? "闰" : "") + NAMES[month - 1];
      }
  
      @Override
      public String toString() {
          return year + getName();
      }
  
      public LunarMonth next(int n) {
          if (n == 0) {
              return fromYm(getYear(), getMonthWithLeap());
          }
          int m = indexInYear + 1 + n;
          LunarYear y = year;
          if (n > 0) {
              int monthCount = y.getMonthCount();
              while (m > monthCount) {
                  m -= monthCount;
                  y = y.next(1);
                  monthCount = y.getMonthCount();
              }
          } else {
              while (m <= 0) {
                  y = y.next(-1);
                  m += y.getMonthCount();
              }
          }
          boolean leap = false;
          int leapMonth = y.getLeapMonth();
          if (leapMonth > 0) {
              if (m == leapMonth + 1) {
                  leap = true;
              }
              if (m > leapMonth) {
                  m--;
              }
          }
          return fromYm(y.getYear(), leap ? -m : m);
      }
  
      /**
       * 本月的公历日列表
       *
       * @return 农历日列表
       */
      public List<LunarDay> getDays() {
          int size = getDayCount();
          int y = getYear();
          int m = getMonthWithLeap();
          List<LunarDay> l = new ArrayList<>(size);
          for (int i = 1; i <= size; i++) {
              l.add(LunarDay.fromYmd(y, m, i));
          }
          return l;
      }
  
      /**
       * 本月的农历周列表
       *
       * @param start 星期几作为一周的开始，1234560分别代表星期一至星期天
       * @return 周列表
       */
      public List<LunarWeek> getWeeks(int start) {
          int size = getWeekCount(start);
          int y = getYear();
          int m = getMonthWithLeap();
          List<LunarWeek> l = new ArrayList<>(size);
          for (int i = 0; i < size; i++) {
              l.add(LunarWeek.fromYm(y, m, i, start));
          }
          return l;
      }
  
      /**
       * 干支
       *
       * @return 干支
       */
      public SixtyCycle getSixtyCycle() {
          return SixtyCycle.fromName(
                  HeavenStem.fromIndex((year.getSixtyCycle().getHeavenStem().getIndex() + 1) * 2 + indexInYear).getName()
                          + EarthBranch.fromIndex(indexInYear + 2).getName());
      }
  
      /**
       * 九星
       *
       * @return 九星
       */
      public NineStar getNineStar() {
          int index = getSixtyCycle().getEarthBranch().getIndex();
          if (index < 2) {
              index += 3;
          }
          return NineStar.fromIndex(27 - year.getSixtyCycle().getEarthBranch().getIndex() % 3 * 3 - index);
      }
  
      /**
       * 太岁方位
       *
       * @return 方位
       */
      public Direction getJupiterDirection() {
          SixtyCycle sixtyCycle = getSixtyCycle();
          int n = new int[] { 7, -1, 1, 3 }[sixtyCycle.getEarthBranch().next(-2).getIndex() % 4];
          return n != -1 ? Direction.fromIndex(n) : sixtyCycle.getHeavenStem().getDirection();
      }
  
      /**
       * 逐月胎神
       *
       * @return 逐月胎神
       */
      public FetusMonth getFetus() {
          return FetusMonth.fromLunarMonth(this);
      }
  
      /**
       * 小六壬
       *
       * @return 小六壬
       */
      public MinorRen getMinorRen() {
          return MinorRen.fromIndex((month - 1) % 6);
      }
  
  }
  