/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*-
 * -*- coding: utf-8 -*-
 *
 * Copyright (C) 2020 KylinSoft Co., Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <QDebug>
#include <math.h>
#include <QTime>
#include <QDBusInterface>
#include <QDBusArgument>
#include <color-info.h>
#include <QThread>
#include "color-manager.h"

#define PLUGIN_COLOR_SCHEMA         "org.ukui.SettingsDaemon.plugins.color"
#define COLOR_KEY_LAST_COORDINATES  "night-light-last-coordinates"
#define COLOR_KEY_ENABLED           "night-light-enabled"
#define COLOR_KEY_ALLDAY            "night-light-allday"
#define COLOR_KEY_AUTO_THEME        "theme-schedule-automatic"
#define COLOR_KEY_TEMPERATURE       "night-light-temperature"
#define COLOR_KEY_AUTOMATIC         "night-light-schedule-automatic"
#define COLOR_KEY_AUTOMATIC_FROM    "night-light-schedule-automatic-from"
#define COLOR_KEY_AUTOMATIC_TO      "night-light-schedule-automatic-to"
#define COLOR_KEY_FROM              "night-light-schedule-from"
#define COLOR_KEY_TO                "night-light-schedule-to"
#define COLOR_KEY_ACTIVE            "active"
#define COLOR_KEY_DARK_MODE         "dark-mode"
#define COLOR_KEY_ENABLED_DM        "night-light-enabled-dm"
#define COLOR_KEY_ALLDAY_DM         "night-light-allday-dm"
#define COLOR_KEY_AUTOMATIC_DM      "night-light-schedule-automatic-dm"
#define COLOR_KEY_STYLE_NAME_DM     "style-name-dm"
#define COLOR_KEY_REAL_TIME_TEMPERATURE "real-time-temperature"
#define HAD_SET_EDU                  "had-set-edu"

#define COLOR_KEY_AUTO_THEME_DM     "theme-schedule-automatic-dm"
#define GTK_THEME_SCHEMA            "org.mate.interface"
#define GTK_THEME_KEY               "gtk-theme"

#define QT_THEME_SCHEMA             "org.ukui.style"
#define QT_THEME_KEY                "style-name"

#define HAD_READ_KWIN               "had-read-kwin-config"

#define KWIN_COLOR_ACTIVE            "Active"
#define KWIN_NIGHT_TEMP              "NightTemperature"
#define KWIN_COLOR_MODE              "Mode"
#define KWIN_COLOR_START             "EveningBeginFixed"
#define KWIN_COLOR_END               "MorningBeginFixed"
#define KWIN_CURRENT_TEMP            "CurrentColorTemperature"

#define USD_NIGHT_LIGHT_SCHEDULE_TIMEOUT    5       /* seconds */
#define USD_NIGHT_LIGHT_POLL_TIMEOUT        60      /* seconds */
#define USD_NIGHT_LIGHT_POLL_SMEAR          1       /* hours */
#define USD_NIGHT_LIGHT_SMOOTH_SMEAR        5.f     /* seconds */

#define USD_FRAC_DAY_MAX_DELTA              (1.f/60.f)     /* 1 minute */
#define USD_TEMPERATURE_MAX_DELTA           (10.f)

#define DESKTOP_ID "ukui-color-panel"
#include <sys/timerfd.h>
#include <unistd.h>

enum theme_status_switch{
    white_mode,
    black_mode
};

double hour_minute_to_value(int hour, int minute) {
    double value = (double)minute/60;
    return (double)hour + value;
}

ColorManager *ColorManager::mColorManager = nullptr;

ColorManager::ColorManager()
{
    forced = false;
    smooth_id = 0;
    m_darkModeChangedBySelf = false;
    smooth_timer = nullptr;
    disabled_until_tmw = false;
    datetime_override = NULL;
    geoclue_enabled = true;
    smooth_enabled  = true;

    cached_temperature = USD_COLOR_TEMPERATURE_DEFAULT;
    settings = new QGSettings (PLUGIN_COLOR_SCHEMA);
    cached_sunrise  = settings->get(COLOR_KEY_AUTOMATIC_TO).toDouble();
    cached_sunset   = settings->get(COLOR_KEY_AUTOMATIC_FROM).toDouble();
    gtk_settings = new QGSettings (GTK_THEME_SCHEMA);
    qt_settings = new QGSettings (QT_THEME_SCHEMA);
    m_NightChecktimer  = new QTimer(this);
    m_stopServiceTimer = new QTimer(this);
    m_stopServiceTimer->setSingleShot(true);
    connect(m_stopServiceTimer, SIGNAL(timeout()), this, SLOT(doStopServerTimer()));
    stopService();
    mColorGtkConfig = new UkuiGtkConfig();
}

ColorManager::~ColorManager()
{
    if (m_NightChecktimer) {
        delete m_NightChecktimer;
        m_NightChecktimer = nullptr;
    }
    if (settings) {
        delete settings;
        settings = nullptr;
    }
    if (gtk_settings) {
        delete gtk_settings;
        gtk_settings = nullptr;
    }
    if (qt_settings) {
        delete qt_settings;
        qt_settings = nullptr;
    }
    if (mColorState) {
        delete mColorState;
        mColorState = nullptr;
    }
    if (mColorProfiles) {
        delete mColorProfiles;
        mColorProfiles = nullptr;
    }
    if (mColorGtkConfig) {
        delete mColorGtkConfig;
        mColorGtkConfig = nullptr;
    }
}

