// Berkeley Open Infrastructure for Network Computing
// http://boinc.berkeley.edu
// Copyright (C) 2005 University of California
//
// This is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any later version.
//
// This software 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 Lesser General Public License for more details.
//
// To view the GNU Lesser General Public License visit
// http://www.gnu.org/copyleft/lesser.html
// or write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#ifdef _WIN32
#include "boinc_win.h"
#else
#include "config.h"
#endif

#include "client_state.h"
#include "client_msgs.h"
#include "ss_logic.h"
#include "util.h"

#define SS_CHANGE_PERIOD    600
    // if > 1 CPUs, change screensaver every 600 secs

SS_LOGIC::SS_LOGIC() {
    do_ss = false;
    blank_time = 0;
    ack_deadline = 0;
    ss_status = 0;
}

void SS_LOGIC::ask_app(ACTIVE_TASK* atp, GRAPHICS_MSG& m) {
    atp->request_graphics_mode(m);
    atp->is_ss_app = true;
    ack_deadline = gstate.now + 30.0;
    if (log_flags.scrsave_debug) {
        msg_printf(0, MSG_INFO,
            "[scrsave_debug] SS_LOGIC::ask_app(): starting %s current time %f deadline %f",
            atp->result->name, gstate.now, ack_deadline
        );
    }
}

// called in response to a set_screensaver_mode RPC with <enabled>.
// Start providing screensaver graphics
//
void SS_LOGIC::start_ss(GRAPHICS_MSG& m, double new_blank_time) {
    if (do_ss) return;
    do_ss = true;
    ss_status = SS_STATUS_ENABLED;

    blank_time = new_blank_time;
    gstate.active_tasks.save_app_modes();
    gstate.active_tasks.hide_apps();

    m.mode = MODE_FULLSCREEN;
    saved_graphics_msg = m;
}

// Stop providing screensaver graphics
// called:
// 1) in response to a set_screensaver_mode RPC without <enabled>
// 2) from ACTIVE_TASK::check_graphics_mode_ack() when mode == MODE_QUIT
//
void SS_LOGIC::stop_ss() {
    if (!do_ss) return;
    reset();
    do_ss = false;
    ss_status = SS_STATUS_QUIT;
    if (log_flags.scrsave_debug) {
        msg_printf(0, MSG_INFO,
            "[scrsave_debug] SS_LOGIC::stop_ss(): stopping screen saver"
        );
    }
    gstate.active_tasks.restore_apps();
}

// If an app is acting as screensaver, tell it to stop.
// Called from CLIENT_STATE::schedule_cpus()
//
void SS_LOGIC::reset() {
    GRAPHICS_MSG m;

    m.mode = MODE_HIDE_GRAPHICS;
    ACTIVE_TASK* atp = gstate.active_tasks.get_ss_app();
    if (atp) {
        if (log_flags.scrsave_debug) {
            msg_printf(0, MSG_INFO,
                "[scrsave_debug] SS_LOGIC::reset(): resetting %s", atp->result->name
            );
        }
        atp->request_graphics_mode(m);
        atp->is_ss_app = false;
        atp->graphics_mode_ack_timeout = gstate.now;
    }
}

