package jp.sourceforge.tamanegisoul.sa.util;

import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

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.os.Build;
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() {
        // 次回アラーム時刻を計算
        Date nextAlarm = calculateNextAlarmTime();
        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_INTERVAL);
        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 Date 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;
                if (Build.VERSION.SDK_INT < 8) {
                    c = mContext.getContentResolver().query(DBHelper.URI_CALENDAR_EVENTS, new String[] { "_id", "title", "dtstart", "allDay" },
                            "Calendars._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");
                } else {
                    c = mContext.getContentResolver().query(DBHelper.URI_CALENDAR_EVENTS, 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).endsWith("-alarm")) {
                            cancelDates.add(FormatUtils.formatDate(d));
                        } else if (c.getString(1).endsWith("+alarm")) {
                            addDates.add(new String[] { FormatUtils.formatDate(d), FormatUtils.formatTime(d) });
                        } else if (c.getString(1).endsWith("#alarm")) {
                            cancelDates.add(FormatUtils.formatDate(d));
                            addDates.add(new String[] { FormatUtils.formatDate(d), FormatUtils.formatTime(d) });
                        }
                    } while (c.moveToNext());
                }
            }
            return calculateNextAlarmTime(Calendar.getInstance(), cancelDates, addDates);
        } finally {
            db.close();
        }
    }

    private Date calculateNextAlarmTime(Calendar date, Set<String> cancelDates, Set<String[]> addDates) {
        LogUtil.d("calculateNextAlarm date->%tF %tR", date, date);
        // 30日後までアラームなしの場合は、アラーム設定なしとする
        if ((date.getTimeInMillis() - new Date().getTime()) / 1000 / 60 / 60 / 24 > 30) {
            return null;
        }
        // アラーム時刻リスト(HH:mm)
        SortedSet<String> alarmTimes = new TreeSet<String>();
        // -alarmの日付に含まれなければ、端末設定時刻も対象
        if (!cancelDates.contains(FormatUtils.formatDate(date))) {
            String key = isHoliday(date) ? PreferenceUtils.KEY_HOLIDAY_TIME : PreferenceUtils.KEY_WEEKDAY_TIME;
            String time = FormatUtils.formatTime(PreferenceUtils.getTime(mContext, key));
            if (time != null)
                alarmTimes.add(time);
        }
        // +alarmの日付に含まれる場合は、その時刻も対象
        String dateStr = FormatUtils.formatDate(date);
        for (String[] addDate : addDates) {
            if (addDate[0].equals(dateStr)) {
                alarmTimes.add(addDate[1]);
            }
        }
        // アラーム時刻リストが空でないなら評価
        if (alarmTimes.size() != 0) {
            if (FormatUtils.formatDate(date).compareTo(FormatUtils.formatDate(new Date())) > 0) {
                // 現在の評価日が今日以降なら最初の時刻にする
                return createDate(date, alarmTimes.first());
            } else {
                // リストのうち、現在時刻以降のものがあれば、その時刻にする
                String nowTimeStr = FormatUtils.formatTime(new Date());
                alarmTimes.add(nowTimeStr);
                SortedSet<String> tail = alarmTimes.tailSet(nowTimeStr);
                for (Iterator<String> itr = tail.iterator(); itr.hasNext();) {
                    String c = itr.next();
                    if (c.compareTo(nowTimeStr) > 0) {
                        return createDate(date, c);
                    }
                }
            }
        }
        // なければ次の日に進む。
        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);
            String holidays = PreferenceUtils.getString(mContext, PreferenceUtils.KEY_HOLIDAY_OF_WEEK);
            if (holidays != null && holidays.contains(String.valueOf(day))) {
                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();
        }
    }

    private Date createDate(Calendar date, String time) {
        Calendar value = Calendar.getInstance();
        value.setTime(FormatUtils.parseTime(time));
        for (int i : new int[] { Calendar.YEAR, Calendar.MONTH, Calendar.DATE }) {
            value.set(i, date.get(i));
        }
        return value.getTime();
    }
}
