package jp.sourceforge.tamanegisoul.sa.util;

import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import jp.sourceforge.tamanegisoul.sa.action.AlarmReceiver;
import android.accounts.Account;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.widget.Toast;

/**
 * アラーム管理クラス
 */
public final class AppAlarmManager {

    private static AppAlarmManager sSingleton;

    public static synchronized AppAlarmManager getInstance(Context context) {
        if (sSingleton == null)
            sSingleton = new AppAlarmManager(context);
        return sSingleton;
    }

    private Context mContext;
    private Set<AppAlarmListener> mListeners;

    private AppAlarmManager(Context context) {
        super();
        mContext = context;
        mListeners = new HashSet<AppAlarmListener>();
    }

    public void registerAppAlarmListener(AppAlarmListener listener) {
        mListeners.add(listener);
    }

    public void unregisterAppAlarmListener(AppAlarmListener listener) {
        mListeners.remove(listener);
    }

    public Date getAlarmTime() {
        String alarmTime = PreferenceUtils.getString(mContext, PreferenceUtils.KEY_ALARM_TIME);
        return alarmTime == null ? null : FormatUtils.parseDateTime(alarmTime);
    }

    public void clearAlarmTime() {
        PreferenceUtils.remove(mContext, PreferenceUtils.KEY_ALARM_TIME);
    }

    /**
     * 次回アラームを設定する。
     */
    public void refreshAlarm() {
        // 時刻未設定の場合は何もしない
        if (PreferenceUtils.getString(mContext, PreferenceUtils.KEY_WEEKDAY_TIME) == null || PreferenceUtils.getString(mContext, PreferenceUtils.KEY_HOLIDAY_TIME) == null) {
            return;
        }
        // 次回アラーム時刻を計算
        Date nextAlarm = calculateNextAlarmTime().getTime();
        Date currentAlarm = getAlarmTime();
        boolean changed = false;
        if (nextAlarm == null) {
            changed = currentAlarm != null;
        } else {
            changed = !nextAlarm.equals(currentAlarm);
        }
        // 次回アラームが変わっていたらアラームを再設定
        if (changed) {
            Intent intent = new Intent(mContext, AlarmReceiver.class);
            intent.setData(Uri.parse("http://sourceforge.jp/projects/schedulealarm/alarm"));
            PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
            if (nextAlarm == null) {
                // アラーム解除
                alarmManager.cancel(pendingIntent);
                Toast.makeText(mContext, "アラームを解除しました", Toast.LENGTH_LONG).show();
                PreferenceUtils.setString(mContext, PreferenceUtils.KEY_ALARM_TIME, null);
            } else {
                // アラーム設定
                alarmManager.set(AlarmManager.RTC_WAKEUP, nextAlarm.getTime(), pendingIntent);
                Toast.makeText(mContext, String.format("アラームを%tF %tRに設定しました", nextAlarm, nextAlarm), Toast.LENGTH_LONG).show();
                PreferenceUtils.setString(mContext, PreferenceUtils.KEY_ALARM_TIME, FormatUtils.foarmatDateTime(nextAlarm));
            }
            for (AppAlarmListener listener : mListeners) {
                listener.alarmSet(nextAlarm);
            }
        }
    }

