#!/usr/bin/env python2
# -*- coding: utf-8 -*-

import sys
reload(sys)
sys.setdefaultencoding('utf8')
import datetime
import xml.etree.ElementTree as ET
import ftplib
import gzip
import json
import re
from StringIO import StringIO
import urllib2


# Returns true if `value` is an integer represented as a string.
def is_int(value):
  # type: (str) -> bool
  try:
    value = int(value)
  except ValueError:
    return False
  return True


# Returns a new string with all instances of multiple whitespace
# replaced with a single space.
def collapse_multiple_spaces(line):
  # type: (str) -> str
  return " ".join(line.split())


# Extracts the file name from a line of an FTP listing.
# The input must be a valid directory entry (starting with "-" or "d").
def ftp_file_name_from_listing(line):
  # type: (str) -> str
  line = collapse_multiple_spaces(line)
  return line.split(" ", 8)[-1]


# Extracts a list of the directories and a list of the files
# from an FTP listing.
def ftp_list_dir_process_listing(lines):
  # type: (List[str]) -> List[str], List[str]
  dirs = []
  files = []
  for line in lines:
    if line.startswith("d"):
      dirs.append(ftp_file_name_from_listing(line))
    elif line.startswith("-"):
      files.append(ftp_file_name_from_listing(line))
  return dirs, files


# Lists the remote FTP directory located at `path`.
# Returns a list of the directories and a list of the files.
def ftp_list_dir(ftp, path):
  # type: (ftplib.FTP, str) -> List[str], List[str]
  ftp.cwd(path)
  lines = []
  ftp.retrlines("LIST", lines.append)
  return ftp_list_dir_process_listing(lines)


# Downloads a binary file to a string.
# Returns the string.
def ftp_download(ftp, path):
  # type: (ftplib.FTP, str) -> str
  blocks = []
  ftp.retrbinary("RETR {0}".format(path), blocks.append)
  return "".join(blocks)


# Extracts the list of links from an HTML string.
def http_links_from_listing(html):
  # type: (str) -> List[str]
  pattern = re.compile(r"""href=['"]+([^'"]+)['"]+""")
  return re.findall(pattern, html)


# Extracts the list of paths (relative links, except to ../*) from an HTML string.
def http_paths_from_listing(html):
  # type: (str) -> List[str]
  paths = []
  for link in http_links_from_listing(html):
    if link.startswith(".."):
      continue
    if link == "./" or link == "/":
      continue
    if "://" in link:
      continue
    paths.append(link)
  return paths


# Downloads a file as string from an URL. Decodes correctly.
def http_download_txt(url):
  # type: (str) -> str
  try:
    r = urllib2.urlopen(url)
    encoding = r.headers.getparam("charset")
    if not encoding:
      encoding = "utf-8"
    return r.read().decode(encoding)
  except:
    raise


# Extracts the list of paths (relative links, except to ../*) from the HTML code
# located at `url`.
def http_list_dir(url):
  # type: (str) -> List[str]
  try:
    html = http_download_txt(url)
  except:
    return []
  return http_paths_from_listing(html)


# Extracts the version and maintainer info for a package, from a Debian repository Packages file.
def deb_packages_extract_version(packages, name):
  # type: (str, str) -> str, str
  inside = False
  version = None
  maintainer = None
  for line in packages.split("\n"):
    if line == "Package: " + name:
      inside = True
    elif not line:
      if inside:
        break
    else:
      if inside:
        if line.startswith("Version:"):
          version = line.split(":", 1)[-1].strip()
        elif line.startswith("Maintainer:"):
          maintainer = line.split(":", 1)[-1].strip()
  return version, maintainer

# Extracts the version and maintainer info for a package, from an Arch PKGBUILD file.
def arch_pkgbuild_extract_version(pkgbuild):
  # type: (str) -> str, str
  version = None
  maintainer = None
  for line in pkgbuild.split("\n"):
    if line.startswith("# Maintainer:"):
      maintainer = line.split(":", 1)[-1].strip()
    elif line.startswith("pkgver="):
      version = line.split("=", 1)[-1].strip()
  return version, maintainer


# Debian

def get_debian_release_version(release):
  data = http_download_txt("http://metadata.ftp-master.debian.org/changelogs/main/t/tint2/{0}_changelog".format(release))
  version = data.split("\n", 1)[0].split("(", 1)[-1].split(")", 1)[0].strip()
  maintainer = [line.split("--", 1)[-1] for line in data.split("\n") if line.startswith(" --")][0].split("  ")[0].strip()
  return release, version, maintainer


def get_debian_versions():
  print >> sys.stderr, "Debian ..."
  return "Debian", "debian", [get_debian_release_version(release) for release in ["stable", "testing", "unstable", "experimental"]]