ColorManager *ColorManager::ColorManagerNew()
{
    if (nullptr == mColorManager)
        mColorManager = new ColorManager();
    return  mColorManager;
}

GDateTime *ColorManager::NightLightGetDateTimeNow()
{
    if (datetime_override != NULL)
        return g_date_time_ref (datetime_override);
    return g_date_time_new_now_local();
}

bool ColorManager::NightLightSmoothCb (ColorManager *manager)
{
        double tmp;
        double frac;

        /* find fraction */
        frac = g_timer_elapsed (manager->smooth_timer, NULL) / USD_NIGHT_LIGHT_SMOOTH_SMEAR;
        if (frac >= 1.f) {
                manager->NightLightSetTemperatureInternal (manager->smooth_target_temperature);
                manager->smooth_id = 0;

                USD_LOG(LOG_DEBUG,"set Temp...%f == %f",tmp,manager->smooth_target_temperature);
                return G_SOURCE_REMOVE;
        }

        /* set new temperature step using log curve */
        tmp = manager->smooth_target_temperature - manager->cached_temperature;
        tmp *= frac;
        tmp += manager->cached_temperature;
        manager->NightLightSetTemperatureInternal (tmp);
//        USD_LOG(LOG_DEBUG,"set Temp...%f",tmp);
        return G_SOURCE_CONTINUE;
}

void ColorManager::PollSmoothCreate (double temperature)
{
        g_assert (smooth_id == 0);
        smooth_target_temperature = temperature;
        smooth_timer = g_timer_new ();
        smooth_id = g_timeout_add (50, (GSourceFunc)NightLightSmoothCb, this);
}

void ColorManager::PollSmoothDestroy ()
{
        if (smooth_id != 0) {
                g_source_remove (smooth_id);
                smooth_id = 0;
        }
        if (smooth_timer != NULL)
                g_clear_pointer (&smooth_timer, g_timer_destroy);
}

void ColorManager::NightLightSetTemperatureInternal (double temperature)
{
    if (ABS(cached_temperature - temperature) <= USD_TEMPERATURE_MAX_DELTA) {
//        USD_LOG(LOG_DEBUG,"set night light %f error ABS:%f delta:%f", temperature,ABS (cached_temperature - temperature),USD_TEMPERATURE_MAX_DELTA);
        if (cached_temperature != temperature) {
            cached_temperature = temperature;
            USD_LOG(LOG_DEBUG,".");
            stopService();
        }
        return;
    }
    if (mColorState == nullptr) {
        startService();
    }
    settings->set(COLOR_KEY_REAL_TIME_TEMPERATURE,temperature);
    cached_temperature = temperature;
    mColorState->ColorStateSetTemperature (cached_temperature);
}

void ColorManager::NightLightSetTemperature(double temperature)
{
    /* immediate */
    if (!smooth_enabled) {
            USD_LOG(LOG_DEBUG,"set night light %f", temperature);
            NightLightSetTemperatureInternal (temperature);
            return;
    }

    /* Destroy any smooth transition, it will be recreated if neccessary */
    PollSmoothDestroy ();

    /* small jump */
    if (ABS (temperature - cached_temperature) < USD_TEMPERATURE_MAX_DELTA) {
            NightLightSetTemperatureInternal (temperature);
            return;
    }

    /* smooth out the transition */
    PollSmoothCreate (temperature);
    USD_LOG(LOG_DEBUG,"set color temp to :%f",temperature);
}

void ColorManager::NightLightSetActive(bool active)
{
    cached_active = active;
    /* ensure set to unity temperature */
    if (!active){
        NightLightSetTemperature (USD_COLOR_TEMPERATURE_DEFAULT);
    }
}


static gdouble
deg2rad (gdouble degrees)
{
        return (M_PI * degrees) / 180.f;
}

static gdouble
rad2deg (gdouble radians)
{
        return radians * (180.f / M_PI);
}

/*
 * Formulas taken from https://www.esrl.noaa.gov/gmd/grad/solcalc/calcdetails.html
 *
 * The returned values are fractional hours, so 6am would be 6.0 and 4:30pm
 * would be 16.5.
 *
 * The values returned by this function might not make sense for locations near
 * the polar regions. For example, in the north of Lapland there might not be
 * a sunrise at all.
 */
