package kr.kangwoo.util.date;

import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;

import kr.kangwoo.util.DateUtils;
import kr.kangwoo.util.MessageUtils;

/**
 * <p>ÀÌ Å¬·¡½º´Â À½·Â °ü·Ã ÇÔ¼ö¸¦ Á¦°øÇÕ´Ï´Ù.</p>
 * <p>ÇöÀç 1800³â ºÎÅÍ 2100³â ±îÁö »ç¿ëÀÌ °¡´ÉÇÕ´Ï´Ù.</p> 
 * 
 * <pre>
 * 1) 2008³â 11¿ù 11ÀÏ Æò´ÞÀÇ À½·Â µ¥ÀÌÅÍ »ý¼º
 *     LunaDateUtils lunaDate = new LunaDateUtils(2008, 11, 11, false);
 * 2) 2008³â 11¿ù 11ÀÏÀÇ ¾ç·Â µ¥ÀÌÅÍ »ý¼º
 *     LunaDateUtils lunaDate = LunaDateUtils.getLunaDate(2008, 11, 11);
 * 3) ÇöÀç ³¯Â¥ÀÇ À½·Â µ¥ÀÌÅÍ »ý¼º
 *     LunaDateUtils lunaDate = LunaDateUtils.getLunaDate();
 *     ¶Ç´Â
 *     LunaDateUtils lunaDate = LunaDateUtils.getLunaDate(new Date());
 * </pre>
 * 
 * 
 * <h4>¶ì¸¦ ±¸ºÐÇÏ´Â ±âÁØ</h4>
 * <ul>
 * 	<li> À½·Â 1¿ù 1ÀÏ ±âÁØ¼³ : ¹Î°£¿¡¼­ ¸¹ÀÌ »ç¿ë
 * 	<li> ÀÔÃá : ¿ª¼úÀÎµéÀÌ ¸¹ÀÌ »ç¿ë(¸¸¼¼·Â)
 * 	<li> µ¿Áö : ÁßÈ­±Ç ±¹°¡¿¡¼­ »ç¿ëÇÏ±âµµ ÇÏÁö¸¸, ±¹³»¿¡¼­´Â °ÅÀÇ »ç¿ëÇÏÁö ¾ÊÀ½
 * </ul>
 * @author <a href="mailto:kangwoo@jarusoft.com">kangwoo</a>
 * @version 1.0
 * @since 1.0
 */
public class LunaDate implements LunaDateConstants {

    // ¾ç/À½·Â ±âÁØÀÏ
    private static final Date SOLRA_FIXED_DATE = DateUtils.toDate(1801, 1, 1);
    private static final LunaDate LUNA_FIXED_DATE = new LunaDate(1800, 11, 17, false);
    
    // ¼Óµµ Çâ»óÀ» À§ÇÑ Ä³½Ã
    private static final Object[][] CACHE = {
    	{1851, DateUtils.toDate(1851, 1, 1), 1850, new LunaDate(1850, 11, 29, false)},
    	{1901, DateUtils.toDate(1901, 1, 1), 1900, new LunaDate(1900, 11, 11, false)},
    	{1951, DateUtils.toDate(1951, 1, 1), 1950, new LunaDate(1950, 11, 24, false)},
    	{2001, DateUtils.toDate(2001, 1, 1), 2000, new LunaDate(2000, 12,  7, false)},
    };
    
    private int year;
    private int month;
    private int day;
    private boolean isLeaf;	// À±´Þ¿©ºÎ
    
    private int gan;	// Ãµ°£
    private int jee;	// ÁöÁö
    private int ddi;	// ¶ì

    /**
     * <p>À½·ÂÀÏ »ý¼ºÀÚÀÌ´Ù.</p>
     * 
     * @param year À½·ÂÀÇ ³â(1799~2100)
     * @param month À½·ÂÀÇ ¿ù(1~12)
     * @param day À½·ÂÀÇ ÀÏ
     * @param isLeaf À½·ÂÀÇ À±´Þ¿©ºÎ
     */
    public LunaDate(int year, int month, int day, boolean isLeaf) {
    	setYear(year);
        setMonth(month, isLeaf);
        setDay(day);
    }