# Ubuntu

def get_ubuntu_versions():
  print >> sys.stderr, "Ubuntu ..."
  data = http_download_txt("https://api.launchpad.net/1.0/ubuntu/+archive/primary?ws.op=getPublishedSources&source_name=tint2&exact_match=true")
  data = json.loads(data)["entries"]
  data.reverse()
  versions = []
  for package in data:
    if package["status"] == "Published":
      version = package["source_package_version"]
      release = package["distro_series_link"].split("/")[-1]
      maintainer = package["package_maintainer_link"]
      versions.append((release, version, maintainer))
  return "Ubuntu", "ubuntu", versions


# BunsenLabs

def get_bunsenlabs_versions():
  print >> sys.stderr, "BunsenLabs ..."
  dirs = http_list_dir("https://eu.pkg.bunsenlabs.org/debian/dists/")
  versions = []
  for d in dirs:
    if d.endswith("/") and "/" not in d[:-1]:
      release = d.replace("/", "")
      packages = http_download_txt("https://eu.pkg.bunsenlabs.org/debian/dists/{0}/main/binary-amd64/Packages".format(release))
      version, maintainer = deb_packages_extract_version(packages, "tint2")
      if version:
        versions.append((release, version, maintainer))
  return "BunsenLabs", "bunsenlabs", versions


# Arch

def get_arch_versions():
  print >> sys.stderr, "Arch ..."
  pkgbuild = http_download_txt("https://git.archlinux.org/svntogit/community.git/plain/trunk/PKGBUILD?h=packages/tint2")
  version, maintainer = arch_pkgbuild_extract_version(pkgbuild)
  return "Arch Linux", "archlinux", [("Community", version, maintainer)]


# Fedora

def get_fedora_versions():
  print >> sys.stderr, "Fedora ..."
  dirs = http_list_dir("http://mirror.switch.ch/ftp/mirror/fedora/linux/development/")
  versions = []
  for d in dirs:
    if d.endswith("/") and "/" not in d[:-1]:
      release = d.replace("/", "")
      packages = http_list_dir("http://mirror.switch.ch/ftp/mirror/fedora/linux/development/{0}/Everything/source/tree/Packages/t/".format(release))
      for p in packages:
        if p.startswith("tint2-"):
          version = p.split("-", 1)[-1].split(".fc")[0]
          v = (release, version, "")
          if v not in versions:
            versions.append(v)
  return "Fedora", "fedora", versions


# Red Hat (EPEL)

def get_redhat_epel_versions():
  print >> sys.stderr, "RedHat ..."
  dirs = http_list_dir("http://mirror.switch.ch/ftp/mirror/epel/")
  versions = []
  for d in dirs:
    if d.endswith("/") and "/" not in d[:-1] and is_int(d[:-1]):
      release = d.replace("/", "")
      packages = http_list_dir("http://mirror.switch.ch/ftp/mirror/epel/{0}/SRPMS/t/".format(release))
      for p in packages:
        if p.startswith("tint2-"):
          version = p.split("-", 1)[-1].split(".el")[0]
          v = (release, version, "")
          if v not in versions:
            versions.append(v)
  return "RedHat (EPEL)", "rhel", versions


# SUSE

def get_suse_versions():
  print >> sys.stderr, "Suse ..."
  ftp = ftplib.FTP("mirror.switch.ch")
  ftp.login()
  releases, _ = ftp_list_dir(ftp, "/mirror/opensuse/opensuse/ports/update/leap/")
  versions = []
  for release in releases:
    root = "/mirror/opensuse/opensuse/ports/update/leap/{0}/oss/repodata/".format(release)
    _, files = ftp_list_dir(ftp, root)
    for fname in files:
      if fname.endswith("-primary.xml.gz"):
        data = ftp_download(ftp, "{0}/{1}".format(root, fname))
        xml = gzip.GzipFile(fileobj=StringIO(data)).read()
        root = ET.fromstring(xml)
        for package in root.findall("{http://linux.duke.edu/metadata/common}package"):
          name = package.find("{http://linux.duke.edu/metadata/common}name").text
          if name == "tint2":
            version = package.find("{http://linux.duke.edu/metadata/common}version").get("ver")
            versions.append((release, version, ""))
  ftp.quit()
  return "OpenSUSE", "opensuse", versions


# Gentoo

def get_gentoo_versions():
  print >> sys.stderr, "Gentoo ..."
  files = http_list_dir("https://gitweb.gentoo.org/repo/gentoo.git/tree/x11-misc/tint2")
  versions = []
  for f in files:
    if "tint2" in f and f.endswith(".ebuild"):
      version = f.split("tint2-")[-1].split(".ebuild")[0]
      v = ("", version, "")
      if v not in versions:
        versions.append(v)
  return "Gentoo", "gentoo", versions


