#!/usr/bin/python
#
# This is major work in progress.  Please don't grimace or yell; it
# upsets the chimpanzees.
#
# CPU speed preferences applet
#
# Copyright 2003 Bryan O'Sullivan <bos@serpentine.com>
#
# This software is licensed under the GNU General Public License.
# See COPYING for details.

import cPickle as pickle
import gnome
import gtk
import os
import speedfreq as sf
import time


USER_HZ = 100.0
KHz = 1000.0
MHz = KHz * KHz


class Policy(object):
    def __init__(self, name, policy, button, prefs):
        self.name = name
        self.policy = policy
        self.button = gtk.RadioButton(button, self.name)
        self.button.connect('toggled', self.radio_toggle)
        self.button.show()
        self.prefs = prefs

    def update(self, pol):
        self.button.set_active(True)

    def radio_toggle(self, widget):
        if widget.get_active():
            print 'New policy: %s' % self
            self.active()
        else:
            self.inactive()

    def active(self):
        pass

    def inactive(self):
        pass

    def __repr__(self):
        return self.name


class PowerSave(Policy):
    def __init__(self, *args, **kwargs):
        super(PowerSave, self).__init__('Save Power',
                                        sf.POL_POWERSAVE,
                                        *args, **kwargs)
    def active(self):
        sf.pol_powersave()


class Performance(Policy):
    def __init__(self, *args, **kwargs):
        super(Performance, self).__init__('Maximise Performance',
                                          sf.POL_PERFORMANCE,
                                          *args, **kwargs)
    def active(self):
        sf.pol_performance()


class ValuedPolicy(Policy):
    def __init__(self, *args, **kwargs):
        super(ValuedPolicy, self).__init__(*args, **kwargs)
        self.min_mhz = self.prefs.info.min / KHz
        self.max_mhz = self.prefs.info.max / KHz

    def active(self):
        self.prefs.valsep.show()
        self.prefs.valbox.show()

    def inactive(self):
        self.prefs.valsep.hide()
        self.prefs.valbox.hide()


class Fixed(ValuedPolicy):
    def __init__(self, *args, **kwargs):
        super(Fixed, self).__init__('Fixed Frequency',
                                    sf.POL_FIXED,
                                    *args, **kwargs)
        self.freq = None
        self.adj = gtk.Adjustment(self.min_mhz, self.min_mhz,
                                  self.max_mhz, 100)
        self.scale = gtk.HScale(self.adj)
        self.scale.set_update_policy(gtk.UPDATE_DELAYED)
        self.scale.set_digits(0)
        self.adj.connect('value_changed', self.value_changed)

        self.prefs.valbox.pack_start(self.scale, True)

    def update(self, pol):
        self.adj.value = pol.freq / KHz
        super(Fixed, self).update(pol)

    def value_changed(self, adj):
        val = adj.value * KHz
        if val == self.freq:
            return
        sf.pol_fixed(val)
        self.freq = val

    def active(self):
        super(Fixed, self).active()
        self.scale.show()
        self.value_changed(self.adj)

    def inactive(self):
        super(Fixed, self).inactive()
        self.scale.hide()


class Dynamic(ValuedPolicy):
    def __init__(self, *args, **kwargs):
        super(Dynamic, self).__init__('Dynamic',
                                      sf.POL_DYNAMIC,
                                      *args, **kwargs)
        self.min = None
        self.max = None

        self.min_adj = gtk.Adjustment(self.min_mhz, self.min_mhz,
                                      self.max_mhz, 100)
        self.min_adj.connect('value_changed', self.value_changed)
        self.max_adj = gtk.Adjustment(self.max_mhz, self.min_mhz,
                                      self.max_mhz, 100)
        self.max_adj.connect('value_changed', self.value_changed)

        self.min_scale = gtk.HScale(self.min_adj)
        self.min_scale.set_update_policy(gtk.UPDATE_DELAYED)
        self.min_scale.set_digits(0)
        self.max_scale = gtk.HScale(self.max_adj)
        self.max_scale.set_update_policy(gtk.UPDATE_DELAYED)
        self.max_scale.set_digits(0)

        self.prefs.valbox.pack_start(self.min_scale, True)
        self.prefs.valbox.pack_start(self.max_scale, True)

    def update(self, pol):
        self.min_adj.value = pol.min / KHz
        self.max_adj.value = pol.max / KHz
        super(Dynamic, self).update(pol)

    def value_changed(self, adj = None):
        if adj is self.min_adj:
            self.max_adj.lower = self.min_adj.value + 1
        else:
            self.min_adj.upper = self.max_adj.value - 1
        if self.min == self.min_adj.value and self.max == self.max_adj.value:
            return
        self.min = self.min_adj.value
        self.max = self.max_adj.value
        sf.pol_dynamic(self.min_adj.value * KHz,
                       self.max_adj.value * KHz)

    def active(self):
        super(Dynamic, self).active()
        self.min_scale.show()
        self.max_scale.show()
        self.value_changed()

    def inactive(self):
        super(Dynamic, self).inactive()
        self.min_scale.hide()
        self.max_scale.hide()


