import os, time, json, sys, re, plistlib
sys.path.append(os.path.abspath(os.path.dirname(os.path.realpath(__file__))))
import run, reveal

class KextUpdater:

    def __init__(self):
        self.json_file = "hashes.json"
        self.update_min = 60
        self.r = run.Run()
        self.re = reveal.Reveal()
        self.plist = "com.corpnewt.LiluAndFriends.plist"
        self.install_path = os.path.expanduser("~/Library/LaunchAgents/" + self.plist)
        self.script_path = os.path.realpath(__file__)

    def is_installed(self):
        return os.path.exists(self.install_path)

    def is_loaded(self):
        out = self.r.run({"args":["/bin/launchctl", "list"]})
        if not out[2] == 0:
            return False
        try:
            proc = [x for x in out[0].split("\n") if self.plist.lower() in x.lower()]
            if len(proc):
                return True
        except:
            pass
        return False

    def install(self):
        if os.path.exists(self.install_path):
            self.unload()
            self.uninstall()
        # Build the dict
        p = {
            "Label" : self.plist,
            "ProgramArguments" : [
                "/usr/bin/python",
                os.path.realpath(__file__)
            ],
            "RunAtLoad" : True
        }
        plistlib.writePlist(p, self.install_path)
        self.load()

    def uninstall(self):
        try:
            self.unload()
        except:
            pass
        if os.path.exists(self.install_path):    
            os.remove(self.install_path)

    def unload(self):
        self.r.run({"args":["/bin/launchctl", "unload", self.install_path]})

    def load(self):
        self.r.run({"args":["/bin/launchctl", "load", self.install_path]})

    def start(self):
        update_wait = 10
        os.chdir(os.path.dirname(os.path.realpath(__file__)))
        while True:
            time.sleep(update_wait)
            # Starts the countdown and update check
            if not os.path.exists(self.json_file):
                # No data - bail
                exit(1)
            # Load the json data and parse for our stuff
            j_data = json.load(open(self.json_file))
            # Get update frequency, and updated kexts
            update_wait = j_data.get("update_wait", None)
            try:
                update_wait = int(update_wait)
            except:
                update_wait = None
            if update_wait == None or update_wait < self.update_min: # Can't go under a minute - too much
                # Set to the min
                update_wait = self.update_min
            # Get our hash list
            built = j_data.get("built_kexts", [])
            # Check kexts!
            updates = self.check_updates(built)
            if not len(updates):
                # Nothing needed
                continue
            # Got updates
            self.re.notify("Kext Updates!", ", ".join([x["name"] for x in updates]))
            # Flush the hashes
            overlap = [x["url"] for x in built for y in updates if x["url"].lower() == y["url"].lower()]
            # Add non-overlapping vars
            for b in built:
                if b["url"].lower() in overlap:
                    continue
                updates.append(b)
            # Udate changes in json data and flush
            j_data["built_kexts"] = updates
            # Save to file
            json.dump(j_data, open(self.json_file, "w"), indent=2)

    def get_hash(self, url):
        out_hash = self.r.run({"args" : ["git", "ls-remote", url]})
        if out_hash[2] != 0:
            # git failed
            return None
        try:
            head = next( x for x in out_hash[0].split("\n") if "HEAD" in x )
            return head.split("\t")[0].lower()
        except:
            pass
        return None

    def check_updates(self, kext_list):
        ups = []
        for kext in kext_list:
            hv = self.check_update(kext)
            if hv:
                kext["last_notified"] = hv
                ups.append(kext)
        return ups

    def check_update(self, kext_dict):
        # Checks the passed kext_dict to see if there's an update
        # Structure is like:
        # { "name" : "kextname", "url" : "kexturl", "last_built" : "buildhash", "last_notified" : "notifyhash" }
        hash_val = self.get_hash(kext_dict["url"])
        if hash_val and hash_val.lower() not in [kext_dict.get("last_built", "").lower(), kext_dict.get("last_notified", "").lower()]:
                return hash_val
        return None

if __name__ == "__main__":
    # Launch the script
    k = KextUpdater()
    try:
        k.start()
    except Exception as e:
        k.re.notify("Error!", str(e))