bool NightLightGetSunriseSunset (GDateTime *dt,
                                double pos_lat,  double pos_long,
                                double *sunrise, double *sunset)
{
        g_autoptr(GDateTime) dt_zero = g_date_time_new_utc (1900, 1, 1, 0, 0, 0);
        GTimeSpan ts = g_date_time_difference (dt, dt_zero);

        g_return_val_if_fail (pos_lat <= 90.f && pos_lat >= -90.f, false);
        g_return_val_if_fail (pos_long <= 180.f && pos_long >= -180.f, false);

        double tz_offset = (double) g_date_time_get_utc_offset (dt) / G_USEC_PER_SEC / 60 / 60; // B5
        double date_as_number = ts / G_USEC_PER_SEC / 24 / 60 / 60 + 2;  // B7
        double time_past_local_midnight = 0;  // E2, unused in this calculation
        double julian_day = date_as_number + 2415018.5 +
                        time_past_local_midnight - tz_offset / 24;
        double julian_century = (julian_day - 2451545) / 36525;
        double geom_mean_long_sun = fmod (280.46646 + julian_century *
                       (36000.76983 + julian_century * 0.0003032), 360); // I2
        double geom_mean_anom_sun = 357.52911 + julian_century *
                        (35999.05029 - 0.0001537 * julian_century);  // J2
        double eccent_earth_orbit = 0.016708634 - julian_century *
                        (0.000042037 + 0.0000001267 * julian_century); // K2
        double sun_eq_of_ctr = sin (deg2rad (geom_mean_anom_sun)) *
                        (1.914602 - julian_century * (0.004817 + 0.000014 * julian_century)) +
                        sin (deg2rad (2 * geom_mean_anom_sun)) * (0.019993 - 0.000101 * julian_century) +
                        sin (deg2rad (3 * geom_mean_anom_sun)) * 0.000289; // L2
        double sun_true_long = geom_mean_long_sun + sun_eq_of_ctr; // M2
        double sun_app_long = sun_true_long - 0.00569 - 0.00478 *
                        sin (deg2rad (125.04 - 1934.136 * julian_century)); // P2
        double mean_obliq_ecliptic = 23 +  (26 +  ((21.448 - julian_century *
                        (46.815 + julian_century * (0.00059 - julian_century * 0.001813)))) / 60) / 60; // Q2
        double obliq_corr = mean_obliq_ecliptic + 0.00256 *
                        cos (deg2rad (125.04 - 1934.136 * julian_century)); // R2
        double sun_declin = rad2deg (asin (sin (deg2rad (obliq_corr)) *
                                            sin (deg2rad (sun_app_long)))); // T2
        double var_y = tan (deg2rad (obliq_corr/2)) * tan (deg2rad (obliq_corr / 2)); // U2
        double eq_of_time = 4 * rad2deg (var_y * sin (2 * deg2rad (geom_mean_long_sun)) -
                        2 * eccent_earth_orbit * sin (deg2rad (geom_mean_anom_sun)) +
                        4 * eccent_earth_orbit * var_y *
                                sin (deg2rad (geom_mean_anom_sun)) *
                                cos (2 * deg2rad (geom_mean_long_sun)) -
                        0.5 * var_y * var_y * sin (4 * deg2rad (geom_mean_long_sun)) -
                        1.25 * eccent_earth_orbit * eccent_earth_orbit *
                                sin (2 * deg2rad (geom_mean_anom_sun))); // V2
        double ha_sunrise = rad2deg (acos (cos (deg2rad (90.833)) / (cos (deg2rad (pos_lat)) *
                        cos (deg2rad (sun_declin))) - tan (deg2rad (pos_lat)) *
                        tan (deg2rad (sun_declin)))); // W2
        double solar_noon =  (720 - 4 * pos_long - eq_of_time + tz_offset * 60) / 1440; // X2
        double sunrise_time = solar_noon - ha_sunrise * 4 / 1440; //  Y2
        double sunset_time = solar_noon + ha_sunrise * 4 / 1440; // Z2

        /* convert to hours */
        if (sunrise != NULL)
                *sunrise = sunrise_time * 24;
        if (sunset != NULL)
                *sunset = sunset_time * 24;
        return true;
}

double NightLightFracDayFromDt (GDateTime *dt)
{
        return g_date_time_get_hour (dt) +
               (double) g_date_time_get_minute (dt) / 60.f +
               (double) g_date_time_get_second (dt) / 3600.f;
}

bool NightLightFracDayIsBetween (double  value,
                                          double  start,
                                          double  end)
{
        /* wrap end to the next day if it is before start,
         * considering equal values as a full 24h period
         */
        if (end <= start)
                end += 24;

        /* wrap value to the next day if it is before the range */
        if (value < start && value < end)
                value += 24;

        /* Check whether value falls into range; together with the 24h
         * wrap around above this means that TRUE is always returned when
         * start == end.
         */
        return value >= start && value < end;
}

static double
LinearInterpolate (double val1, double val2, double factor)
{
        g_return_val_if_fail (factor >= 0.f, -1.f);
        g_return_val_if_fail (factor <= 1.f, -1.f);
        return ((val1 - val2) * factor) + val2;
}

