import os
import csv
import datetime

from config import BASE_PATH, DL_PATH, SOURCE_PATH
import rewise
import sources
import analyze

""" -- InstallerStats

  Generate or read build stats
"""

class InstallerStats:
  __DATA = {
    "title"       : "",
    "filename"    : "",
    "b3"          : "",
    "md5"         : "",
    "peBuild"     : 0,
    "scriptBuild" : 0,
    "overlay"     : 0,
    "scriptDeflateOffset": 0,
    "isNe"        : 0, # bool
    "isPk"        : 0, # bool
    "langCount"   : 0,
    "devStatus"   : rewise.getRewiseStatusString(rewise.REWISE_STATUS_UNSET),
    "devError"    : "",
    "devListStatus": rewise.getRewiseStatusString(rewise.REWISE_STATUS_UNSET),
    "devRawStatus": rewise.getRewiseStatusString(rewise.REWISE_STATUS_UNSET),
    "devRawError" : "",
    "rew02Status" : rewise.getRewiseStatusString(rewise.REWISE_STATUS_UNSET),
    "rew02Error"  : "",
    "comment"    : ""
    #"dlUrl"       : "",
    #"filepath"    : ""
  }

  __FANCY_NAMES = [
    "Title",
    "Filename",
    "B3",
    "MD5",
    "PE Build",
    "Script Build",
    "Overlay offset",
    "Script deflate file offset",
    "NE",
    "PK",
    "Languages",
    "Dev status",
    "Dev error",
    "Dev list status",
    "Dev raw status",
    "Dev raw error",
    "0.2 status",
    "0.2 error",
    "Comments"
    #"Download URL",
    #"Local filepath"
  ]

  def __init__(self, source=None, rowData=None):
    if not source and not rowData:
      raise Exception("InstallerStats: give either source or rowData")

    self.__data = dict(InstallerStats.__DATA)
    self.__keys = list(self.__data.keys())
    self.__keyCount = len(self.__keys)
    self.__columns = [col for col in self.__data.keys()]

    # Load, rowData should be a list
    if rowData:
      colIndex = 0
      for col in rowData:
        key = self.__keys[colIndex]
        dtype = type(InstallerStats.__DATA[key])
        self.__data[key] = dtype(col)
        colIndex += 1
      self.filepath = os.path.join(DL_PATH, self.b3)
    # Gather stats
    else:
      self.__data["b3"] = source.b3
      self.__data["md5"] = source.md5
      self.__data["dlUrl"] = source.dlUrl
      self.__data["filename"] = source.getFilename()
      self.filepath = source.filepath

    # tmp
    self.overlayHeaderUnk2 = ""
    self.wsHeaderUnk22 = ""
    self.realpath = os.path.join(DL_PATH, self.filepath)

  def __getattr__(cls, key):
    return cls.__data[key]

  def __str__(self):
    return str(self.__data)

  # for sorting on b3
  def __lt__(self, other):
    return self.b3 < other.b3

  def addComment(self, comment):
    if self.__data["comment"]:
      self.__data["comment"] += "\n---\n"
    self.__data["comment"] += comment

  def clearComments(self):
    self.__data["comment"] = ""

  @staticmethod
  def columns():
    return InstallerStats.__FANCY_NAMES

  def asRow(self):
    return [self.__data[k] for k in InstallerStats.__DATA.keys()]

  def formattedDate(self):
    return datetime.datetime.fromtimestamp(int(self.peBuild)).strftime('%Y-%m-%d %H:%M:%S')

  def formattedScriptDate(self):
    return datetime.datetime.fromtimestamp(int(self.scriptBuild)).strftime('%Y-%m-%d %H:%M:%S')

  def gatherInfo(self):
    otherStats = rewise.getRewiseDevOtherStats(self.realpath)
    self.__data["peBuild"] = otherStats["peBuild"]
    self.__data["scriptBuild"] = otherStats["scriptBuild"]
    self.__data["isNe"] = 1 if otherStats["isNe"] else 0
    self.__data["isPk"] = 1 if otherStats["isPk"] else 0
    self.__data["overlay"] = otherStats["overlayOffset"]
    self.__data["scriptDeflateOffset"] = otherStats["scriptDeflateOffset"]
    self.__data["title"] = otherStats["title"]
    self.__data["langCount"] = otherStats["langCount"]

    self.overlayHeaderUnk2 = otherStats["overlayUnk2"]
    self.wsHeaderUnk22 = otherStats["wsHdrUnk22"]

  def testDevVerify(self):
    devVerify = rewise.getRewiseDevVerifyStats(self.realpath)
    self.__data["devStatus"] = rewise.getRewiseStatusString(devVerify["status"])
    self.__data["devError"] = devVerify["error"]

  def testDevList(self):
    devList = rewise.getRewiseDevListStats(self.realpath)
    self.__data["devListStatus"] = rewise.getRewiseStatusString(devList["status"])
    #self.__data["devListError"] = devList["error"]

  def testDevRaw(self):
    devRaw = rewise.getRewiseDevRawVerifyStats(self.realpath)
    self.__data["devRawStatus"] = rewise.getRewiseStatusString(devRaw["status"])
    self.__data["devRawError"] = devRaw["error"]

  def test02Verify(self):
    r02Verify = rewise.getRewise02VerifyStats(self.realpath)
    self.__data["rew02Status"] = rewise.getRewiseStatusString(r02Verify["status"])
    self.__data["rew02Error"] = r02Verify["error"]

  def generateComments(self):
    self.clearComments()
    analyze.addCustomComments(self)

    if not self.isNe:
      if analyze.addPeComments(self):
        return

    if self.overlayHeaderUnk2 and self.overlayHeaderUnk2 != "0008":
      self.addComment(f"Overlay header unknown2 != 0x8000 but 0x{self.overlayHeaderUnk2}.")

    elif self.isPk:
      analyze.falsePositivePkCheck(self)

    elif self.wsHeaderUnk22 and self.wsHeaderUnk22[-12:] not in ["000000000001", "000000000000"]:
      self.addComment(f"Possible weird stuff at WiseScriptHeader.unknown_22 '{self.wsHeaderUnk22[-12:]}'.")

    elif self.scriptDeflateOffset != -1:
      if self.scriptDeflateOffset < -1:
        self.addComment(f"Negative script deflate offset")
      elif self.scriptDeflateOffset > 0x100000:
        self.addComment(f"Script deflate offset is absurd high")

  def fullTest(self):
    self.gatherInfo()
    self.testDevVerify()
    self.testDevList()
    self.testDevRaw()
    self.test02Verify()

