#!/usr/bin/python3
# -*- coding: utf-8 -*-
__copyright__ = """
    Copyright (c) 2021 armpit <armpit@rumpigs.net>

    Permission to use, copy, modify, and distribute this software for any
    purpose with or without fee is hereby granted, provided that the above
    copyright notice and this permission notice appear in all copies.

    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""

__author__ = "armpit"
__email__ = "armpit@rumpigs.net"
__version__ = "0.1.1"

import atexit
import datetime
import os
import psutil
import setproctitle
import signal
import subprocess
import sys
import time

from argparse import ArgumentParser
from signal import signal, SIGTERM, SIGKILL, SIGINT, SIGHUP, SIGUSR1, SIGUSR2
from subprocess import check_output, Popen, PIPE

import RPi.GPIO as GPIO

"""
    Todo: handle changing user (file ownership, etc)
          start using return values (daemon should not produce messages)
"""

_PRODUCT_NAME="buttond2"
_MODULE_FILE = sys.executable if getattr(sys, 'frozen', False) else __file__
_PROGRAM_DIR = os.path.abspath(os.path.dirname(_MODULE_FILE))

E_SUCCESS = 0
E_FAILED = 1
E_RUNNING = 2
E_NOT_RUNNING = 3
E_SET_USER_FAILED = 4

_DEBUG_LOG = 0

"""
    Satans Child