bool ColorManager::UpdateCachedSunriseSunset()
{
    bool ret = false;
    double latitude;
    double longitude;
    double sunrise;
    double sunset;
    g_autoptr(GVariant) tmp = NULL;
    g_autoptr(GDateTime) dt_now = NightLightGetDateTimeNow ();
    GSettings *setting = g_settings_new(PLUGIN_COLOR_SCHEMA);

    /* calculate the sunrise/sunset for the location */
    tmp = g_settings_get_value (setting, COLOR_KEY_LAST_COORDINATES);

    g_clear_object(&setting);

    g_variant_get (tmp, "(dd)", &latitude, &longitude);

    if (latitude > 90.f || latitude < -90.f)
            return false;
    if (longitude > 180.f || longitude < -180.f)
            return false;
    if (!NightLightGetSunriseSunset (dt_now, latitude, longitude,
                                               &sunrise, &sunset)) {
             USD_LOG(LOG_DEBUG,"failed to get sunset/sunrise for %.3f,%.3f",
                       longitude, longitude);
            return false;
    }
//    USD_LOG(LOG_DEBUG," get sunset/sunrise for %.3f,%.3f,%.3f~%.3f",
//              longitude, longitude,cached_sunset,cached_sunrise);
    /* anything changed */
    if (ABS (cached_sunrise - sunrise) > USD_FRAC_DAY_MAX_DELTA) {
            cached_sunrise = sunrise;
            ret = true;
            settings->set(COLOR_KEY_AUTOMATIC_TO, cached_sunrise);
            USD_LOG(LOG_DEBUG,"set cached_sunrise..%f.",cached_sunrise);
    }
    if (ABS (cached_sunset - sunset) > USD_FRAC_DAY_MAX_DELTA) {
        cached_sunset = sunset;
        ret = true;
        settings->set(COLOR_KEY_AUTOMATIC_FROM, cached_sunset);
        USD_LOG(LOG_DEBUG,"set cached_sunset..%f.",cached_sunset);
    }
    return ret;
}
/*Active:1,使能，0禁用。
 *0:全天，1跟随日出日落，2自定义
 *Mode:1 自定义
 *Mode:2--EveningBeginFixed（17:55:01）---跟随日出日落
 *Mode:3--全天
*/
bool ColorManager::ReadKwinColorTempConfig()
{
    QVector<ColorInfo> nightColor;
    if (settings->keys().contains(HAD_READ_KWIN)) {
        if (settings->get(HAD_READ_KWIN).toBool() == true) {
            USD_LOG(LOG_DEBUG,"Kwin had read over..");
            return false;
        }
    } else {
        USD_LOG(LOG_DEBUG,"can't find key:%s", HAD_READ_KWIN);
        return false;
    }

    QDBusInterface colorIft("org.ukui.KWin",
                            "/ColorCorrect",
                            "org.ukui.kwin.ColorCorrect",
                            QDBusConnection::sessionBus());

    if (!colorIft.isValid()) {
        USD_LOG(LOG_DEBUG, "org.ukui.kwin.ColorCorrect is not valid interface");
        return false;
    }

    QDBusMessage result = colorIft.call("nightColorInfo");
    const QDBusArgument &dbusArgs = result.arguments().at(0).value<QDBusArgument>().asVariant().value<QDBusArgument>();

    dbusArgs.beginArray();
    while (!dbusArgs.atEnd()) {
        ColorInfo color;
        dbusArgs >> color;
        nightColor.push_back(color);
    }
    dbusArgs.endArray();

    for (ColorInfo it : nightColor) {
        mNightConfig.insert(it.arg, it.out.variant());
    }
    if (mNightConfig[KWIN_NIGHT_TEMP].toInt() > 4000 &&  mNightConfig[KWIN_NIGHT_TEMP].toInt() < 6500) {
        settings->set(COLOR_KEY_TEMPERATURE, mNightConfig[KWIN_NIGHT_TEMP].toInt());
    }
    settings->set(COLOR_KEY_ENABLED,mNightConfig[KWIN_COLOR_ACTIVE].toBool());

    if (3 == mNightConfig[KWIN_COLOR_MODE].toInt()) {
        settings->set(COLOR_KEY_ALLDAY, true);
    } else if (2 == mNightConfig[KWIN_COLOR_MODE].toInt() && mNightConfig[KWIN_COLOR_START].toString() == "17:55:01"){
        settings->set(COLOR_KEY_AUTOMATIC, true);
    } else {
        QTime startTime = QTime::fromString(mNightConfig[KWIN_COLOR_START].toString(),"hh:mm:ss");
        QTime endTime = QTime::fromString(mNightConfig[KWIN_COLOR_END].toString(),"hh:mm:ss");

        settings->set(COLOR_KEY_FROM, hour_minute_to_value(startTime.hour(), startTime.minute()));
        settings->set(COLOR_KEY_TO, hour_minute_to_value(endTime.hour(), endTime.minute()));
    }

    USD_LOG_SHOW_PARAM1(mNightConfig[KWIN_COLOR_ACTIVE].toBool());
    USD_LOG_SHOW_PARAM1(mNightConfig[KWIN_COLOR_MODE].toInt());
    USD_LOG_SHOW_PARAMS(mNightConfig[KWIN_COLOR_START].toString().toLatin1().data());
    USD_LOG_SHOW_PARAMS(mNightConfig[KWIN_COLOR_END].toString().toLatin1().data());

    settings->set(HAD_READ_KWIN,true);
    mNightConfig[KWIN_COLOR_ACTIVE] = false;
    colorIft.call("setNightColorConfig", mNightConfig);

    mNightConfig[KWIN_NIGHT_TEMP] = mNightConfig[KWIN_CURRENT_TEMP];
    mNightConfig[KWIN_COLOR_ACTIVE] = false;
    colorIft.call("setNightColorConfig", mNightConfig);
    return true;
}

void ColorManager::checkTempWarmAndTheme()
{
    bool allDay = settings->get(COLOR_KEY_ALLDAY).toBool();
    bool enable = settings->get(COLOR_KEY_ENABLED).toBool();
    QString theme = qt_settings->get(QT_THEME_KEY).toString();
    if (allDay && enable && theme == "ukui-dark") {
        m_darkModeChangedBySelf = true;
        settings->set(COLOR_KEY_DARK_MODE, true);
        settings->apply();
    }
}