    /**
     * <p>À½·ÂÀÏ »ý¼ºÀÚÀÌ´Ù.</p>
     * 
     * @param dateStr À½·Â ¹®ÀÚ¿­
     * @param pattern ÀÔ·Â ¹®ÀÚ¿­ÀÇ ÆÐÅÏ
     * @param isLeaf À½·ÂÀÇ À±´Þ¿©ºÎ
     * @throws ParseException À½·Â ¹®ÀÚ¿­ ºÐ¼® ½ÇÆÐ½Ã
     */
    public LunaDate(String dateStr, String pattern, boolean isLeaf) throws ParseException {
    	Date date = DateUtils.toDate(dateStr, pattern);
		setYear(DateUtils.getYear(date));
		setMonth(DateUtils.getMonth(date), isLeaf);
		setDay(DateUtils.getDay(date));
    }
	
    /**
     * <p>À½·ÂÀÏ »ý¼ºÀÚÀÌ´Ù.</p>
     * 
     * @param dateStr À½·Â ¹®ÀÚ¿­
     * @param isLeaf À½·ÂÀÇ À±´Þ¿©ºÎ
     * @throws ParseException À½·Â ¹®ÀÚ¿­ ºÐ¼® ½ÇÆÐ½Ã
     */
    public LunaDate(String dateStr, boolean isLeaf) throws ParseException {
    	if (dateStr == null) {
    		throw new IllegalArgumentException(MessageUtils.format("Illegal Argument Date String \"{}\"", dateStr));
    	}
    	Date date = DateUtils.toDate(dateStr);
		setYear(DateUtils.getYear(date));
		setMonth(DateUtils.getMonth(date), isLeaf);
		setDay(DateUtils.getDay(date));
    }
    
    /**
     * <p>¾ç·ÂÀ» À½·ÂÀ¸·Î º¯È¯ÇÑ´Ù.</p>
     * 
     * @param solraDate ¾ç·Â
     * @return À½·Â
     */
    public static LunaDate toLunaDate(Date solraDate) {
    	int year = DateUtils.getYear(solraDate);
    	Date solraFixedDate = null;
    	LunaDate lunaFixedDate = null;
    	for (int i = CACHE.length - 1; i >= 0; i--) {
    		if ((Integer)CACHE[i][0] < year) {
    			solraFixedDate = (Date)CACHE[i][1];
    			lunaFixedDate = (LunaDate)CACHE[i][3];
    			break;
    		}
    	}
    	if (solraFixedDate == null || lunaFixedDate == null) {
			solraFixedDate = SOLRA_FIXED_DATE;
			lunaFixedDate = LUNA_FIXED_DATE;
    	}
    	LunaDate lunaDate = (LunaDate)lunaFixedDate.clone();
    	lunaDate.addDays(DateUtils.getDaysBetween(solraFixedDate, solraDate));
    	return lunaDate;
    }
    
    /**
     * <p>¾ç·Â(³â/¿ù/ÀÏ)À» À½·ÂÀ¸·Î º¯È¯ÇÑ´Ù.</p>
     * 
     * @param year ¾ç·ÂÀÇ ³â
     * @param month ¾ç·ÂÀÇ ¿ù
     * @param day ¾ç·ÂÀÇ ÀÏ
     * @return À½·Â
     */
    public static LunaDate toLunaDate(int year, int month, int day) {
    	return toLunaDate(DateUtils.toDate(year, month, day));
    }
    
    /**
     * <p>ÇöÀç ³¯Â¥¸¦ ±âÁØÀ¸·Î À½·ÂÀ» »ý¼ºÇÑ´Ù.</p>
     * 
     * @return À½·Â
     */
    public static LunaDate toLunaDate() {
    	return toLunaDate(new Date());
    }

    /**
     * <p>¾ç·ÂÀ¸·Î º¯È¯½ÃÅ²´Ù.</p>
     * 
     * @return ¾ç·Â
     */
    public Date toSolarDate() {
    	Date solraFixedDate = null;
    	LunaDate lunaFixedDate = null;
    	for (int i = CACHE.length - 1; i >= 0; i--) {
    		if ((Integer)CACHE[i][2] < year) {
    			solraFixedDate = (Date)CACHE[i][1];
    			lunaFixedDate = (LunaDate)CACHE[i][3];
    			break;
    		}
    	}
    	if (solraFixedDate == null || lunaFixedDate == null) {
			solraFixedDate = SOLRA_FIXED_DATE;
			lunaFixedDate = LUNA_FIXED_DATE;
    	}
    	int amount = lunaFixedDate.getPeriodDays(this);
    	Calendar calendar = DateUtils.toCalendar(solraFixedDate);
    	calendar.add(Calendar.DAY_OF_YEAR, amount);
    	return calendar.getTime();
    }
    