"""
class Daemon(object):

    def __init__(self, pid_file, stdout='/var/log/buttond_out.log', stderr='/var/log/buttond_err.log'):
        self.stdout = stdout
        self.stderr = stderr
        self.pid_file = pid_file
        # user to run under
        self.daemon_user = None

    """
        Delete the pid file.
    """
    def del_pid(self):
        self.log_message("daemon.del_pid()", 10)
        os.remove(self.pid_file)

    """
        Perform double fork.
    """
    def daemonize(self):
        self.log_message("daemon.daemonize()", 10)

        # Fork first child
        try:
            self.log_message("daemon.daemonize() - first fork", 10)
            if os.fork():
                sys.exit(0)
        except OSError as e:
            self.log_message("daemon.daemonize() - first fork failed", 10)
            return E_FAILED

        # Decouple from parent environment
        os.chdir("/")
        os.setsid()
        os.umask(0)

        # Second fork
        try:
            self.log_message("daemon.daemonize() - second fork", 10)
            if os.fork():
                sys.exit()
        except OSError as e:
            self.log_message("daemon.daemonize() - second fork failed", 10)
            return E_FAILED

        # redirect standard file descripters
        with open('/dev/null', 'r') as dev_null:
            os.dup2(dev_null.fileno(), sys.stdin.fileno())

        sys.stderr.flush()
        with open(self.stderr, 'a+') as stderr:
            os.dup2(stderr.fileno(), sys.stderr.fileno())

        sys.stdout.flush()
        with open(self.stdout, 'a+') as stdout:
            os.dup2(stdout.fileno(), sys.stdout.fileno())

        # write pidfile
        atexit.register(self.del_pid)
        pid = str(os.getpid())
        with open(self.pid_file, 'w+') as pid_file:
            pid_file.write('{0}'.format(pid))

        # Change user
        if self.daemon_user:
            self.log_message("changing daemon user", 10)
            try:
                uid = pwd.getpwnam(self.daemon_user)[2]
                os.setuid(uid)
            except NameError as e:
                return E_SET_USER_FAILED
            except OSError as e:
                return E_SET_USER_FAILED

        self.log_message("here shines a shiny daemon....", 20)
        return E_SUCCESS


    """
        Return the pid read from the pid file.
    """
    def get_pid_by_file(self):
        self.log_message("daemon.get_pid_by_file()", 10)
        try:
            with open(self.pid_file, 'r') as pid_file:
                pid = int(pid_file.read().strip())
            return pid
        except IOError:
            return E_FAILED


    """
        Return pid from pidof.
    """
    def get_pid(self):
        try:
            pid = int(check_output(["pidof", "-s", "PiTFT-buttond"]))
            self.log_message("get_pid() returned: " + str(pid), 10)
            return pid
        except:
            self.log_message("get_pid() failed", 10)
            return E_FAILED


    """
        Start the daemon.
    """
    def start(self):
        self.log_message("Product Name: " + _PRODUCT_NAME, 20)
        self.log_message("Product Version: " + __version__, 20)
        self.log_message("Module File: " + _MODULE_FILE, 20)
        self.log_message("Program Dir: " + _PROGRAM_DIR, 20)

        print("Starting...")
        self.log_message("daemon.start()", 10)

        pid = self.get_pid_by_file()
        if pid != 1:
            print('PID file {0} exists. Is the deamon already running?'.format(self.pid_file))
            sys.exit(1)

        d = self.daemonize()

        if d == E_SUCCESS:
            self.log_message("daemonized", 20)
            self.run()
        elif d == E_SET_USER_FAILED:
            self.log_message("Failed to set daemon user", 20)
            sys.exit(1)


    """
        Stop the daemon.
    """
    def stop(self):
        print("Stopping...")
        self.log_message("daemon.stop()", 10)

        pid = self.get_pid_by_file()

        if not pid:
            pid = self.get_pid()
            if not pid:
                self.log_message("PID file {0} doesn't exist and process cannot retrieve pid. daemon not running?".format(self.pid_file), 20)
                return E_FAILED

        # Time to kill.
        try:
            while 1:
                os.kill(pid, SIGTERM)
                time.sleep(0.1)
        except OSError as err:
            if 'No such process' in err.strerror and os.path.exists(self.pid_file):
                os.remove(self.pid_file)
            else:
                return E_FAILED

        return E_SUCCESS

    """
        Restart the deamon.
    """
    def restart(self):
        self.log_message("daemon.restart()", 10)
        status = 0

        if self.check():
            status = self.stop()

        if status == E_FAILED:
            return status

        return self.start()


    """
        Run the daemon.
    """
    def run(self):
        self.log_message("daemon.run()", 10)
        SPT_DEBUG = 1
        setproctitle.setproctitle("PiTFT-buttond")
        do_shit()


    """
        Write to logfile.
    """
    def log_message(self, message, level):
        if level == 10:
            if _DEBUG_LOG == 0:
                return

        with open(self.stdout, 'a') as stdout:
            stdout.write(datetime.datetime.now().isoformat() + ' - ' + message + '\n')
        time.sleep(0.2)


    """
        Change user

    """
    def set_user(self, username):
        self.log_message("daemon.set_user()", 10)

        if not isinstance(username, str):
            raise TypeError('username should be a string')
        self.daemon_user = username


    """
        Check if daemon is running.
    """
    def check(self):
        self.log_message("daemon.check()", 10)

        if os.path.exists(self.pid_file):
            pid = self.get_pid_by_file()
            self.log_message("Pidfile found (" + str(pid) +")", 10)

            try:
                os.kill(pid, 0)
            except OSError as err:
                if 'No such process' in err.strerror and os.path.exists(self.pid_file):
                    print("Not running, removing stale pidfile")
                    os.remove(self.pid_file)
                    return None
                else:
                    print(err)
                    return None
            else:
                print(("running (" + str(pid) + ")"))
        else:
            self.log_message("No pidfile found.", 10)
            try:
                pid = self.get_pid()

                if pid != 1:
                    self.log_message("Checking status via via profile", 10)

                    procfile = file("/proc/%d/status" % pid, 'r')
                    procfile.close()

                    print(("running (" + str(pid) + "), but no pidfile present."))
                    with open(self.pid_file, 'w+') as pid_file:
                        pid_file.write('{0}'.format(pid))
                else:
                    print("not running")
                    return None

            except IOError:
                if pid == 1:
                    print("not running")
                    return None
                else:
                    sys.stdout.write("there is not a process with the PID specified in %s\n" % pid)

        return pid

"""
    Handle Signals
"""
def signal_handler(signal_recieved, frame):
    if signal_recieved == 1:
        daemon.log_message("Recieved SIGHUP, restarting." , 10)
        daemon.restart()
    if signal_recieved == 2:
        daemon.log_message("Recieved SIGINT, quitting", 10)
        daemon.stop()
    if signal_recieved == 10:
        daemon.log_message("Shiver me timbers!", 10)
    if signal_recieved == 12:
        daemon.log_message("Stove me head in with a lead pipe!", 10)

"""
    Lets actually cook this chook!