void ColorManager::NightLightRecheck(ColorManager *manager)
{
    double frac_day;
    double schedule_from = -1.f;
    double schedule_to = -1.f;
    double theme_from = -1.f;
    double theme_to = -1.f;
    double smear = USD_NIGHT_LIGHT_POLL_SMEAR; /* hours */
    int theme_now = -1;
    guint temperature;
    guint temp_smeared;
    GDateTime *dt_now = manager->NightLightGetDateTimeNow ();

    /* Forced mode, just set the temperature to night light.
     * Proper rechecking will happen once forced mode is disabled again */
    if (manager->forced) {
        temperature = manager->settings->get(COLOR_KEY_TEMPERATURE).toUInt();
        manager->NightLightSetTemperature (temperature);
        USD_LOG(LOG_DEBUG,"return .......");
        return;
    }

     manager->m_getGeoclueSuccess = manager->UpdateCachedSunriseSunset();//不需要每次进行计算，经纬度更换后才需要进行计算

    /* calculate the position of the sun */
    if (manager->settings->get(COLOR_KEY_AUTO_THEME).toBool()) {

        theme_to = manager->settings->get(COLOR_KEY_AUTOMATIC_TO).toDouble();
        theme_from = manager->settings->get(COLOR_KEY_AUTOMATIC_FROM).toDouble();

        /* get the current hour of a day as a fraction */
        frac_day = NightLightFracDayFromDt (dt_now);
        if (NightLightFracDayIsBetween (frac_day, theme_from, theme_to)) {
            manager->gtk_settings->set(GTK_THEME_KEY, "ukui-black");
            manager->qt_settings->set(QT_THEME_KEY, "ukui-dark");
        } else {
            manager->gtk_settings->set(GTK_THEME_KEY, "ukui-white");
            manager->qt_settings->set(QT_THEME_KEY, "ukui-light");
        }
    }

    if(!manager->settings->get(COLOR_KEY_ENABLED).toBool()){
        manager->NightLightSetActive (false);
        return;
    }

    if(manager->settings->get(COLOR_KEY_ALLDAY).toBool()){
        temperature = manager->settings->get(COLOR_KEY_TEMPERATURE).toUInt();
        manager->NightLightSetTemperature (temperature);

        return;
    }

    /* calculate the position of the sun */
    if (manager->settings->get(COLOR_KEY_AUTOMATIC).toBool()) {
        if (manager->cached_sunrise > 0.f && manager->cached_sunset > 0.f) {
            schedule_to   = manager->cached_sunrise;
            schedule_from = manager->cached_sunset;
            manager->settings->set(COLOR_KEY_AUTOMATIC_FROM, schedule_from);
            manager->settings->set(COLOR_KEY_AUTOMATIC_TO, schedule_to);
        } else {
            schedule_from = manager->settings->get(COLOR_KEY_AUTOMATIC_FROM).toDouble();
            schedule_to = manager->settings->get(COLOR_KEY_AUTOMATIC_TO).toDouble();
        }
    }

    /* fall back to manual settings */
    if (schedule_to < 0.f || schedule_from < 0.f) {
        schedule_from = manager->settings->get(COLOR_KEY_FROM).toDouble();
        schedule_to = manager->settings->get(COLOR_KEY_TO).toDouble();
    }

    /* get the current hour of a day as a fraction */
    frac_day = NightLightFracDayFromDt (dt_now);
//    //qDebug("fractional day = %.3f, limits = %.3f->%.3f",
//         frac_day, schedule_from, schedule_to);

    /* disabled until tomorrow */
    if (manager->disabled_until_tmw) {
        GTimeSpan time_span;
        bool reset = false;

        time_span = g_date_time_difference (dt_now, manager->disabled_until_tmw_dt);

        /* Reset if disabled until tomorrow is more than 24h ago. */
        if (time_span > (GTimeSpan) 24 * 60 * 60 * 1000000) {
            //qDebug("night light disabled until tomorrow is older than 24h, resetting disabled until tomorrow");
            reset = true;
        } else if (time_span > 0) {
            /* Or if a sunrise lies between the time it was disabled and now. */
            gdouble frac_disabled;
            frac_disabled = NightLightFracDayFromDt (manager->disabled_until_tmw_dt);
            if (frac_disabled != frac_day &&
                NightLightFracDayIsBetween (schedule_to,
                                            frac_disabled,
                                            frac_day)) {
                    //qDebug("night light sun rise happened, resetting disabled until tomorrow");
                    reset = true;
            }
        }

        if (reset) {
                manager->disabled_until_tmw = false;
                g_clear_pointer(&manager->disabled_until_tmw_dt, g_date_time_unref);
        } else {
                USD_LOG(LOG_DEBUG,"night light still day-disabled, resetting");
                manager->NightLightSetTemperature (USD_COLOR_TEMPERATURE_DEFAULT);
                return;
        }
    }

    /* lower smearing period to be smaller than the time between start/stop */
    smear = MIN (smear,
                MIN (ABS (schedule_to - schedule_from),
                     24 - ABS (schedule_to - schedule_from)));

    if (!NightLightFracDayIsBetween (frac_day,
                                     schedule_from - smear,
                                     schedule_to)) {

        manager->NightLightSetActive (false);
        return;
    }

    /* smear the temperature for a short duration before the set limits
    *
    *   |----------------------| = from->to
    * |-|                        = smear down
    *                        |-| = smear up
    *
    * \                        /
    *  \                      /
    *   \--------------------/
    */
    temperature = manager->settings->get(COLOR_KEY_TEMPERATURE).toUInt();
    if (smear < 0.01) {
        /* Don't try to smear for extremely short or zero periods */
        temp_smeared = temperature;
    } else if (NightLightFracDayIsBetween (frac_day,
                                           schedule_from - smear,
                                           schedule_from)) {
        double factor = 1.f - ((frac_day - (schedule_from - smear)) / smear);
        temp_smeared = LinearInterpolate (USD_COLOR_TEMPERATURE_DEFAULT,
                                          temperature, factor);
        USD_LOG(LOG_DEBUG,"val1:%d val2:%d factor:%f,frac_day:%f,schedule_from:%f",USD_COLOR_TEMPERATURE_DEFAULT, temperature, factor,
                frac_day,schedule_from);
    } else if (NightLightFracDayIsBetween (frac_day,
                                           schedule_to - smear,
                                           schedule_to)) {
        double factor = (frac_day - (schedule_to - smear)) / smear;
        temp_smeared = LinearInterpolate (USD_COLOR_TEMPERATURE_DEFAULT,
                                          temperature, factor);
        USD_LOG(LOG_DEBUG,"val1:%d val2:%d factor:%f,frac_day:%d,schedule_from:%d",USD_COLOR_TEMPERATURE_DEFAULT, temperature, factor,
                frac_day,schedule_from);
    } else {
        temp_smeared = temperature;
    }
    USD_LOG(LOG_DEBUG,"temp_smeared:%d ...%d", temp_smeared, USD_COLOR_TEMPERATURE_DEFAULT-temp_smeared);
    manager->NightLightSetActive (true);
    manager->NightLightSetTemperature (temp_smeared);
}