    /**
     * <p>³âµµ¸¦ ¼³Á¤ÇÕ´Ï´Ù.</p>
     * 
     * @param year ¼³Á¤ÇÏ·Á´Â ³âµµ(1799~2100).
     */
    protected void setYear(int year) {
    	if (year < (LUNA_BEGIN_YEAR + 1) && year > (LUNA_END_YEAR - 1)) {
    		throw new IllegalArgumentException("°¡´ÉÇÑ ¿¬µµ ¹üÀ§´Â " + (LUNA_BEGIN_YEAR + 1) + " ~ " + (LUNA_END_YEAR - 1) + " ±îÁöÀÔ´Ï´Ù.");
    	}
        this.year = year;
        this.gan = (year+6) % 10;
        this.jee = (year+8) % 12;
        this.ddi = (year+8) % 12;
    }
 
    /**
     * <p>¿ùÀ» ¼³Á¤ÇÕ´Ï´Ù.</p>
     * 
     * @param month ¼³Á¤ÇÏ·Á´Â ¿ù(1~12).
     */
    protected void setMonth(int month, boolean isLeaf) {
    	if (month < 1 || month > 12) {
    		throw new IllegalArgumentException("°¡´ÉÇÑ ¿ù ¹üÀ§´Â 1 ~ 12 ±îÁöÀÔ´Ï´Ù.");	
    	}
    	// À±´Þ °¡´É ¿©ºÎ °Ë»ç
    	if (isLeaf) {
    		if (getDaysOfMonthByIndex(year - LUNA_BEGIN_YEAR, month - 1).length != 2) {
    			throw new IllegalArgumentException(MessageUtils.format("{0}³â {1}¿ùÀº À±´ÞÀÌ Á¸ÀçÇÏÁö ¾Ê½À´Ï´Ù.", year, month));	
    		}
    	}

        this.month = month;
        this.isLeaf = isLeaf;
        
    }

    /**
     * <p>ÀÏÀ» ¼³Á¤ÇÕ´Ï´Ù.</P>
     * 
     * @param day ¼³Á¤ÇÏ·Á´Â ÀÏ.
     */
    protected void setDay(int day) {
    	int[] daysOfMonth = getDaysOfMonthByIndex(year - LUNA_BEGIN_YEAR, month - 1);
    	int maxDay = isLeaf ? daysOfMonth[1] : daysOfMonth[0];
    	if (day > maxDay || day < 1) {
    		throw new IllegalArgumentException(MessageUtils.format("{0}³â {1}¿ù({2}´Þ)Àº 1ÀÏºÎÅÍ {3}ÀÏ±îÁö¸¸ Á¸ÀçÇÕ´Ï´Ù.({4})", year, month, (isLeaf ? "À±" : "Æò"), maxDay, day));
    	}
        this.day = day;
    }  
    
    /**
     * <p>ÇØ´ç ³â, ¿ùÀÇ ÀÏ¼ö¸¦ °¡Á®¿É´Ï´Ù.</p>
     * 
     * @param yearIndex ³â ÀÎµ¦½º(0ºÎÅÍ ½ÃÀÛ)
     * @param monthIndex ¿ù ÀÎµ¦½º(0ºÎÅÍ ½ÃÀÛ)
     * @return
     */
    public static int[] getDaysOfMonthByIndex(int yearIndex, int monthIndex) {
    	return DAYS_OF_MONTH[LUNA_MONTH_TABLE[yearIndex][monthIndex] - 1];
    }
    
    /**
     * <p>ÇØ´ç ³â, ¿ùÀÇ ÀÏ¼ö¸¦ °¡Á®¿É´Ï´Ù.</p>
     * 
     * @param year ³â
     * @param month ¿ù
     * @return
     */
    public static int[] getDaysOfMonth(int year, int month) {
    	return getDaysOfMonthByIndex(year - LUNA_BEGIN_YEAR, month - 1);
    }
    
