#!/usr/bin/python3
#
# Copyright 2012-2014 "Korora Project" <dev@kororaproject.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the temms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) 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/>.
#

import dnf
import hawkey
import json
import logging
import math
import os
import re

from lens.app import App
from lens.system import System
from lens.thread import Thread

from Pharlap import detect
from Pharlap.modprobe import ModProbe
from Pharlap.dnfcache import DNFCache

class DetectThread(Thread):
  def __init__(self, config, do_cache=False, do_modules=False, do_modaliases=False, do_devices=False):
    Thread.__init__(self)

    self._config = config

    self._modprobe = ModProbe()

    self._build_cache = do_cache
    self._build_modules = do_modules
    self._build_modaliases = do_modaliases
    self._build_devices = do_devices

    self._md_progress_last = -1

  def _md_progress_cb(self, name, percentage):
    self.emit('md-progress', name, percentage)

  def _progress_cb(self, percentage, alias):
    self.emit('progress', int(percentage), alias)

  def run(self):
    modules = {}

    if self._build_cache:
      self._config.cache = DNFCache(dnf.Base(), md_progress_cb=self._md_progress_cb)

    if self._build_modaliases:
      self._config.modaliases = detect.system_modaliases()

    if self._build_modules:
      self._modprobe.parse()
      modules = self._modprobe.getConfig()
      self._config.loaded_modules = detect.loaded_modules_for_modaliases(modaliases=self._config.modaliases)

    if self._build_devices:
      self._config.devices = detect.system_driver_packages(cache=self._config.cache, modaliases=self._config.modaliases, progress_cb=self._progress_cb)

    self.emit('complete', self._config.modaliases, self._config.loaded_modules, self._config.devices, modules)



class PharlapConfig(object):
  def __init__(self):

    self._cache = None

    self._modaliases = detect.system_modaliases()
    self._devices = {}
    self._loaded_modules = {}

  @property
  def cache(self):
    return self._cache

  @cache.setter
  def cache(self, cache):
    if not isinstance(cache, DNFCache):
      raise TypeError("Cache is must be a DNFCache type.")

    self._cache = cache

  @property
  def devices(self):
    return self._devices

  @devices.setter
  def devices(self, devices):
    self._devices = devices

  @property
  def loaded_modules(self):
    return self._loaded_modules

  @loaded_modules.setter
  def loaded_modules(self, loaded_modules):
    self._loaded_modules = loaded_modules

  @property
  def modaliases(self):
    return self._modaliases

  @modaliases.setter
  def modaliases(self, modaliases):
    self._modaliases = modaliases


app = App(name="Pharlap", width=792, height=496, inspector=True)

app.namespaces = ['data', '/usr/share/pharlap', 'data']

db = dnf.Base()
config = PharlapConfig()

system = System().to_dict()

@app.bind('init')
def init_cb():
  t = DetectThread(config, do_cache=True, do_modules=True, do_modaliases=True, do_devices=True)

  app.threads.on(t, 'md-progress', _detect_thread_md_progress_cb)
  app.threads.on(t, 'progress', _detect_thread_progress_cb)
  app.threads.on(t, 'complete', _detect_thread_complete_cb)
  app.threads.add(t)

@app.bind('apply-changes')
def apply_changes_cb(request_install, request_remove):
  drivers_install = request_install
  drivers_remove = request_remove

  # we don't want to ever remove kernel-modules
  drivers_remove = [k for k in request_remove if k != 'kernel-modules']

  # we always add .i686 for nvidia proprietary drivers (ie. steam)
  for k in drivers_install:
    if 'akmod-nvidia' in k:
      drivers_install.append('xorg-x11-drv-%s-libs.i686' % (k[6:])) # strip akmod-
    elif 'kmod-nvidia' in k:
      drivers_install.append('xorg-x11-drv-%s-libs.i686' % (k[5:])) # strip kmod-

  try:
    if len(drivers_install):
      print('Installing: %s' % (', '.join(drivers_install)))
      daemon.Install(' '.join(drivers_install))

    if len(drivers_remove):
      print('Removing: %s' % (', '.join(drivers_remove)))
      daemon.Remove(' '.join(drivers_remove))

    daemon.BuildTransaction()
    app.dbus_async_call('update-packages', daemon.RunTransaction, 100)

  except:
    app.emit('dnf-abort')