void ColorManager::OnLocationNotify(GClueSimple *simple,
                                    GParamSpec *pspec,
                                    gpointer user_data)
{
    GClueLocation *location;
    gdouble latitude, longitude;
    ColorManager *manager = (ColorManager *)user_data;
    location = gclue_simple_get_location (simple);
    latitude = gclue_location_get_latitude (location);
    longitude = gclue_location_get_longitude (location);
    GSettings *setting = g_settings_new(PLUGIN_COLOR_SCHEMA);
    /* calculate the sunrise/sunset for the location */
    g_settings_set_value (setting,
                          COLOR_KEY_LAST_COORDINATES,
                          g_variant_new ("(dd)", latitude, longitude));
    g_clear_object(&setting);

    // //qDebug("got geoclue latitude %f, longitude %f", latitude, longitude);

    /* recheck the levels if the location changed significantly */
    if (manager->UpdateCachedSunriseSunset ()) {
        manager->NightLightRecheck (manager);
    }

//    USD_LOG(LOG_DEBUG,"set latitude:%f  longitude:%f,%f-%f", latitude, longitude,manager->cached_sunset,manager->cached_sunrise);
   //获取成功一次后停止服务。。
    manager->stopService();
}
void ColorManager::OnGeoclueSimpleReady(GObject *source_object,
                                        GAsyncResult *res,
                                        gpointer user_data)
{
    GClueSimple *geoclue_simple;
    ColorManager *manager = (ColorManager *)user_data;
    g_autoptr(GError) error = NULL;

    geoclue_simple = gclue_simple_new_finish (res, &error);
    if (geoclue_simple == NULL) {
            if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
                     USD_LOG(LOG_DEBUG,"Failed to connect to GeoClue2 service: %s", error->message);
            return;
    }

    manager->geoclue_simple = geoclue_simple;
    manager->geoclue_client = gclue_simple_get_client (manager->geoclue_simple);
    g_object_set (G_OBJECT (manager->geoclue_client),
                  "time-threshold", 60*60, NULL); /* 1 hour */

    g_signal_connect (manager->geoclue_simple, "notify::location",
                      G_CALLBACK (OnLocationNotify), user_data);

    OnLocationNotify (manager->geoclue_simple, NULL, user_data);
}

void ColorManager::StartGeoclue()
{
    cancellable = g_cancellable_new ();
    gclue_simple_new (DESKTOP_ID,
                      GCLUE_ACCURACY_LEVEL_CITY,
                      cancellable,
                      OnGeoclueSimpleReady,
                      this);
}

void ColorManager::StopGeoclue()
{
    g_cancellable_cancel (cancellable);
    g_clear_object (&cancellable);

    if (geoclue_client != NULL) {
            gclue_client_call_stop (geoclue_client, NULL, NULL, NULL);
            geoclue_client = NULL;
    }
    g_clear_object (&geoclue_simple);
}

void ColorManager::startService()
{
        QDBusInterface iface("com.settings.daemon.qt.systemdbus", \
                             "/", \
                             "com.settings.daemon.interface", \
                             QDBusConnection::systemBus());
        if (iface.isValid()) {
            iface.call("startColorService");
            USD_LOG(LOG_DEBUG,"start geoclue");
        }
        USD_LOG(LOG_DEBUG,"start ...");

        QThread::msleep(200);
        if(mColorState ==nullptr) {
            mColorState    = new ColorState();
        }
        if (mColorProfiles == nullptr) {
            mColorProfiles = new ColorProfiles();
        }
        StartGeoclue();
        mColorProfiles->ColorProfilesStart();
        mColorState->ColorStateStart();
        NightLightRecheck(this);

}