    /**
     * <p>ÇØ´ç ³âÀÇ ÀÏ¼ö¸¦ °¡Á®¿É´Ï´Ù.</p>
     * 
     * @param yearIndex ³â ÀÎµ¦½º(0ºÎÅÍ ½ÃÀÛ)
     * @return
     */
    public static int getDaysOfYearByIndex(int yearIndex) {
    	int daysOfYear = 0;
    	int[] daysOfMonth = null;
    	for (int i = 0; i < 12; i++) {
    		daysOfMonth = getDaysOfMonthByIndex(yearIndex, i);
    		daysOfYear += daysOfMonth[0];
    		if (daysOfMonth.length > 1) {
    			daysOfYear += daysOfMonth[1];
    		}
    	}
    	return daysOfYear;
    }

    /**
     * <p>ÇØ´ç ³âÀÇ ÀÏ¼ö¸¦ °¡Á®¿É´Ï´Ù.</p>
     * 
     * @param yearIndex ³â
     * @return
     */
    public static int getDaysOfYear(int year) {
    	return getDaysOfYearByIndex(year - LUNA_BEGIN_YEAR);
    }
    
    /**
     * <p>ÇØ´ç ÀÏ¼ö¸¸Å­ ´õÇÏ°Å³ª »®´Ï´Ù.</p>
     * 
     * @param days ÀÏ¼ö
     * @return
     */
    public LunaDate addDays(int days) {
    	if (days > 0) {
    		return afterDays(days);
    	} else if (days < 0) {
    		return beforeDays(0 - days);
    	}
    	return this;
    }
    
    /**
     * <p>ÇØ´ç ÀÏ¼ö ¸¸Å­ ´õÇÑ ³¯Â¥¸¦ ±¸ÇÕ´Ï´Ù.</p>
     * 
     * @param days
     * @return
     */
	public LunaDate afterDays(int days) {
		if (days < 0) {
			return beforeDays(days);
		}
		
		int tmpDays = day + days;
		boolean tmpIsLeaf = isLeaf;
		
		boolean notFinished = true;
		int[] daysOfMonth = null;
		int i = year - LUNA_BEGIN_YEAR;
		int j = month - 1;
		// Ã³À½ ÇÑ¹ø À±´Þ º¸Á¤
		if (tmpIsLeaf) {
			daysOfMonth = getDaysOfMonthByIndex(i, j);
			tmpDays += daysOfMonth[0];
		}
        for (	; (i < LUNA_MONTH_TABLE.length) && (notFinished); i++) {
            for (	; (j < 12) && (notFinished); j++) {
            	tmpIsLeaf = false;
            	daysOfMonth = getDaysOfMonthByIndex(i, j);
                if (tmpDays > daysOfMonth[0]) {
                	tmpDays = tmpDays - daysOfMonth[0];
                	if (daysOfMonth.length > 1) {
                		if (tmpDays > daysOfMonth[1]) {
                    		tmpDays = tmpDays - daysOfMonth[1];
                		} else {
                			tmpIsLeaf = true;
                        	setYear(i + LUNA_BEGIN_YEAR);
                        	setMonth(j + 1, tmpIsLeaf);
                        	setDay(tmpDays);
                        	notFinished = false;
                    	}
                	} 
                } else {
                	setYear(i + LUNA_BEGIN_YEAR);
                	setMonth(j + 1, tmpIsLeaf);
                	setDay(tmpDays);
                	notFinished = false;
                }
            } // end for j
            j = 0;
        }
        
    	if (notFinished) {
    		throw new IllegalArgumentException("°è»ê °¡´ÉÇÑ ³¯Â¥ ¹üÀ§¸¦ ¹þ¾î³µ½À´Ï´Ù. (" + (LUNA_BEGIN_YEAR + 1) + "0101~" + (LUNA_END_YEAR - 1) + "12DD)");
    	}
        return this;
	}
	