# Void

def get_void_versions():
  print >> sys.stderr, "Void ..."
  template = http_download_txt("https://raw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/tint2/template")
  versions = []
  version = None
  maintainer = None
  for line in template.split("\n"):
    if line.startswith("version="):
      version = line.split("=", 1)[-1].replace('"', "").strip()
    elif line.startswith("maintainer="):
      maintainer = line.split("=", 1)[-1].replace('"', "").strip()
  if version:
    versions.append(("", version, maintainer))
  return "Void Linux", "void", versions


# Alpine

def get_alpine_versions():
  print >> sys.stderr, "Alpine ..."
  apkbuild = http_download_txt("https://git.alpinelinux.org/cgit/aports/plain/community/tint2/APKBUILD")
  versions = []
  version = None
  maintainer = None
  for line in apkbuild.split("\n"):
    if line.startswith("pkgver="):
      version = line.split("=", 1)[-1].replace('"', "").strip()
    elif line.startswith("# Maintainer:"):
      maintainer = line.split(":", 1)[-1].replace('"', "").strip()
  if version:
    versions.append(("", version, maintainer))
  return "Alpine Linux", "alpine", versions


# Slackware

def get_slack_versions():
  print >> sys.stderr, "Slackware ..."
  dirs = http_list_dir("https://slackbuilds.org/slackbuilds/")
  versions = []
  for d in dirs:
    if d.endswith("/") and "/" not in d[:-1]:
      release = d.replace("/", "")
      try:
        info = http_download_txt("https://slackbuilds.org/slackbuilds/{0}/desktop/tint2/tint2.info".format(release))
      except:
        continue
      version = None
      maintainer = None
      for line in info.split("\n"):
        if line.startswith("VERSION="):
          version = line.split("=", 1)[-1].replace('"', "").strip()
        elif line.startswith("MAINTAINER="):
          maintainer = line.split("=", 1)[-1].replace('"', "").strip()
      if version:
        versions.append((release, version, maintainer))
  return "Slackware", "slackware", versions

# FreeBSD

def get_freebsd_versions():
  print >> sys.stderr, "FreeBSD ..."
  makefile = http_download_txt("https://svnweb.freebsd.org/ports/head/x11/tint/Makefile?view=co")
  versions = []
  version = None
  maintainer = None
  for line in makefile.split("\n"):
    if line.startswith("PORTVERSION="):
      version = line.split("=", 1)[-1].strip()
    elif line.startswith("MAINTAINER="):
      maintainer = line.split("=", 1)[-1].strip()
  if version:
    versions.append(("", version, maintainer))
  return "FreeBSD", "freebsd", versions


# OpenBSD

def get_openbsd_versions():
  print >> sys.stderr, "OpenBSD ..."
  makefile = http_download_txt("http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/ports/x11/tint2/Makefile?content-type=text/plain")
  versions = []
  version = None
  for line in makefile.split("\n"):
    if line.startswith("V="):
      version = line.split("=", 1)[-1].strip()
  if version:
    versions.append(("", version, ""))
  return "OpenBSD", "openbsd", versions


# Upstream

def get_tint2_version():
  print >> sys.stderr, "Upstream ..."
  readme = http_download_txt("https://gitlab.com/o9000/tint2/raw/master/README.md")
  version = readme.split("\n", 1)[0].split(":", 1)[-1].strip()
  return version


def main():
  latest = get_tint2_version()
  distros = []
  distros.append(get_debian_versions())
  distros.append(get_bunsenlabs_versions())
  distros.append(get_ubuntu_versions())
  distros.append(get_fedora_versions())
  distros.append(get_redhat_epel_versions())
  #distros.append(get_suse_versions())
  distros.append(get_alpine_versions())
  distros.append(get_slack_versions())
  distros.append(get_arch_versions())
  distros.append(get_void_versions())
  distros.append(get_gentoo_versions())
  distros.append(get_freebsd_versions())
  distros.append(get_openbsd_versions())
  print "| Distribution | Release | Version | Status |"
  print "| ------------ | ------- | ------- | ------ |"
  for dist, dcode, releases in distros:
    icon = "![](numix-icons/distributor-logo-{0}.svg.png)".format(dcode)
    for r in releases:
      if r[1].split("-", 1)[0] == latest:
        status = ":white_check_mark: Latest"
      else:
        status = ":warning: Out of date"
      print "| {0} {1} | {2} | {3} | {4} |".format(icon, dist, r[0], r[1], status)
  utc_datetime = datetime.datetime.utcnow()
  print ""
  print "Last updated:", utc_datetime.strftime("%Y-%m-%d %H:%M UTC")


if __name__ == "__main__":
  main()
