#!/usr/bin/env python

# Copyright (C) 2005 - 2021  Eric Van Dewoestine
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

##
# Script to check for any new updates for each of eclim's eclipse plugin
# dependencies and to optionally install/upgrade them.
#
# Author: Eric Van Dewoestine
##
from __future__ import print_function

import os
import re
import shutil
import sys
import tempfile

from collections import defaultdict
from subprocess import Popen, PIPE
from xml.dom.minidom import parse
from zipfile import ZipFile

def main():
  path = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
  module = os.path.join(path, 'installer', 'install')
  try:
    import importlib.machinery
    import importlib.util
    loader = importlib.machinery.SourceFileLoader('install', module)
    spec = importlib.util.spec_from_loader('install', loader)
    install = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(install)
  except ImportError:
    import imp
    with open(module, 'r') as f:
      install = imp.load_module('<none>', f, module, ('', 'r', imp.PY_SOURCE))

  __builtins__.terminal = install.Terminal()

  eclim = install.Eclim(home=os.path.join(path, 'installer'))
  eclipseHome = getEclipseHome(eclim)
  if not os.path.exists(eclipseHome):
    print('Directory not found: %s' % eclipseHome)
    sys.exit(1)

  validateJavaVersion(eclipseHome)

  proxy = install.Proxy(None) # add proxy support later if necessary
  eclipse = install.Eclipse(eclipseHome, proxy)
  dependencies = eclim.dependencies()
  installed = eclipse.installed()
  available = eclipse.available(set(d.site for d in dependencies if d.site))

  compareDependenciesXml(dependencies, available)
  actionable = compareInstalled(dependencies, installed, available)
  if actionable:
    print('Choose which dependencies you would like to install/upgrade')
    print('by typing the number for each dependency:')
    print('  Ex 0 1')
    print('  Ex 0,1')
    print('  Ex 0-2')
    chosen = []
    while not chosen:
      response = terminal.prompt('> ')
      chosen, invalid = install._choose(actionable, response)
      if invalid:
        chosen = []
        for inv in invalid:
          print(inv)
        continue

      installUpgrade(eclipse, chosen)
      break

def getEclipseHome(eclim):
  propertiesPath = os.path.join(os.path.dirname(eclim.home), 'user.properties')
  if os.path.isfile(propertiesPath):
    branch = Popen(
      'git rev-parse --abbrev-ref HEAD',
      cwd=eclim.home, shell=True, stdout=PIPE
    ).stdout.read().strip()
    props = {}
    for line in open(propertiesPath).read().split('\n'):
      if not line or line.strip().startswith('#'):
        continue
      key, _, value = line.strip().partition('=')
      props[key] = value

    branch_home = props.get('eclipse.home.%s' % branch)
    eclipse_home = props.get('eclipse.home')
    if branch_home or eclipse_home:
      home = props.get('eclipse.home.%s' % branch, props.get('eclipse.home'))
      home = home.replace('${user.home}', os.environ.get('HOME'))
      return home

  if 'ECLIM_ECLIPSE_HOME' in os.environ:
    return os.environ['ECLIM_ECLIPSE_HOME']

  print(
    'The eclipse install location must be set using either an\n'
    'ECLIM_ECLIPSE_HOME environment variable or eclipse.home in\n'
    'a user.properties file.'
  )
  sys.exit(1)

def validateJavaVersion(eclipseHome):
  eclipseIni = os.path.join(eclipseHome, 'eclipse.ini')
  if not os.path.exists(eclipseIni):
    print('eclipse.ini not found: %s' % eclipseIni)
    sys.exit(1)

  required = None
  with open(eclipseIni) as ini:
    for line in ini.readlines():
      if 'requiredJavaVersion' in line:
        required = int(line.partition('=')[-1])
        break

  cmd = Popen('java -version', shell=True, stdout=PIPE, stderr=PIPE)
  version = cmd.stdout.read().strip() + cmd.stderr.read().strip()
  version = version.decode()
  for line in version.split('\n'):
    if 'version' in line:
      version = line
      break

  version = re.sub(r'.*"(\d+\.\d+)(.*)".*', r'\1', version)
  version = float(version)
  if version < required:
    print('You must have at least java %s installed.' % required)
    sys.exit(1)

def compareDependenciesXml(dependencies, availableFeatures):
  availableByName = defaultdict(list)
  for feature in availableFeatures:
    availableByName[feature.name].append(feature)

  print('\ndependencies.xml')
  for dependency in dependencies:
    if dependency.name not in availableByName:
      print('     [\033[1;31m*\033[0m] %s not found at %s' % (
        dependency.name, dependency.site
      ))
    else:
      versions = set([
        f.version
        for f in availableByName[dependency.name]
        if f.version >= dependency.version
      ])
      if not versions:
        print('     [%s] %s not found at %s' % (
          terminal.red('*'),
          dependency,
          dependency.site
        ))
      elif len(versions) == 1 and dependency.version in versions:
        print('     [%s] %s (%s) is up to date.' % (
          terminal.green('*'),
          dependency.name,
          dependency.version,
        ))
      else:
        if dependency.version in versions:
          versions.remove(dependency.version)
        print('     [%s] %s (%s) has the following updates available: %s' % (
          terminal.yellow('*'),
          dependency.name,
          dependency.version,
          ', '.join([str(v) for v in sorted(versions, reverse=True)]),
        ))

def compareInstalled(dependencies, installed, available):
  installedByName = dict((f.name, f) for f in installed)
  availableByName = defaultdict(list)
  for feature in available:
    availableByName[feature.name].append(feature)

  print('\ninstalled')
  index = 0
  actionable = []
  for dependency in dependencies:
    prefix = '  '

    feature = installedByName.get(dependency.name)
    if not feature:
      if dependency.name in availableByName:
        actionable.append((dependency, 'install'))
        prefix = '%s)' % index
        index += 1

      print('  %s [%s] %s not installed' % (
        prefix,
        terminal.red('*'),
        dependency.name,
      ))
    else:
      versions = set([
        f.version
        for f in availableByName[dependency.name]
        if f.version >= feature.version
      ])
      if not versions:
        print('  %s [%s] %s not found at %s' % (
          prefix,
          terminal.red('*'),
          dependency,
          dependency.site
        ))
      elif len(versions) == 1 and feature.version in versions:
        print('  %s [%s] %s (%s) is up to date.' % (
          prefix,
          terminal.green('*'),
          dependency.name,
          feature.version,
        ))
      else:
        if dependency.name in availableByName:
          actionable.append((dependency, 'upgrade'))
          prefix = '%s)' % index
          index += 1

        if feature.version in versions:
          versions.remove(feature.version)
        versions = sorted(versions, reverse=True)
        print('  %s [%s] %s (%s) has the following updates available: %s' % (
          prefix,
          terminal.yellow('*'),
          dependency.name,
          dependency.version,
          ', '.join([str(v) for v in versions]),
        ))

  installUpgrade = []
  for dependency, action in actionable:
    features = sorted(availableByName[dependency.name], reverse=True)
    installUpgrade.append((dependency, features[0], action))

  return installUpgrade

def installUpgrade(eclipse, actionable):
  for dependency, feature, action in actionable:
    eclipse.execute(
      '-application', 'org.eclipse.equinox.p2.director',
      '-repository', dependency.site,
      '-installIU', '%s.feature.group' % dependency.name,
    )

if __name__ == '__main__':
  try:
    main()
  except KeyboardInterrupt:
    print('')
    sys.exit(0)

# vim:ft=python