def installerStatsCsvIt(csvFile):
	with open(csvFile, newline='') as csvfile:
		spamreader = csv.reader(csvfile, delimiter=',', quotechar='"')
		for row in list(spamreader)[1:]:  # skip the header
			yield InstallerStats(rowData=row)


# Filter out installers that appear to be invalid / non Wise.
# - When the installer is a PE file, it has a PE build date, filter
#   it out when the PE build date has no working installers.
# - Filter out installer where the exe parsing failed.
# - Filter out CRC32 corrupt installers.
def acceptableInstallerIt(resultFile):
  installers = list(installerStatsCsvIt(resultFile))

  # Get working PE dates (PE build dates with at least one success
  # fully operation for one installer).
  workingPeDates = []
  for installer in installers:
    if installer.peBuild in workingPeDates:
      continue

    if (installer.devStatus == "OK" or
        installer.devRawStatus == "OK" or
        installer.devListStatus == "OK" or
        installer.rew02Status == "OK"):
      workingPeDates.append(installer.peBuild)

  for installer in installers:
    if installer.devStatus in ["ERROR_INVALID_PENE", "ERROR_NOT_PENE",
                               "ERROR_NOT_WISE"]:
      continue

    if installer.peBuild not in workingPeDates:
      continue

    if installer.devRawStatus == "ERROR_CRC32":
      # this installer is corrupt
      continue

    yield installer


if __name__ == "__main__":
  for source in sources.InstallerSourceUniqIt(os.path.join(SOURCE_PATH,
                                              "shareware.csv")):
    installer = InstallerStats(source)
    installer.gatherInfo()
    print(installer.title)