void ColorManager::stopService()
{
    m_stopServiceTimer->start(10000);
}

bool ColorManager::getNetworkState()
{
    QDBusInterface iface("org.freedesktop.NetworkManager", \
                         "/org/freedesktop/NetworkManager", \
                         "org.freedesktop.NetworkManager", \
                         QDBusConnection::systemBus());

    if (iface.isValid()) {
      USD_LOG(LOG_DEBUG,"network state:%d",iface.property("State").toInt());
    } else {
       USD_LOG(LOG_DEBUG,"network state:xx!!");
    }

}

void ColorManager::SettingsChangedCb(QString key)
{
    bool darkMode = settings->get(COLOR_KEY_DARK_MODE).toBool();
    bool ret;
    if (key.contains("-dm") || key == COLOR_KEY_REAL_TIME_TEMPERATURE) {
        return;
    }

    //外部修改，则直接退出夜间模式。
    if (key == COLOR_KEY_ALLDAY || key == COLOR_KEY_ENABLED) {
        ret = settings->get(key).toBool();
        if (darkMode && false == ret) {
            m_darkModeChangedBySelf = true;
            settings->set(COLOR_KEY_DARK_MODE,false);
            settings->apply();
        }
        USD_LOG(LOG_DEBUG, "=============>key:%s:%d,dm:%d",key.toLatin1().data(),ret,darkMode);
    } else if (key == COLOR_KEY_AUTOMATIC) {
        ret = settings->get(key).toBool();
        if (darkMode && true == ret) {
            m_darkModeChangedBySelf = true;
            settings->set(COLOR_KEY_DARK_MODE,false);
            settings->apply();
        }
         USD_LOG(LOG_DEBUG, "=============>key:%s:%d,dm:%d",key.toLatin1().data(),ret,darkMode);
    } else if (key == COLOR_KEY_AUTO_THEME) {
        ret = settings->get(key).toBool();
        if (darkMode && true == ret) {
            m_darkModeChangedBySelf = true;
            settings->set(COLOR_KEY_DARK_MODE,false);
            settings->apply();
        }
        USD_LOG(LOG_DEBUG, "=============>key:%s:%d,dm:%d",key.toLatin1().data(),ret,darkMode);
    }

    if (key == COLOR_KEY_DARK_MODE) {
        if (m_darkModeChangedBySelf) {
            USD_LOG(LOG_DEBUG, "skip it....");
            m_darkModeChangedBySelf = false;
            return;
        }
        if (settings->get(key).toBool()) {//进入夜间模式
            settings->delay();
            settings->set(COLOR_KEY_ALLDAY_DM, settings->get(COLOR_KEY_ALLDAY).toBool());
            settings->set(COLOR_KEY_ENABLED_DM, settings->get(COLOR_KEY_ENABLED).toBool());
            settings->set(COLOR_KEY_AUTOMATIC_DM, settings->get(COLOR_KEY_AUTOMATIC).toBool());
            settings->set(COLOR_KEY_STYLE_NAME_DM, qt_settings->get(QT_THEME_KEY).toString());
            settings->set(COLOR_KEY_AUTO_THEME_DM, settings->get(COLOR_KEY_AUTO_THEME).toString());//四个任意一个改变则退出夜间模式。

            settings->set(COLOR_KEY_ALLDAY, true);
            settings->set(COLOR_KEY_ENABLED, true);
            settings->set(COLOR_KEY_AUTOMATIC, false);

            settings->set(COLOR_KEY_AUTO_THEME, false);
            qt_settings->set(QT_THEME_KEY, "ukui-dark");
            gtk_settings->set(GTK_THEME_KEY, "ukui-black");
            settings->apply();
            USD_LOG(LOG_DEBUG, "enter dark mode");
        } else {//退出夜间模式1
            settings->delay();
            settings->set(COLOR_KEY_ALLDAY, settings->get(COLOR_KEY_ALLDAY_DM).toBool());
            settings->set(COLOR_KEY_ENABLED, settings->get(COLOR_KEY_ENABLED_DM).toBool());
            settings->set(COLOR_KEY_AUTOMATIC, settings->get(COLOR_KEY_AUTOMATIC_DM).toBool());
            settings->set(COLOR_KEY_AUTO_THEME, settings->get(COLOR_KEY_AUTO_THEME_DM).toBool());

            if (false == settings->get(COLOR_KEY_AUTO_THEME).toBool()) {
                if (settings->get(COLOR_KEY_STYLE_NAME_DM).toString() == "ukui-default") {
                    qt_settings->set(QT_THEME_KEY, "ukui-default");
                    gtk_settings->set(GTK_THEME_KEY, "ukui-white");
                } else if(settings->get(COLOR_KEY_STYLE_NAME_DM).toString() == "ukui-light"){
                    qt_settings->set(QT_THEME_KEY, "ukui-light");
                    gtk_settings->set(GTK_THEME_KEY, "ukui-white");
                } else {
                    qt_settings->set(QT_THEME_KEY, "ukui-dark");
                    gtk_settings->set(GTK_THEME_KEY, "ukui-black");
                }
            } else {

            }
            settings->apply();
            USD_LOG(LOG_DEBUG, "exit dark mode");
        }
    }

    if(key == COLOR_KEY_AUTOMATIC_FROM || key == COLOR_KEY_AUTOMATIC_TO){
        USD_LOG(LOG_DEBUG,"KEY:%s",key.toLatin1().data());
        NightLightRecheck(this);
        return;
    }

    USD_LOG(LOG_DEBUG,"KEY:%s",key.toLatin1().data());

    if (key == COLOR_KEY_ACTIVE) {
        USD_LOG(LOG_DEBUG,"get active.");
        if (!settings->get(key).toBool()) {
            if (mColorState) {
                delete mColorState;
                mColorState = nullptr;
            }
            if (mColorProfiles) {
                delete mColorProfiles;
                mColorProfiles = nullptr;
            }
            USD_LOG(LOG_DEBUG,".");
            stopService();
        }
        return;
    }

//    USD_LOG(LOG_DEBUG,"KEY:%s",key.toLatin1().data());

    NightLightRecheck(this);
    if (cached_temperature == USD_COLOR_TEMPERATURE_DEFAULT) {
        if (mColorState == nullptr) {
            startService();
            QThread::msleep(300);
        }
        mColorState->ColorStateSetTemperature (cached_temperature);
    } else {
        if (mColorState == nullptr) {
            startService();
        }
        mColorState->ColorStateSetTemperature (cached_temperature);
    }

}

