package com.coolerfall.widget.lunar;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.View;
import java.util.Calendar;

/**
 * Display one month with solar and lunar date on a calendar.
 *
 * @author Vincent Cheung ([email protected])
 */
@SuppressLint("ViewConstructor") final class MonthView extends View {
  private static final int DAYS_IN_WEEK = 7;

  private int selectedIndex = -1;

  private float solarTextSize;
  private float lunarTextSize;
  private float lunarOffset;
  private float circleRadius;

  private Month month;
  private LunarView lunarView;

  private final Region[][] monthWithFourWeeks = new Region[4][DAYS_IN_WEEK];
  private final Region[][] monthWithFiveWeeks = new Region[5][DAYS_IN_WEEK];
  private final Region[][] monthWithSixWeeks = new Region[6][DAYS_IN_WEEK];
  private Paint paint;

  /**
   * The constructor of month view.
   *
   * @param context context to use
   * @param lunarView {@link LunarView}
   */
  public MonthView(Context context, Month month, LunarView lunarView) {
    super(context);

    this.month = month;
    this.lunarView = lunarView;
    init();
  }

  @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    int dayWidth = (int) (w / 7f);
    int dayHeightInFourWeek = (int) (h / 4f);
    int dayHeightInFiveWeek = (int) (h / 5f);
    int dayHeightInSixWeek = (int) (h / 6f);

    circleRadius = dayWidth / 2.2f;

    solarTextSize = h / 15f;
    paint.setTextSize(solarTextSize);
    float solarHeight = paint.getFontMetrics().bottom - paint.getFontMetrics().top;

    lunarTextSize = solarTextSize / 2;
    paint.setTextSize(lunarTextSize);
    float lunarHeight = paint.getFontMetrics().bottom - paint.getFontMetrics().top;

    lunarOffset = (Math.abs(paint.ascent() + paint.descent()) + solarHeight + lunarHeight) / 3f;

