#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
#
# CPU-G is a program that displays information about your CPU,
# RAM, Motherboard and some general information about your System.
#
# Copyright © 2009  Fotis Tsamis <ftsamis at gmail dot com>.
# Copyright © 2016-2019  Lorenzo Carbonell (aka atareao)
# <lorenzo.carbonell.cerezo at gmail dot com>
#
# 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
# (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 gi
try:
    gi.require_version('Gdk', '3.0')
except Exception as e:
    print(e)
    exit(-1)
from gi.repository import Gdk
import os
import re
import sys
import platform
import subprocess
import shlex
import comun
import psutil
from distro import get_distro
from comun import _

BATTERY_DIR = '/sys/class/power_supply/BAT0'


class Investigator():
    def readfile(self, filename):
        try:
            f = open(filename, 'r')
            data = f.read()
            f.close()
            if data.endswith('\n'):
                data = data[:-1]
            return data
        except Exception as e:
            print(e)
            return _('N/A')

    def get_window_manager(self):
        try:
            ans = self.execute('/usr/bin/wmctrl -m')
            res = ans[6:ans.find('\n')]
        except Exception as e:
            res = _('N/A')
        return res

    def resolution(self):
        s = Gdk.Screen.get_default()
        return '%dx%d' % (s.get_width(), s.get_height())

    def desktop_environment(self):
        # From http://stackoverflow.com/questions/2035657/\
        # what-is-my-current-desktop-environment
        # and http://ubuntuforums.org/showthread.php?t=652320
        # and http://ubuntuforums.org/showthread.php?t=652320
        # and http://ubuntuforums.org/showthread.php?t=1139057
        if sys.platform in ["win32", "cygwin"]:
            return "windows"
        elif sys.platform == "darwin":
            return "mac"
        else:  # Most likely either a POSIX system or something not much common
            desktop_session = os.environ.get("DESKTOP_SESSION")
            # easier to match if we doesn't have  to deal with caracter cases
            if desktop_session is not None:
                desktop_session = desktop_session.lower()
                if desktop_session in ["gnome", "unity", "cinnamon", "mate",
                                       "xfce4", "lxde", "fluxbox", "blackbox",
                                       "openbox", "icewm", "jwm",
                                       "afterstep", "trinity", "kde",
                                       "lightdm-xsession"]:
                    return desktop_session
                # ## Special cases ##
                # Canonical sets $DESKTOP_SESSION to Lubuntu rather than
                # LXDE if using LXDE.
                # There is no guarantee that they will not do the same with
                # the other desktop environments.
                elif "xfce" in desktop_session or\
                        desktop_session.startswith("xubuntu"):
                    return "xfce4"
                elif desktop_session.startswith("ubuntu"):
                    return "unity"
                elif desktop_session.startswith("lubuntu"):
                    return "lxde"
                elif desktop_session.startswith("kubuntu"):
                    return "kde"
                elif desktop_session.startswith("razor"):  # e.g. razorkwin
                    return "razor-qt"
                elif desktop_session.startswith("wmaker"):  # eg. wmaker-common
                    return "windowmaker"
            if os.environ.get('KDE_FULL_SESSION') == 'true':
                return "kde"
            elif os.environ.get('GNOME_DESKTOP_SESSION_ID'):
                if "deprecated" not in os.environ.get(
                        'GNOME_DESKTOP_SESSION_ID'):
                    return "gnome2"
            # From http://ubuntuforums.org/showthread.php?t=652320
            elif self.is_running("xfce-mcs-manage"):
                return "xfce4"
            elif self.is_running("ksmserver"):
                return "kde"
        return "unknown"

    def convert2int(value):
        try:
            return int(value)
        except Exception as e:
            return value

    def is_running(self, process):
        # From http://www.bloggerpolis.com/2011/05/\
        # how-to-check-if-a-process-is-running-using-python/
        # and http://richarddingwall.name/2009/06/18/\
        # windows-equivalents-of-ps-and-kill-commands/
        try:  # Linux/Unix
            s = subprocess.Popen(["/bin/ps", "axw"], stdout=subprocess.PIPE,
                                 universal_newlines=True)
        except Exception as e:  # Windows
            print(e)
            return False
        for x in s.stdout:
            if re.search(process, x.decode('utf-8')):
                return True
        return False

    def logo(self, core):
        model = self.cpuinfo("model", core)
        vendor = self.cpuinfo("vendor", core)
        if vendor == 'AMD':
            label = 'amd.png'
        elif vendor == 'Intel':
            label = 'intel.png'
        # AMDs
        if re.match("AMD Athlon\(tm\) 64 X2.*", model)\
                or re.match("AMD Athlon\(tm\) X2.*", model):
            label = 'AMD-AthlonX2.png'
        elif re.match("AMD Sempron\(tm\).*", model):
            label = 'AMD-Sempron.png'
        elif re.match("Mobile AMD Sempron\(tm\).*", model):
            label = 'AMD-Sempron-Mobile.png'
        elif re.match("Dual-Core AMD Opteron\(tm\).*", model)\
                or re.match("AMD Opteron\(tm\).*", model):
            label = 'AMD-Opteron.png'
        elif re.match("AMD Athlon\(tm\) XP.*", model):
            label = 'AMD-AthlonXP.png'
        elif re.match("AMD Athlon\(tm\) 64 Processor.*", model):
            label = 'AMD-Athlon64.png'
        elif re.match("AMD Phenom\(tm\).*", model):
            label = 'AMD-Phenom.png'
        # Intels
        elif re.match("Intel\(R\) Core\(TM\)2 Duo.*", model):
            label = 'Intel-Core2Duo.png'
        elif re.match("Intel\(R\) Core\(TM\)2 Quad.*", model):
            label = 'Intel-Core2Quad.png'
        elif re.match("Intel\(R\) Core\(TM\)2 CPU.*", model):
            label = 'Intel-Core2Quad.png'
        elif re.match("Intel\(R\) Atom\(TM\) CPU.*", model):
            label = 'Intel-Atom.png'
        elif re.match("Intel\(R\) Core\(TM\)2 Extreme CPU.*", model):
            label = 'Intel-Core2Extreme.png'
        elif re.match("Intel\(R\) Xeon\(TM\).*", model):
            label = 'Intel-Xeon.png'
        elif re.match(".*Pentium II.*", model):
            label = 'Intel-Pentium2.png'
        elif re.match("Intel\(R\) Pentium\(R\) Dual CPU.*", model):
            label = 'Intel-PentiumDual.png'
        return label

    def hostname(self):
        ans = self.readfile('/etc/hostname')
        if ans.endswith('\n'):
            ans = ans[:-1]
        return ans

    def battery_info(self, data='manufacturer'):
        info = 0
        if data == 'manufacturer':
            info = self.readfile(os.path.join(BATTERY_DIR, 'manufacturer'))
        elif data == 'model_name':
            info = self.readfile(os.path.join(BATTERY_DIR, 'model_name'))
        elif data == 'serial_number':
            info = self.readfile(os.path.join(BATTERY_DIR, 'serial_number'))
        elif data == 'technology':
            info = self.readfile(os.path.join(BATTERY_DIR, 'technology'))
        elif data == 'status':
            info = self.readfile(os.path.join(BATTERY_DIR, 'status'))
        elif data == 'capacity':
            info = Investigator.convert2int(
                self.readfile(os.path.join(BATTERY_DIR, 'capacity')))
        elif data == 'capacity-level':
            info = self.readfile(os.path.join(BATTERY_DIR, 'capacity_level'))
        elif data == 'voltage-now':
            info = Investigator.convert2int(
                self.readfile(os.path.join(BATTERY_DIR, 'voltage_now')))
        elif data == 'voltage-min-design':
            info = Investigator.convert2int(
                self.readfile(os.path.join(BATTERY_DIR, 'voltage_min_design')))
        elif data == 'charge-now':
            info = Investigator.convert2int(
                self.readfile(os.path.join(BATTERY_DIR, 'charge_now')))
        elif data == 'current-now':
            info = Investigator.convert2int(
                self.readfile(os.path.join(BATTERY_DIR, 'current_now')))
        elif data == 'charge-full':
            info = Investigator.convert2int(
                self.readfile(os.path.join(BATTERY_DIR, 'charge_full')))
        elif data == 'charge-full-design':
            info = Investigator.convert2int(
                self.readfile(os.path.join(BATTERY_DIR, 'charge_full_design')))
        elif data == 'cycle-count':
            info = Investigator.convert2int(
                self.readfile(os.path.join(BATTERY_DIR, 'cycle_count')))
        elif data == 'present':
            info = (self.readfile(os.path.join(BATTERY_DIR,
                                               'present')) == 1)
        elif data == 'exists':
            info = os.path.exists(BATTERY_DIR)
        return info

    def cpuinfo(self, var, core=0):
        info = self.readfile("/proc/cpuinfo")

        if var == 'vendor':
            vendor = re.findall("vendor_id\s*:\s*(.*)", info)
            if vendor[core] == 'AuthenticAMD':
                vendor[core] = 'AMD'
            elif vendor[core] == 'GenuineIntel':
                vendor[core] = 'Intel'
            return vendor[core]
        elif var == 'corespeed':
            return re.findall("cpu MHz\s*:\s*(.*)", info)[core] + ' MHz'
        elif var == 'model':
            return re.findall("model name\s*:\s*(.*)", info)[core]
        elif var == 'cache':
            return re.findall("cache size\s*:\s*(.*)", info)[core]
        elif var == 'modelnumber':
            return re.findall("model\s*:\s*(.*)", info)[core]
        elif var == 'family':
            return re.findall("cpu family\s*:\s*(.*)", info)[core]
        elif var == 'stepping':
            return re.findall("stepping\s*:\s*(.*)", info)[core]
        elif var == 'coresnum':
            return str(len(re.findall("processor\s*:\s*(.*)", info)))
        elif var == 'flags':
            return re.findall("flags\s*:\s*(.*)", info)[core]
        elif var == 'bogomips':
            return re.findall("bogomips\s*:\s*(.*)", info)[core]
        elif var == 'width':
            if re.findall(' lm(?![-a-zA-Z0-9_])',
                          re.findall("flags\s*:(.*)", info)[core]):
                width = '64-bit'
            else:
                width = '32-bit'
            return width

    def sysdevcpu(self, core, level, kind):
        if not os.path.exists('/sys/devices/system/cpu'):
            return _('N/A')
        coresinsysdev = str(
            len(re.findall("'cpu[0-9]+'",
                           str(os.listdir("/sys/devices/system/cpu/")))))
        if coresinsysdev == self.cpuinfo('coresnum'):
            cores_matching = True
        else:
            print("Error: Cannot decide if the cores are %s or %s.\n" +
                  "Using the lowest value as the real cores number." %
                  (self.cpuinfo('coresnum'), coresinsysdev))
        path = '/sys/devices/system/cpu/cpu%i/cache/' % (core)
        indexes = len(
            re.findall("'index[0-9]*'", str(os.listdir(path))))
        for index in range(indexes):
            levelpath = path + 'index%i/level' % (index)
            typepath = path + 'index%i/type' % (index)
            size = path + 'index%i/size' % (index)
            # os.chdir(newpath)
            if self.readfile(levelpath).strip() == str(level) and\
                    self.readfile(typepath).strip() == kind:
                return self.readfile(size).strip()
        return _('N/A')

    def distro(self):
        return get_distro()
        try:
            values = platform.linux_distribution()
        except AttributeError:
            values = platform.dist()
        if len(values) == 3:
            return "%s %s %s" % (values[0], values[1], values[2])
        return self.readfile('/etc/issue').strip()

    def get_distro_logo(self):
        distro = self.distro().split()[0].lower() + '.png'
        logo = os.path.join(comun.DISTROSDIR, distro)
        if os.path.exists(logo):
            return logo
        return None

    def gccver(self):
        gcc_version = self.execute('/usr/bin/gcc -dumpversion').strip()
        if gcc_version == '':
            gcc_version = _('N/A')
        return gcc_version

    def xver(self):
        try:
            xver_version = self.execute('/usr/bin/apt-cache show xorg')
        except FileNotFoundError:
            xver_version = ""
        ans = re.findall("Version: 1:(.*)\+", xver_version)
        if ans:
            return ans[0]
        return _('N/A')

    def raminfo(self):
        mem = psutil.virtual_memory()
        values = {'total': mem.total/1024/1024,
                  'available': mem.available/1024/1024,
                  'percent': mem.percent,
                  'used': mem.used/1024/1024,
                  'free': mem.free/1024/1024,
                  'active': mem.active/1024/1024,
                  'inactive': mem.inactive/1024/1024,
                  'buffers': mem.buffers/1024/1024,
                  'cached': mem.cached/1024/1024,
                  }
        return values

    def swapinfo(self):
        mem = psutil.swap_memory()
        values = {'total': mem.total/1024/1024,
                  'used': mem.used/1024/1024,
                  'free': mem.free/1024/1024,
                  'sin': mem.sin/1024/1024,
                  'sout': mem.sout/1024/1024,
                  }
        return values

    def disksinfo(self):
        values = []
        disk_partitions = psutil.disk_partitions(all=False)
        for partition in disk_partitions:
            usage = psutil.disk_usage(partition.mountpoint)
            device = {'device': partition.device,
                      'mountpoint': partition.mountpoint,
                      'fstype': partition.fstype,
                      'opts': partition.opts,
                      'total': usage.total,
                      'used': usage.used,
                      'free': usage.free,
                      'percent': usage.percent
                      }
            values.append(device)
        values = sorted(values, key=lambda device: device['device'])
        return values

    def mobo(self, var):
        # var can be: board_vendor, board_name, bios_vendor,
        # bios_version, bios_date, or chassis_type
        return self.readfile('/sys/devices/virtual/dmi/id/' + var).strip()

    def uptime(self):
        total = int(self.readfile('/proc/uptime').split('.')[0])
        days = int(total / 86400)
        hours = int((total / 3600) - (days * 24))
        minutes = int((total / 60) - ((days * 1440) + (hours * 60)))
        return "%i days, %i hours, %i minutes" % (days, hours, minutes)

    def get_graphic_card_logo(self):
        card_logo = self.execute('/usr/bin/lspci', 'VGA(.*)')
        if card_logo is not None:
            # Intel
            if re.findall("Intel\s*", card_logo):
                label = 'intel.png'
            # ATI
            # ATI Technologies replace to ATI. See bug
            # https://bugs.launchpad.net/cpug/+bug/959115
            elif re.findall("ATI\s*", card_logo):
                label = 'ati.png'
            # nVidia
            # elif re.findall("nVidia\s*", card_logo):
            elif re.findall("nVidia\s*", card_logo, re.I):
                label = 'nvidia.png'
            else:
                label = 'unknown.png'
        else:
            label = 'unknown.png'
        return os.path.join(comun.GRAPHICCARDDIR, label)

    # Graphic tab
    def open_gl(self, var):
        open_gl_ = self.execute('/usr/bin/glxinfo')
        vga = self.execute('/usr/bin/lspci', 'VGA[^:]*:(.*)')
        if var == 'vendor':
            if open_gl_ != '':
                return re.findall("OpenGL vendor string: (.*)", open_gl_)[0]
            return _('N/A')
        elif var == 'renderer':
            if open_gl_ != '':
                return re.findall("OpenGL renderer string: (.*)", open_gl_)[0]
            return _('N/A')
        elif var == 'version':
            if open_gl_ != '':
                return re.findall("OpenGL version string: (.*)", open_gl_)[0]
            return _('N/A')
        elif var == 'VGA':
            if vga is not None:
                return vga
            return _('N/A')
    # End Graphic Tab

    def execute(self, command, parser=None):
        command_with_args = shlex.split(command)
        execution = subprocess.Popen(command_with_args, stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
        stdout, stderr = execution.communicate()
        if parser is None:
            result = stdout.decode()
        else:
            matches = re.search(parser, stdout.decode())
            if matches.groups():
                result = matches.group(1)
            else:
                result = None
        return result

if __name__ == '__main__':
    print(psutil.cpu_times())
    print(psutil.cpu_count())
    print(psutil.virtual_memory())
    print(psutil.swap_memory())
    print(psutil.disk_partitions())
    print(psutil.disk_usage('/'))
    # exit(0)
    inv = Investigator()
    print(inv.cpuinfo)
    # board_vendor, board_name, bios_vendor, bios_version, bios_date,\
    # chassis_type
    print(inv.cpuinfo('coresnum'))
    for i in range(0, int(inv.cpuinfo('coresnum'))):
        print(inv.cpuinfo('vendor', i))
        print(inv.cpuinfo('corespeed', i))
        print(inv.cpuinfo('model', i))
        print(inv.cpuinfo('cache', i))
        print(inv.cpuinfo('modelnumber', i))
        print(inv.cpuinfo('family', i))
        print(inv.cpuinfo('stepping', i))
        print(inv.cpuinfo('flags', i))
        print(inv.cpuinfo('bogomips', i))
        print(inv.cpuinfo('width', i))
        print(inv.logo(i))
        print(inv.sysdevcpu(i, 1, 'Data'))
        print(inv.sysdevcpu(i, 1, 'Instruction'))
        print(inv.sysdevcpu(i, 2, 'Unified'))
        print(inv.sysdevcpu(i, 3, 'Unified'))
    print(inv.mobo('board_vendor'))
    print(inv.mobo('board_name'))
    print(inv.mobo('bios_vendor'))
    print(inv.mobo('bios_version'))
    print(inv.mobo('chassis_type'))
    print(inv.raminfo())
    print(inv.distro())
    print(inv.xver())
    print(inv.gccver())
    print(inv.disksinfo())
    print(inv.get_window_manager())

    exit(0)