	/**
	 * <p>ÇØ´ç ÀÏ¼ö ¸¸Å­ »« ³¯Â¥¸¦ ±¸ÇÕ´Ï´Ù.</p>
	 * 
	 * @param days ¾ç¼öÀÏ °æ¿ì »«´Ù.
	 * @return
	 */
	public LunaDate beforeDays(int days) {
		if (days < 0) {
			return afterDays(0 - days);
		}
		
		int tmpDays = day - days;
		boolean tmpIsLeaf = isLeaf;
		
		int[] daysOfMonth = null;
		int i = year - LUNA_BEGIN_YEAR;
		int j = month - 1 - 1;
		
		// Ã³À½ ÇÑ¹ø À±´Þ º¸Á¤
		if (tmpIsLeaf == false) {
			daysOfMonth = getDaysOfMonthByIndex(i, j);
			if (daysOfMonth.length > 1) {
				tmpDays -= daysOfMonth[1];	
			}
		}
		int tmpYear = year;
		int tmpMonth = month;
		int tmpDay = tmpDays;
        for (	; (i >= 0) && (tmpDays < 1); i--) {
            for (	; (j >= 0) && (tmpDays < 1); j--) {
            	tmpIsLeaf = false;
            	daysOfMonth = getDaysOfMonthByIndex(i, j);
            	if (daysOfMonth.length > 1) {
            		tmpDays += daysOfMonth[1];
            		if (tmpDays > 0) {
            			tmpIsLeaf = true;
            			tmpYear = i + LUNA_BEGIN_YEAR;
            			tmpMonth = j + 1;
            			tmpDay = tmpDays;
            		} else {
            			tmpDays += daysOfMonth[0];
            			if (tmpDays > 0) {
                			tmpYear = i + LUNA_BEGIN_YEAR;
                			tmpMonth = j + 1;
                			tmpDay = tmpDays;
            			}
            		}
            	} else {
            		tmpDays += daysOfMonth[0];
        			if (tmpDays > 0) {
            			tmpYear = i + LUNA_BEGIN_YEAR;
            			tmpMonth = j + 1;
            			tmpDay = tmpDays;
        			}
            	}

            } // end for j
        	j = 11;
        }
        
    	if (tmpDay < 1) {
    		throw new IllegalArgumentException("°è»ê °¡´ÉÇÑ ³¯Â¥ ¹üÀ§¸¦ ¹þ¾î³µ½À´Ï´Ù. (" + (LUNA_BEGIN_YEAR + 1) + "0101~" + (LUNA_END_YEAR - 1) + "12DD)");
    	}
        
    	setYear(tmpYear);
    	setMonth(tmpMonth, tmpIsLeaf);
    	setDay(tmpDay);
        return this;
	}
	