void ColorManager::qtSetingsChangedCb(QString key)
{
    if (key == QT_THEME_KEY) {
        if (qt_settings->get(key).toString() != "ukui-dark") {
            if (settings->get(COLOR_KEY_DARK_MODE).toBool()) {
                m_darkModeChangedBySelf = true;
                settings->set(COLOR_KEY_STYLE_NAME_DM, qt_settings->get(QT_THEME_KEY).toString());
                settings->set(COLOR_KEY_DARK_MODE,false);
                settings->apply();
            }
        }
        checkTempWarmAndTheme();
    }
}

bool ColorManager::ColorManagerStart()
{
    USD_LOG(LOG_DEBUG,"--Color manager start--");
    int ms = 2000;

    if (UsdBaseClass::isEdu())
    {
        if (!settings->get(HAD_SET_EDU).toBool()) {
            settings->set(COLOR_KEY_TEMPERATURE, 5150);
            settings->set(COLOR_KEY_ALLDAY, true);
            settings->set(COLOR_KEY_AUTOMATIC, false);
            settings->set(COLOR_KEY_TEMPERATURE, 5150);
            settings->set(HAD_SET_EDU,true);
             USD_LOG(LOG_DEBUG,"--edu first  start--");
        }
        ms = 100;
        USD_LOG(LOG_DEBUG,"--Color edu start--");
    }

    if (!settings->get(HAD_READ_KWIN).toBool()){
        if (false == ReadKwinColorTempConfig()) {
            ms = 100;
        }
    }

    QTimer::singleShot(ms, this, [=](){
        connect(m_NightChecktimer, SIGNAL(timeout()), this, SLOT(checkTime()));
        m_NightChecktimer->start(USD_NIGHT_LIGHT_POLL_TIMEOUT*1000);
        connect(settings,SIGNAL(changed(QString)),this,SLOT(SettingsChangedCb(QString)));
        SettingsChangedCb(COLOR_KEY_ENABLED);
        m_pNetworkBus = new QDBusInterface("org.freedesktop.NetworkManager", \
                                  "/org/freedesktop/NetworkManager", \
                                  "org.freedesktop.NetworkManager", \
                                  QDBusConnection::systemBus());

        if (m_pNetworkBus->property("State").toInt() == 70) {
            USD_LOG(LOG_DEBUG,"network connect success");
            startService();
            StartGeoclue();
        } else {
            connect(m_pNetworkBus, SIGNAL(StateChanged(uint)), this, SLOT(doNetworkStateCanged(uint)));
        }
    });
    return  true;
}

void ColorManager::checkTime()
{
    NightLightRecheck (this);
}

void ColorManager::doStopServerTimer()
{
        QDBusInterface iface("com.settings.daemon.qt.systemdbus", \
                             "/", \
                             "com.settings.daemon.interface", \
                             QDBusConnection::systemBus());

        if (iface.isValid()) {
            iface.call("stopColorService");
            USD_LOG(LOG_DEBUG,"stopService");
        }

        QThread::msleep(200);
        if (mColorState) {
            delete mColorState;
            mColorState = nullptr;
        }
        if (mColorProfiles) {
            delete mColorProfiles;
            mColorProfiles = nullptr;
        }
        USD_LOG(LOG_DEBUG,"stop ...");
}

void ColorManager::doNetworkStateCanged(uint state)
{
    if (m_lastNetworkState != state && state == 70) {
        m_lastNetworkState = state;
        USD_LOG(LOG_DAEMON,"network had ready");
        startService();
        StartGeoclue();
    }
}

void ColorManager::ColorManagerStop()
{
    USD_LOG(LOG_DEBUG,"Color manager stop");
    mColorProfiles->ColorProfilesStop();
    mColorState->ColorStateStop();
    StopGeoclue();
}