    public void setSnooze() {
        // 設定時刻
        Integer minute = PreferenceUtils.getInteger(mContext, PreferenceUtils.KEY_SNOOZE);
        if (minute != null) {
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.MINUTE, minute);
            Intent intent = new Intent(mContext, AlarmReceiver.class);
            intent.setData(Uri.parse("http://sourceforge.jp/projects/schedulealarm/alarm/snooze"));
            PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
            alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
            PreferenceUtils.setProgressSnooze(mContext, true);
        }
    }

    public void cancelSnooze() {
        Intent intent = new Intent(mContext, AlarmReceiver.class);
        intent.setData(Uri.parse("http://sourceforge.jp/projects/schedulealarm/alarm/snooze"));
        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(pendingIntent);
        PreferenceUtils.setProgressSnooze(mContext, false);
    }

    private Calendar calculateNextAlarmTime() {
        SQLiteDatabase db = new DBHelper(mContext).getReadableDatabase();
        try {
            // -alarmの日付(yyyy-MM-dd)
            Set<String> cancelDates = new HashSet<String>();
            // +alarmの日時(yyyy-MM-dd,HH:mm)
            Set<String[]> addDates = new HashSet<String[]>();
            Account account = AppAccountManager.getInstance(mContext).getAccount();
            if (account != null) {
                // 今日の0時から7日後までの予定を取得
                // SQLiteの関数はエポックからの秒（ミリ秒ではない）なので注意。dtstartはミリ秒。
                Cursor c = mContext.getContentResolver().query(DBHelper.URI_CALENDAR, new String[] { "_id", "title", "dtstart", "allDay" },
                        "_sync_account=? and dtstart>=strftime('%s', datetime('now','start of day')) || '000' and dtstart<=strftime('%s', datetime(datetime('now','start of day')),'7 days') || '000' and title like '%alarm%'", new String[] { account.name }, "dtstart");
                // REGZA phoneでNullPointerException
                // Cursorがnullなのかもしれない。。。
                if (c != null && c.moveToFirst()) {
                    do {
                        Date d = new Date(c.getLong(2));
                        if (c.getString(1).contains("-alarm")) {
                            cancelDates.add(FormatUtils.formatDate(d));
                        } else {
                            addDates.add(new String[] { FormatUtils.formatDate(d), FormatUtils.formatTime(d) });
                        }
                    } while (c.moveToNext());
                }
            }
            return calculateNextAlarmTime(Calendar.getInstance(), cancelDates, addDates);
        } finally {
            db.close();
        }
    }

    private Calendar calculateNextAlarmTime(Calendar date, Set<String> cancelDates, Set<String[]> addDates) {
        LogUtil.d("calculateNextAlarm date->%tF %tR", date, date);
        // 7日後までアラームなしの場合は、アラーム設定なしとする
        if ((date.getTimeInMillis() - new Date().getTime()) / 1000 / 60 / 60 / 24 > 7) {
            return null;
        }
        // -alarmの日付に含まれる場合は、次の日に進む
        if (cancelDates.contains(FormatUtils.formatDate(date))) {
            date.add(Calendar.DATE, 1);
            return calculateNextAlarmTime(date, cancelDates, addDates);
        }
        // +alarmの日付に含まれる場合は、その時刻にする
        {
            String dateStr = FormatUtils.formatDate(date);
            String timeStr = FormatUtils.formatTime(date);
            String time = null;
            boolean dateMatched = false;
            for (String[] addDate : addDates) {
                // +alarmの日付に一致
                if (addDate[0].equals(dateStr)) {
                    dateMatched = true;
                    // 未来時刻かつ最早であればその時刻にする
                    // 未来時刻の判定方法->明日以降または時刻が現在時刻以降
                    if ((FormatUtils.formatDate(Calendar.getInstance()).compareTo(addDate[0]) < 0 || addDate[1].compareTo(timeStr) > 0) && (time == null || addDate[1].compareTo(time) < 0)) {
                        time = addDate[1];
                    }
                }
            }
            if (time != null) {
                date.set(Calendar.HOUR_OF_DAY, Integer.parseInt(time.substring(0, 2)));
                date.set(Calendar.MINUTE, Integer.parseInt(time.substring(3, 5)));
                date.set(Calendar.SECOND, 0);
                date.set(Calendar.MILLISECOND, 0);
                return date;
            } else if (dateMatched) {
                // +alarmに日付一致したが未来時刻のスケジュールがないので、次の日に進む
                date.add(Calendar.DATE, 1);
                return calculateNextAlarmTime(date, cancelDates, addDates);
            }
        }

        // -alarm、+alarmに含まれない場合は、アプリの設定時刻にする
        {
            String key = isHoliday(date) ? PreferenceUtils.KEY_HOLIDAY_TIME : PreferenceUtils.KEY_WEEKDAY_TIME;
            Calendar appAlarm = Calendar.getInstance();
            appAlarm.setTime(PreferenceUtils.getTime(mContext, key));
            // 設定時刻の日付を比較時刻のものに合わせる
            for (int i : new int[] { Calendar.YEAR, Calendar.MONTH, Calendar.DATE }) {
                appAlarm.set(i, date.get(i));
            }
            // 設定時刻が比較時刻より後なら設定時刻にする。そうでないなら次の日に進む
            if (appAlarm.after(Calendar.getInstance())) {
                return appAlarm;
            } else {
                date.add(Calendar.DATE, 1);
                return calculateNextAlarmTime(date, cancelDates, addDates);
            }
        }
    }

    private boolean isHoliday(Calendar calendar) {
        SQLiteDatabase db = null;
        try {
            int day = calendar.get(Calendar.DAY_OF_WEEK);
            if (day == Calendar.SATURDAY || day == Calendar.SUNDAY) {
                return true;
            } else {
                db = new DBHelper(mContext).getReadableDatabase();
                Cursor c = db.query(DBHelper.T_HOLIDAY, null, DBHelper.C_HOLIDAY_DATE + "=?", new String[] { FormatUtils.formatDate(calendar.getTime()) }, null, null, null);
                return c.moveToFirst();
            }
        } finally {
            if (db != null)
                db.close();
        }
    }

}