// called 1X per second
//
void SS_LOGIC::poll() {
    ACTIVE_TASK* atp, *new_atp=0;
    GRAPHICS_MSG m;
    static double last_time=0;
    static double ss_change_time = 0;

    double dt = gstate.now - last_time;
    if (dt < 1) return;
    last_time = gstate.now;

#if 0
    // if you want to debug screensaver functionality...
#define BLANK_DELAY 15 /* seconds */
#define SAVER_PERIOD 7 /* seconds */
    GRAPHICS_MSG gm;
    static int foo=0;
    foo++;
    if (foo == (SAVER_PERIOD)) {
        msg_printf(0, MSG_INFO,
            "starting screensaver"
        );
        start_ss(gm, time(0)+BLANK_DELAY);
    } 
    if (foo >= ((2*SAVER_PERIOD)+BLANK_DELAY)) {
        msg_printf(0, MSG_INFO,
            "resetting screensaver counter"
        );
        stop_ss();
        foo = 0;
    }
#endif

    if (!do_ss) return;

    if (gstate.suspend_reason & (~SUSPEND_REASON_CPU_USAGE_LIMIT)) {
        reset();
        ss_status = SS_STATUS_BOINCSUSPENDED;
        return;
    }

    if (gstate.disable_graphics) {
        reset();
        ss_status = SS_STATUS_DAEMONALLOWSNOGRAPHICS;
        return;
    }


    // check if it's time to go to black screen
    //
    if (blank_time && (gstate.now > blank_time)) {
        if (SS_STATUS_BLANKED != ss_status) {
            if (log_flags.scrsave_debug) {
                msg_printf(0, MSG_INFO,
                    "[scrsave_debug] SS_LOGIC::poll(): going to black"
                );
            }
            reset();
            ss_status = SS_STATUS_BLANKED;
        }
    } else {
        atp = gstate.active_tasks.get_ss_app();
        if (atp) {
            bool stop_app_ss = false;
            if (atp->graphics_mode_acked == MODE_FULLSCREEN) {
                if (atp->scheduler_state != CPU_SCHED_SCHEDULED) {
                    if (log_flags.scrsave_debug) {
                        msg_printf(0, MSG_INFO,
                            "[scrsave_debug] SS_LOGIC::poll(): app %s not scheduled\n",
                            atp->result->name
                        );
                    }
                    stop_app_ss = true;
                } else if (gstate.now - ss_change_time > SS_CHANGE_PERIOD) {
                    new_atp = gstate.get_next_graphics_capable_app();
                    if (new_atp && (new_atp != atp)) {
                        stop_app_ss = true;
                    }
                }
            } else {
                if (gstate.now > ack_deadline) {
                    if (log_flags.scrsave_debug) {
                        msg_printf(0, MSG_INFO,
                            "[scrsave_debug] SS_LOGIC::poll(): app %s is no longer fullscreen and passed ack deadline, current time %f deadline %f",
                            atp->result->name, gstate.now, ack_deadline
                        );
                    }
                    stop_app_ss = true;
                }
            }
            if (!stop_app_ss) return;

            // tell app not to do SSG any more
            //
            m.mode = MODE_HIDE_GRAPHICS;
            atp->request_graphics_mode(m);
            atp->is_ss_app = false;
        }

        // here if no app currently doing SSG
        // try to find one.
        //
        if (new_atp) {
            atp = new_atp;
        } else {
            atp = gstate.get_next_graphics_capable_app();
        }

        if (atp) {
            if (log_flags.scrsave_debug) {
                msg_printf(0, MSG_INFO,
                    "[scrsave_debug] SS_LOGIC::poll(): picked %s, request restart\n",
                    atp->result->name
                );
            }
            ask_app(atp, saved_graphics_msg);
            ss_status = SS_STATUS_ENABLED;
            ss_change_time = gstate.now;
        } else {
            if (log_flags.scrsave_debug) {
                msg_printf(0, MSG_INFO,
                    "[scrsave_debug] SS_LOGIC::poll(): no app found"
                );
            }
            if (gstate.active_tasks.active_tasks.size()==0) {
                if (gstate.projects.size()>0) {
                    ss_status = SS_STATUS_NOAPPSEXECUTING;
                } else {
                    ss_status = SS_STATUS_NOPROJECTSDETECTED;
                }
            } else {
                ss_status = SS_STATUS_NOGRAPHICSAPPSEXECUTING;
            }
        }
    }
}

const char *BOINC_RCSID_dd5060e766 = "$Id: ss_logic.C 12053 2007-02-08 16:38:02Z davea $";