"""
def do_shit():
    daemon.log_message("cooking the chook", 20)

    test = subprocess.Popen(['cat /sys/class/backlight/soc\:backlight/brightness'], stdout = subprocess.PIPE, shell = True)

    if test == 0:
        btn1State = 0
    else:
        btn1State = 1

    btn2State = 0
    btn3State = 0
    btn4State = 0

    Btn1 = 17       # first button
    Btn2 = 22       # second button
    Btn3 = 23       # third button
    Btn4 = 27       # fourth button

    GPIO.setmode(GPIO.BCM)
    GPIO.setup(Btn1, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(Btn2, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(Btn3, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(Btn4, GPIO.IN, pull_up_down=GPIO.PUD_UP)

    """ The main loop of the daemon. """
    daemon.log_message("entering the twilight zone......", 20)
    while 1:
        time.sleep(0.2)

        Btn1_state = GPIO.input(Btn1)
        if Btn1_state == False:
            daemon.log_message("button #1 pressed", 10)
            btn1State = btn1_pressed(btn1State)

        Btn2_state = GPIO.input(Btn2)
        if Btn2_state == False:
            daemon.log_message("button #2 pressed.", 10)
            btn2State = btn2_pressed(btn2State);

        Btn3_state = GPIO.input(Btn3)
        if Btn3_state == False:
            daemon.log_message("button #3 pressed.", 10)
            btn3State = btn3_pressed(btn3State)

        Btn4_state = GPIO.input(Btn4)
        if Btn4_state == False:
            daemon.log_message("button #4 pressed.", 10)
            btn4State = btn4_pressed(btn4State)


"""
    Button #1 pressed.
"""
def btn1_pressed(state):
    if state == 1:
        state = 0
        #os.system("sudo halt")
    else:
        state = 1
        # nothing to do here because we never get here
    time.sleep(0.2)
    return state

"""
    Button #2 pressed.
"""
def btn2_pressed(state):
    if state == 1:
        state = 0
        # run some command
    else:
        state = 1
        # run some command
    time.sleep(0.2)
    return state

"""
    Button #3 pressed.
"""
def btn3_pressed(state):
    padd_pid = 0

#    for line in os.popen("ps aux | grep padd.sh | grep -v grep"):
#        fields = line.split()
#    padd_pid = fields[1]

#    os.kill(int(padd_pid), signal.SIGKILL)
#    daemon.log_message("padd restarted.", 20)
    time.sleep(0.2)
    return state


"""
    Button #4 pressed.
"""
def btn4_pressed(state):
    if state == 0:
        state = 1
        os.system("sudo sh -c \"echo '1' > /sys/class/backlight/soc\:backlight/brightness\"")
        daemon.log_message('PiTFT turned on', 20)
    else:
        state = 0
        os.system("sudo sh -c \"echo '0' > /sys/class/backlight/soc\:backlight/brightness\"")
        daemon.log_message('PiTFT turned off', 20)
    time.sleep(0.2)
    return state


"""
    Up and at em!
"""
if __name__ == '__main__':
    signal(SIGINT,  signal_handler)
    signal(SIGHUP,  signal_handler)
    signal(SIGUSR1, signal_handler)
    signal(SIGUSR2, signal_handler)

    parser = ArgumentParser(
        prog = "buttond2",
        description = "PiTFT button daemon.",
        epilog = "buttond2 is released under the ISC license.",
    )
    actions = parser.add_argument_group('actions')
#    parser.add_argument('-d', help = 'Do not daemonize.')
    parser.add_argument('-D', '--debug', help = 'Enable debug logging.', action = 'store_true', dest = 'debug_log')
    parser.add_argument('-l', '--license', help = 'Display license.', action = 'store_true', dest = 'display_license')
    parser.add_argument('-V', '--version', action = 'version', version = _PRODUCT_NAME + " " + str(__version__))
    actions.add_argument('start', nargs = '?', help = 'Start the daemon.')
    actions.add_argument('stop', nargs = '?', help = 'Stop the daemon.')
    actions.add_argument('restart', nargs = '?', help = 'Restart the daemon.')
    actions.add_argument('check', nargs = '?', help = 'Check daemon status.')
    args = parser.parse_args()

    action = None
    if len(sys.argv) == 3:
        if sys.argv[2] == '-V' or sys.argv[2] == '-D':
            action = sys.argv[1]
        else:
            action = sys.argv[2]
    elif len(sys.argv) == 2:
        action = sys.argv[1]
    elif len(sys.argv) < 2:
        print("Usage: {0} start|stop|restart".format(sys.argv[0]))
        sys.exit(2)

    if args.display_license == True:
        print(__copyright__)
        sys.exit(1)

#    if args.show_version == True:
#        print(_PRODUCT_NAME + " " + str(__version__))
#        sys.exit(1)

    if args.debug_log == True:
        _DEBUG_LOG = 1

    daemon = Daemon('/var/run/buttond.pid')

#    if '-d' == sys.argv[1]:
#        daemon.run()
    if 'start' == action:
#        daemon.set_user("pi")
        daemon.start()
    elif 'stop' == action:
        daemon.stop()
    elif 'restart' == action:
        daemon.restart()
    elif 'check' == action:
        daemon.check()
    else:
        if '-D' == action:
            print("The -D argument requires an action to be provided.")
            sys.exit(0)
        print(("Unknown action '{0}'".format(action)))
        sys.exit(2)