    initMonthRegion(monthWithFourWeeks, dayWidth, dayHeightInFourWeek);
    initMonthRegion(monthWithFiveWeeks, dayWidth, dayHeightInFiveWeek);
    initMonthRegion(monthWithSixWeeks, dayWidth, dayHeightInSixWeek);
  }

  @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
    setMeasuredDimension(measureWidth, (int) (measureWidth * 6f / 7f));
  }

  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    if (month == null) {
      return;
    }

    canvas.save();
    int weeks = month.getWeeksInMonth();
    Region[][] monthRegion = getMonthRegion();

    for (int i = 0; i < weeks; i++) {
      for (int j = 0; j < DAYS_IN_WEEK; j++) {
        draw(canvas, monthRegion[i][j].getBounds(), i, j);
      }
    }
    canvas.restore();
  }

  @SuppressLint("ClickableViewAccessibility") @Override
  public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        return true;

      case MotionEvent.ACTION_UP:
        handleClickEvent((int) event.getX(), (int) event.getY());
        return true;

      default:
        return super.onTouchEvent(event);
    }
  }

  /* init month view */
  private void init() {
    paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.LINEAR_TEXT_FLAG);
    paint.setTextAlign(Paint.Align.CENTER);

    if (month.isMonthOfToday()) {
      selectedIndex = month.getIndexOfToday();
    }

    setBackgroundColor(lunarView.getMonthBackgroundColor());
  }

  /* init month region with the width and height of day */
  private void initMonthRegion(Region[][] monthRegion, int dayWidth, int dayHeight) {
    for (int i = 0; i < monthRegion.length; i++) {
      for (int j = 0; j < monthRegion[i].length; j++) {
        Region region = new Region();
        region.set(j * dayWidth, i * dayHeight, dayWidth + j * dayWidth, dayWidth + i * dayHeight);
        monthRegion[i][j] = region;
      }
    }
  }

  /* get month region for current month */
  private Region[][] getMonthRegion() {
    int weeks = month.getWeeksInMonth();
    Region[][] monthRegion;
    if (weeks == 4) {
      monthRegion = monthWithFourWeeks;
    } else if (weeks == 5) {
      monthRegion = monthWithFiveWeeks;
    } else {
      monthRegion = monthWithSixWeeks;
    }

    return monthRegion;
  }

  /* draw all the text in month view */
  private void draw(Canvas canvas, Rect rect, int xIndex, int yIndex) {
    MonthDay monthDay = month.getMonthDay(xIndex, yIndex);

    drawBackground(canvas, rect, monthDay, xIndex, yIndex);
    drawSolarText(canvas, rect, monthDay);
    drawLunarText(canvas, rect, monthDay);
  }

  /* draw solar text in month view */
  private void drawSolarText(Canvas canvas, Rect rect, MonthDay monthDay) {
    if (monthDay == null) {
      return;
    }

    if (!monthDay.isCheckable()) {
      paint.setColor(lunarView.getUnCheckableColor());
    } else if (monthDay.isWeekend()) {
      paint.setColor(lunarView.getHightlightColor());
    } else {
      paint.setColor(lunarView.getSolarTextColor());
    }

    paint.setTextSize(solarTextSize);
    canvas.drawText(monthDay.getSolarDay(), rect.centerX(), rect.centerY(), paint);
  }

  /* draw lunar text in month view */
  private void drawLunarText(Canvas canvas, Rect rect, MonthDay monthDay) {
    if (monthDay == null) {
      return;
    }

    if (!monthDay.isCheckable()) {
      paint.setColor(lunarView.getUnCheckableColor());
    } else if (monthDay.isHoliday()) {
      paint.setColor(lunarView.getHightlightColor());
    } else {
      paint.setColor(lunarView.getLunarTextColor());
    }

    paint.setTextSize(lunarTextSize);
    canvas.drawText(monthDay.getLunarDay(), rect.centerX(), rect.centerY() + lunarOffset, paint);
  }

  /* draw circle for selected day */
  private void drawBackground(Canvas canvas, Rect rect, MonthDay day, int xIndex, int yIndex) {
    if (day.isToday()) {
      Drawable background = lunarView.getTodayBackground();
      if (background == null) {
        drawRing(canvas, rect);
      } else {
        background.setBounds(rect);
        background.draw(canvas);
      }

      return;
    }

		/* not today was selected */
    if (selectedIndex == -1 && day.isFirstDay()) {
      selectedIndex = xIndex * DAYS_IN_WEEK + yIndex;
    }

    if (selectedIndex / DAYS_IN_WEEK != xIndex || selectedIndex % DAYS_IN_WEEK != yIndex) {
      return;
    }

    paint.setColor(lunarView.getCheckedDayBackgroundColor());
    canvas.drawCircle(rect.centerX(), rect.centerY(), circleRadius, paint);
  }

  /* draw ring as background of today */
  private void drawRing(Canvas canvas, Rect rect) {
    paint.setColor(Color.RED);
    canvas.drawCircle(rect.centerX(), rect.centerY(), circleRadius, paint);
    paint.setColor(lunarView.getMonthBackgroundColor());
    canvas.drawCircle(rect.centerX(), rect.centerY(), circleRadius - 4, paint);
  }

  /* handle date click event */
  private void handleClickEvent(int x, int y) {
    Region[][] monthRegion = getMonthRegion();
    for (int i = 0; i < monthRegion.length; i++) {
      for (int j = 0; j < DAYS_IN_WEEK; j++) {
        Region region = monthRegion[i][j];
        if (!region.contains(x, y)) {
          continue;
        }

        MonthDay monthDay = month.getMonthDay(i, j);
        if (monthDay == null) {
          return;
        }

        int day = monthDay.getCalendar().get(Calendar.DAY_OF_MONTH);

        if (monthDay.isCheckable()) {
          selectedIndex = i * DAYS_IN_WEEK + j;
          performDayClick();
          invalidate();
        } else {
          if (monthDay.getDayFlag() == MonthDay.PREV_MONTH_DAY) {
            lunarView.showPrevMonth(day);
          } else if (monthDay.getDayFlag() == MonthDay.NEXT_MONTH_DAY) {
            lunarView.showNextMonth(day);
          }
        }
        break;
      }
    }
  }

  /**
   * Perform day click event.
   */
  protected void performDayClick() {
    MonthDay monthDay = month.getMonthDay(selectedIndex);
    lunarView.dispatchDateClickListener(monthDay);
  }

  /**
   * Set selected day, the selected day will draw background.
   *
   * @param day selected day
   */
  protected void setSelectedDay(int day) {
    if (month.isMonthOfToday() && day == 0) {
      selectedIndex = month.getIndexOfToday();
    } else {
      int selectedDay = day == 0 ? 1 : day;
      selectedIndex = month.getIndexOfDayInCurMonth(selectedDay);
    }

    invalidate();

    if ((day == 0 && lunarView.getShouldPickOnMonthChange()) || day != 0) {
      performDayClick();
    }
  }
}