@app.bind('close')
def close_cb():
  app.close()

@app.bind('app.close')
def _close_app_cb(*args):
  try:
    daemon.Unlock()
  except:
    pass

@app.bind('dbus.get-hardware')
def _dnf_update_packages_cb(err, hardware):
  # print(json.loads(ret))
  pass

@app.bind('dbus.update-packges')
def _dnf_update_packages_cb(err, ret):
  print(ret)


# we filter the following noisy signal to total percentage changes
md_progress_last = -1
def _detect_thread_md_progress_cb(thread, name, frac):
  _progress = int(100 * frac)

  global md_progress_last
  if _progress != md_progress_last:
    md_progress_last = _progress
    app.emit('update-md-progress', name, _progress)

def _detect_thread_progress_cb(thread, *args):
  app.emit('update-progress', *args)

def _detect_thread_complete_cb(thread, modaliases, loaded_modules, devices, modules):
  global system
  app.emit('init-config', system, modaliases, loaded_modules, devices, modules)
  app.emit('init-complete')


def _sig_error_cb(error, sender=None):
  print(error)
  app.emit('dnf-error', error)

def _sig_transaction_cb(event, data, sender=None):
  print(event)
  app.emit('dnf-transaction-state', event);

def _sig_download_start_cb(num_files, num_bytes, sender=None):
  app.emit('dnf-download-start', num_files, num_bytes)
  print('DOWNLOADING: %d' % (num_files))

# we filter the following noisy signal to total percentage changes
download_progress_total = -1
def _sig_download_progress_cb(name, frac, total_frac, total_files, sender=None):
  progress_total = math.floor(total_frac * 100)

  global download_progress_total
  if download_progress_total != progress_total:
    download_progress_total = progress_total
    print('DOWNLOAD: %d, %f' % (total_files, total_frac))
    app.emit('dnf-download-progress', name, frac, total_frac, total_files)

# we filter the following noisy signal to total percentage changes
rpm_progress_total = -1
def _sig_rpm_progress_cb(package, action, te_current, te_total, ts_current, ts_total, sender=None):
  global rpm_progress_total

  if action == 'install' or action == 'erase':
    progress_total = math.floor(100 * (ts_current+(te_current/te_total)) / ts_total)

    if rpm_progress_total != progress_total:
      rpm_progress_total = progress_total
      app.emit('dnf-rpm-progress', package, action, te_current, te_total, ts_current, ts_total);
      print('RPM: %s %s (%d, %d)' % (action, package, ts_current, ts_total))

  elif action == 'verify':
    progress_total = math.floor(100 * ts_current / ts_total)

    if rpm_progress_total != progress_total:
      rpm_progress_total = progress_total
      app.emit('dnf-rpm-progress', package, action, te_current, te_total, ts_current, ts_total);
      print('RPM: %s %s (%d, %d)' % (action, package, ts_current, ts_total))

  else:
    print('RPM: %s %s (%d, %d)' % (action, package, ts_current, ts_total))

# build our daemon interface
daemon = app.dbus_system_interface('org.baseurl.DnfSystem', '/')

# connect the daemon signals
daemon.connect_to_signal('TransactionEvent', _sig_transaction_cb, sender_keyword='sender')
daemon.connect_to_signal('RPMProgress', _sig_rpm_progress_cb, sender_keyword='sender')
daemon.connect_to_signal('DownloadStart', _sig_download_start_cb, sender_keyword='sender')
daemon.connect_to_signal('DownloadProgress', _sig_download_progress_cb, sender_keyword='sender')
daemon.connect_to_signal('ErrorMessage', _sig_error_cb, sender_keyword='sender')

daemon.Lock()

#app.dbus_async_call('get-hardware', daemon.GetHardware, 100)

app.start()
