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

class Disk:

    def __init__(self):
        self.r = run.Run()
        self.wmic = self._get_wmic()
        if self.wmic and not os.path.exists(self.wmic):
            self.wmic = None
        self.disks = {}
        self._update_disks()

    def _get_wmic(self):
        # Attempt to locate WMIC.exe
        wmic_list = self.r.run({"args":["where","wmic"]})[0].replace("\r","").split("\n")
        if wmic_list:
            return wmic_list[0]
        return None

    def update(self):
        self._update_disks()

    def _update_disks(self):
        self.disks = self.get_disks()

    def _get_rows(self, row_list):
        rows = []
        last_row = []
        for row in row_list:
            if not row.strip(): # Empty
                if last_row: # Got a row at least - append it and reset
                    rows.append(last_row)
                    last_row = []
                continue # Skip anything else
            # Not an empty row - let's try to get the info
            try: last_row.append(" : ".join(row.split(" : ")[1:]))
            except: pass
        return rows

    def _get_diskdrive(self):
        disks = []
        if self.wmic: # Use WMIC where possible
            wmic = self.r.run({"args":[self.wmic, "DiskDrive", "get", "DeviceID,Index,Model,Partitions,Size", "/format:csv"]})[0]
            # Get the rows - but skip the first 2 (empty, headers) and the last 1 (empty again)
            disks = list(csv.reader(wmic.replace("\r","").split("\n"), delimiter=","))[2:-1]
            # We need to skip the Node value for each row as well
            disks = [x[1:] for x in disks]
        else: # Use PowerShell and parse the info manually
            ps = self.r.run({"args":["powershell", "-c", "Get-WmiObject -Class Win32_DiskDrive | Format-List -Property DeviceID,Index,Model,Partitions,Size"]})[0]
            # We need to iterate the rows and add each column manually
            disks = self._get_rows(ps.replace("\r","").split("\n"))
        return disks

    def _get_ldtop(self):
        disks = []
        if self.wmic: # Use WMIC where possible
            wmic = self.r.run({"args":[self.wmic, "path", "Win32_LogicalDiskToPartition", "get", "Antecedent,Dependent"]})[0]
            # Get the rows - but skip the first and last as they're empty
            disks = wmic.replace("\r","").split("\n")[1:-1]
        else: # Use PowerShell and parse the info manually
            ps = self.r.run({"args":["powershell", "-c", "Get-WmiObject -Class Win32_LogicalDiskToPartition | Format-List -Property Antecedent,Dependent"]})[0]
            # We need to iterate the rows and add each column manually
            disks = self._get_rows(ps.replace("\r","").split("\n"))
            # We need to join the values with 2 spaces to match the WMIC output
            disks = ["  ".join(x) for x in disks]
        return disks

    def _get_logicaldisk(self):
        disks = []
        if self.wmic: # Use WMIC where possible
            wmic = self.r.run({"args":[self.wmic, "LogicalDisk", "get", "DeviceID,DriveType,FileSystem,Size,VolumeName", "/format:csv"]})[0]
            # Get the rows - but skip the first 2 (empty, headers) and the last 1 (empty again)
            disks = list(csv.reader(wmic.replace("\r","").split("\n"), delimiter=","))[2:-1]
            # We need to skip the Node value for each row as well
            disks = [x[1:] for x in disks]
        else: # Use PowerShell and parse the info manually
            ps = self.r.run({"args":["powershell", "-c", "Get-WmiObject -Class Win32_LogicalDisk | Format-List -Property DeviceID,DriveType,FileSystem,Size,VolumeName"]})[0]
            # We need to iterate the rows and add each column manually
            disks = self._get_rows(ps.replace("\r","").split("\n"))
        return disks

    def get_disks(self):
        # We hate windows... all of us.
        #
        # This has to be done in 3 commands,
        # 1. To get the PHYSICALDISK entries, index, and model
        # 2. To get the drive letter, volume name, fs, and size
        # 3. To get some connection between them...
        #
        # May you all forgive me...

        disks = self._get_diskdrive()
        p_disks = {}
        for ds in disks:
            if len(ds) < 5:
                continue
            p_disks[ds[1]] = {
                "device":ds[0],
                "model":" ".join(ds[2:-2]),
                "type":0 # 0 = Unknown, 1 = No Root Dir, 2 = Removable, 3 = Local, 4 = Network, 5 = Disc, 6 = RAM disk
                }
            # More fault-tolerance with ints
            p_disks[ds[1]]["index"] = int(ds[1]) if len(ds[1]) else -1
            p_disks[ds[1]]["size"] = int(ds[-1]) if len(ds[-1]) else -1
            p_disks[ds[1]]["partitioncount"] = int(ds[-2]) if len(ds[-2]) else 0
            
        if not p_disks:
            # Drat, nothing
            return p_disks
        # Let's find a way to map this biz now
        ldtop = self._get_ldtop()
        for l in ldtop:
            l = l.lower()
            d = p = mp = None
            try:
                dp = l.split("deviceid=")[1].split('"')[1]
                mp = l.split("deviceid=")[-1].split('"')[1].upper()
                d = dp.split("disk #")[1].split(",")[0]
                p = dp.split("partition #")[1]
            except:
                pass
            if any([d, p, mp]):
                # Got *something*
                if p_disks.get(d,None):
                    if not p_disks[d].get("partitions",None):
                        p_disks[d]["partitions"] = {}
                    p_disks[d]["partitions"][p] = {"letter":mp}
        # Last attempt to do this - let's get the partition names!
        parts = self._get_logicaldisk()
        if not parts:
            return p_disks
        for ps in parts:
            if len(ps) < 2:
                # Need the drive letter and disk type at minimum
                continue
            # Organize!
            plt = ps[0] # get letter
            ptp = ps[1] # get disk type
            # Initialize
            pfs = pnm = None
            psz = -1 # Set to -1 initially for indeterminate size
            try:
                pfs = ps[2] # get file system
                psz = ps[3] # get size
                pnm = ps[4] # get the rest in the name
            except:
                pass
            for d in p_disks:
                p_dict = p_disks[d]
                for pr in p_dict.get("partitions",{}):
                    pr = p_dict["partitions"][pr]
                    if pr.get("letter","").upper() == plt.upper():
                        # Found it - set all attributes
                        pr["size"] = int(psz) if len(psz) else -1
                        pr["file system"] = pfs
                        pr["name"] = pnm
                        # Also need to set the parent drive's type
                        if len(ptp):
                            p_dict["type"] = int(ptp)
                        break
        return p_disks