	public int compare(LunaDate date) {
		int thisVal = (year * 100000) + (month * 1000) + (day * 10) + (isLeaf ? 1 : 0);
		int anotherVal = (date.year * 100000) + (date.month * 1000) + (date.day * 10) + (date.isLeaf ? 1 : 0);
		return (thisVal<anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1));
	}
	
	public int getPeriodDays(LunaDate date) {
		if (date == null || this.equals(date)) {
			return 0;
		}

		int fromYear, fromMonth, fromDay;
		int toYear, toMonth, toDay;
		boolean fromIsLeaf, toIsLeaf;
		boolean needRevert = false;
		if (compare(date) < 0) {
			fromYear = this.year;
			fromMonth = this.month;
			fromDay = this.day;
			fromIsLeaf = this.isLeaf;
			toYear = date.year;
			toMonth = date.month;
			toDay = date.day;
			toIsLeaf = date.isLeaf;
			
			needRevert = false;
		} else {
			fromYear = date.year;
			fromMonth = date.month;
			fromDay = date.day;
			fromIsLeaf = date.isLeaf;
			toYear = this.year;
			toMonth = this.month;
			toDay = this.day;	
			toIsLeaf = this.isLeaf;
			
			needRevert = true;
		}

		int diffDays = 0;
		int[] daysOfMonth = null;
		
		if (toYear > fromYear) {
			for (int i = fromYear + 1; i < toYear; i++) {
				diffDays += getDaysOfYear(i);
			}
			for (int i = 1; i < toMonth; i++) {
				daysOfMonth = getDaysOfMonth(toYear, i);
				diffDays += daysOfMonth[0];
				if (daysOfMonth.length > 1) {
					diffDays += daysOfMonth[1];
				}
			}
			daysOfMonth = getDaysOfMonth(toYear, toMonth);
			if (toIsLeaf) {
				diffDays += daysOfMonth[0];
			}
			diffDays += toDay;
			
			toYear = fromYear;
			toMonth = 12;
			daysOfMonth = getDaysOfMonth(toYear, toMonth);
			toIsLeaf = daysOfMonth.length > 1;
			toDay = toIsLeaf ? daysOfMonth[1] : daysOfMonth[0];
		}
		
		if (toMonth > fromMonth) {
			for (int i = fromMonth + 1; i < toMonth; i++) {
				daysOfMonth = getDaysOfMonth(toYear, i);
				diffDays += daysOfMonth[0];
				if (daysOfMonth.length > 1) {
					diffDays += daysOfMonth[1];
				}
			}
			daysOfMonth = getDaysOfMonth(toYear, toMonth);
			if (toIsLeaf) {
				diffDays += daysOfMonth[0];
			}
			diffDays += toDay;
			
			toMonth = fromMonth;
			daysOfMonth = getDaysOfMonth(toYear, toMonth);
			toIsLeaf = daysOfMonth.length > 1;
			toDay = toIsLeaf ? daysOfMonth[1] : daysOfMonth[0];
		}
		
		daysOfMonth = getDaysOfMonth(toYear, toMonth);
		if (fromIsLeaf == toIsLeaf) {
			diffDays += (toDay - fromDay);
		} else {
			if (fromIsLeaf == false && toIsLeaf == true) {
				diffDays += (daysOfMonth[0] - fromDay);
				diffDays += toDay;
			} else {
				// ¹ß»ýÇÒ ¼ö ¾øÀ½ - ¸¸¾à¿¡ ¹ß»ýÇÑ´Ù¸é ·ÎÁ÷»ó¿¡ ¹®Á¦°¡ ÀÖ´Â°ÍÀÓ.
				throw new RuntimeException("ÀÌ ¿¡·¯´Â ¹ß»ýÇÒ ¼ö ¾ø½À´Ï´Ù.");
			}
		}
        return needRevert ? 0 - diffDays : diffDays;
	}
    
    /**
     * <p>³âÀ» ¹ÝÈ¯ÇÑ´Ï´Ù.</p>
     * 
     * @return year
     */
    public int getYear() {
        return year;
    }
    
    
    /**
     * <p>¿ùÀ» ¹ÝÈ¯ÇÑ´Ï´Ù.</p>
     * 
     * @return month
     */
    public int getMonth() {
        return month;
    }
    
    /**
     * <p>±æÀ» ¹ÝÈ¯ÇÑ´Ï´Ù.</p>
     * 
     * @return day
     */
    public int getDay() {
        return day;
    }

    /**
     * <p>À±´Þ¿©ºÎ¸¦ ¹ÝÈ¯ÇÑ´Ï´Ù.</p>
     * 
     * @return À±´Þ¿©ºÎ
     */
    public boolean isLeaf() {
        return isLeaf;
    }
    
    
    /**
     * <p>¶ì ¹øÈ£¸¦ ¹ÝÈ¯ÇÕ´Ï´Ù.</p>
     * <p>0="Áã",1="¼Ò",2="¹ü",3="Åä³¢",4="¿ë",5="¹ì",6="¸»",7="¾ç",8="¿ø¼þÀÌ",9="´ß",10="°³",11="µÅÁö"</p>
     * 
     * @return ¶ì¹øÈ£
     */
    public int getDdi() {
        return ddi;
    }
    
    /**
     * <p>¶ì¸¦ ¹ÝÈ¯ÇÕ´Ï´Ù.</p>
     * <p>"Áã","¼Ò","¹ü","Åä³¢","¿ë","¹ì","¸»","¾ç","¿ø¼þÀÌ","´ß","°³","µÅÁö"</p>
     * 
     * @return
     */
    public String getDdiAsString() {
        return HANGUL_DDI[getDdi()];
    }

    /**
     * <p>Ãµ°£(ô¸ÊÎ)ÀÇ ¹øÈ£¸¦ ¹ÝÈ¯ÇÕ´Ï´Ù.</p>
     * 
     * <pre>
     * À°½Ê°©ÀÚÀÇ À­ºÎºÐÀ» ÀÌ·ç´Â ¿­ °³ÀÇ Ãµ°£
     * [°©(Ë£)¡¤À»(ëà)¡¤º´(Ü°)¡¤Á¤(ïË)¡¤¹«(Ùæ)¡¤±â(Ðù)¡¤°æ(ÌÒ)¡¤½Å(ãô)¡¤ÀÓ(ìó)¡¤°è(Í¤)]
     * (°© = 0, À» = 1, ..., °è = 9)
     * 
     * @return Ãµ°£(ô¸ÊÎ)ÀÇ ¹øÈ£
     */
    public int getGan() {
        return gan;
    }
    
    /**
     * <p>Ãµ°£(ô¸ÊÎ)À» ¹ÝÈ¯ÇÕ´Ï´Ù.</p>
     * 
     * <pre>
     * À°½Ê°©ÀÚÀÇ À­ºÎºÐÀ» ÀÌ·ç´Â ¿­ °³ÀÇ Ãµ°£
     * [°©(Ë£)¡¤À»(ëà)¡¤º´(Ü°)¡¤Á¤(ïË)¡¤¹«(Ùæ)¡¤±â(Ðù)¡¤°æ(ÌÒ)¡¤½Å(ãô)¡¤ÀÓ(ìó)¡¤°è(Í¤)]
     * 
     * @return Ãµ°£(ô¸ÊÎ)
     */
    public String getGanAsString() {
        return HANJA_GAN[getGan()];
    }
    
    /**
     * <p>½ÊÀÌÀÚ(ä¨ì£í­)ÀÇ ¹øÈ£¸¦ ¹ÝÈ¯ÇÕ´Ï´Ù.</p>
     * 
     * <pre>
     * À°½Ê°©ÀÚÀÇ ¾Æ·§ºÎºÐÀ» ÀÌ·ç´Â 12°³ÀÇ ÁöÁö(ò¢ò¨)
     * [ÀÚ(í­)¡¤Ãà(õä)¡¤ÀÎ(ìÙ)¡¤¹¦(ÙÖ)¡¤Áø(òã)¡¤»ç(ÞÓ)¡¤¿À(çí)¡¤¹Ì(Ú±)¡¤½Å(ãé)¡¤À¯(ë·)¡¤¼ú(âù)¡¤ÇØ(ú¤)]
     * (ÀÚ = 0, Ãà = 1, ..., ÇØ = 11)
     * </pre>
     * 
     * @return ½ÊÀÌÀÚ(ä¨ì£í­)ÀÇ ¹øÈ£
     */
    public int getJee() {
        return jee;
    }
    
    /**
     * <p>½ÊÀÌÀÚ(ä¨ì£í­)¸¦ ¹ÝÈ¯ÇÕ´Ï´Ù.</p>
     * 
     * <pre>
     * À°½Ê°©ÀÚÀÇ ¾Æ·§ºÎºÐÀ» ÀÌ·ç´Â 12°³ÀÇ ÁöÁö(ò¢ò¨)
     * [ÀÚ(í­)¡¤Ãà(õä)¡¤ÀÎ(ìÙ)¡¤¹¦(ÙÖ)¡¤Áø(òã)¡¤»ç(ÞÓ)¡¤¿À(çí)¡¤¹Ì(Ú±)¡¤½Å(ãé)¡¤À¯(ë·)¡¤¼ú(âù)¡¤ÇØ(ú¤)]
     * </pre>
     * 
     * @return ½ÊÀÌÀÚ(ä¨ì£í­)
     */
    public String getJeeAsString() {
        return HANJA_JEE[getJee()];
    }
    	
    @Override
	public Object clone() {
		return new LunaDate(year, month, day, isLeaf);
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null || obj.getClass() != this.getClass()) {
			return false;
		}

		LunaDate another = (LunaDate) obj;
		return (this.year == another.year) && (this.month == another.month)
				&& (this.day == another.day) && (this.isLeaf == another.isLeaf);
	}

	@Override
	public int hashCode() {
		return (year * 100000) + (month * 1000) + (day * 10) + (isLeaf ? 1 : 0);
	}

	@Override
    public String toString() {
        StringBuilder buffer = new StringBuilder();
        buffer.append("LunaDateUtils[");
        buffer.append("year = ").append(year);
        buffer.append(", month = ").append(month);
        buffer.append(", day = ").append(day);
        buffer.append(", isLeaf = ").append(isLeaf);
        buffer.append(", gan = ").append(getGanAsString());
        buffer.append(", jee = ").append(getJeeAsString());
        buffer.append(", ddi = ").append(getDdiAsString());
        buffer.append("]");
        return buffer.toString();
    }
}