_policies = (
    PowerSave,
    Performance,
    Fixed,
    Dynamic
    )


class Prefs(object):
    def delete_event(self, widget, event, data = None):
        return self.close_app()

    def show_help(self, thingy):
        print 'help!', thingy
        pass

    def close_app(self, thingy = None):
        sf.disconnect()
        gtk.main_quit()
        return False

    def mon_event(self, fd, cond):
        evt = sf.poll(0)
        if isinstance(evt, sf.Policy):
            self.update_policy(evt)
        elif isinstance(evt, sf.CpuInfo):
            self.update_info(evt)
        return True

    def timeout_cb(self):
        last_idle, last_now = self.idle
        idle = int(open('/proc/stat', 'r').readline().rstrip().split()[4])
        now = time.time()
        di = (idle - last_idle) / USER_HZ
        dt = now - last_now
        self.load.set_text('%.1f%%' % ((1 - (di / dt)) * 100))
        self.idle = idle, now
        return True

    def update_policy(self, pol):
        self.__policy_map[pol.policy].update(pol)

    def update_info(self, info):
        speed = info.current / KHz
        units = 'MHz'
        if speed > 1000:
            speed /= 1000.0
            units = 'GHz'
        self.speed.set_text('%.4g %s' % (speed, units))

    def __init__(self):
        self.__policy_map = {}

        sf.connect()
        self.win = gtk.Window(gtk.WINDOW_TOPLEVEL)
        win = self.win
        win.set_title('CPU Speed Preferences')
        win.connect('delete_event', self.delete_event)

        box = gtk.VBox(False, 0)
        win.add(box)

        lbox = gtk.VBox(False, 0)
        lbox.set_border_width(10)
        rbox = gtk.VBox(False, 0)
        rbox.set_border_width(10)
        pbox = gtk.HBox(False, 0)
        pbox.pack_start(lbox)
        pbox.pack_start(rbox)
        lbox.show()
        rbox.show()

        l = gtk.Label('CPU speed')
        l.set_justify(gtk.JUSTIFY_RIGHT)
        lbox.pack_start(l)
        l.show()

        self.speed = gtk.Label('? MHz')
        rbox.pack_start(self.speed)
        self.speed.show()

        self.info = sf.get_cpu_info()

        l = gtk.Label('CPU load')
        l.set_justify(gtk.JUSTIFY_RIGHT)
        lbox.pack_start(l)
        l.show()

        self.load = gtk.Label('?%')
        self.update_info(self.info)
        rbox.pack_start(self.load)
        self.load.show()

        l = gtk.Label('Kernel driver')
        l.set_justify(gtk.JUSTIFY_RIGHT)
        lbox.pack_start(l)
        l.show()

        l = gtk.Label(self.info.driver)
        rbox.pack_start(l)
        l.show()

        box.pack_start(pbox)
        pbox.show()

        sep = gtk.HSeparator()
        box.pack_start(sep, False)
        sep.show()

        self.speedbox = gtk.VBox(False, 0)
        sbox = self.speedbox
        sbox.set_border_width(10)
        box.pack_start(sbox)

        box.show()
        sbox.show()

        self.valsep = gtk.HSeparator()
        box.pack_start(self.valsep, False)

        self.valbox = gtk.VBox(False, 10)
        self.valbox.set_border_width(10)
        box.pack_start(self.valbox, False)

        btn = None

        for klass in _policies:
            pol = klass(btn, self)
            btn = pol.button
            sbox.pack_start(btn)
            self.__policy_map[pol.policy] = pol

        sep = gtk.HSeparator()
        box.pack_start(sep, False)
        sep.show()

        cbox = gtk.HBox(False, 10)
        cbox.set_border_width(10)
        box.pack_start(cbox)

        btn = gtk.Button('Help', gtk.STOCK_HELP)
        btn.connect('clicked', self.show_help)
        cbox.pack_start(btn, False, False)
        btn.show()

        btn = gtk.Button('Close', gtk.STOCK_CLOSE)
        btn.connect('clicked', self.close_app)
        cbox.pack_end(btn, False, False)
        btn.show()

        cbox.show()

        btn.set_flags(gtk.CAN_DEFAULT)
        btn.grab_default()
        win.show()

        self.update_policy(sf.get_policy())

        sf.poll(0)

        # We start monitoring after a short delay, to reduce the
        # likelihood of two applets starting around the same time and
        # pegging the CPU at near 100% when the policy is dynamic.
        gtk.timeout_add(250, self.start_mon)

    def start_mon(self):
        self.id = gtk.input_add(sf.get_mon_fd(), gtk.gdk.INPUT_READ,
                                self.mon_event)
        self.idle = 0,0
        gtk.timeout_add(1000, self.timeout_cb)


if __name__ == '__main__':
    prefs = Prefs()
    gtk.main()
