#!/usr/bin/env python2
'''
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
'''

import re
import os
import sys
import logging
import subprocess
from optparse import OptionParser
import ConfigParser

USAGE = "Usage: %prog [OPTION]... URL"
DESCRIPTION = "URL should point to full tar.gz location e.g.: https://public-repo-1.hortonworks.com/something/ambari-server.tar.gz"

logger = logging.getLogger("install_ambari_tarball")

PREINST_SCRIPT = "preinst"
PRERM_SCRIPT = "prerm"
POSTINST_SCRIPT = "postinst"
POSTRM_SCRIPT = "postrm"
OS_CHECK = "os_check.py"
OS_PACKAGE_DEPENDENCIES = "dependencies.properties"
OS_FAMILY_DESCRIPTION = "resources/os_family.json"
RPM_DEPENDENCIES_PROPERTY =  "rpm.dependency.list"
DEB_DEPENDENCIES_PROPERTY =  "deb.dependency.list"

FILES_TO_DOWNLOAD = [PREINST_SCRIPT, PRERM_SCRIPT, POSTINST_SCRIPT, POSTRM_SCRIPT, OS_CHECK, OS_FAMILY_DESCRIPTION, OS_PACKAGE_DEPENDENCIES]

ROOT_FOLDER_ENV_VARIABLE = "RPM_INSTALL_PREFIX"
          
class Utils:
  verbose = False
  @staticmethod
  def os_call(command, logoutput=None, env={}):
    shell = not isinstance(command, list)
    print_output = logoutput==True or (logoutput==None and Utils.verbose)
    
    if not print_output:
      stdout = subprocess.PIPE
      stderr = subprocess.STDOUT
    else:
      stdout = stderr = None
    
    logger.info("Running '{0}'".format(command))
    proc = subprocess.Popen(command, shell=shell, stdout=stdout, stderr=stderr, env=env)
      
    if not print_output:
      out = proc.communicate()[0].strip('\n')
    else:
      proc.wait()
      out = None
      
    code = proc.returncode
  
    if code:
      err_msg = ("Execution of '%s'\n returned %d. %s") % (command, code, out)
      raise OsCallFailure(err_msg)
      
    return out
  
  @staticmethod
  def install_package(name):
    from os_check import OSCheck
    
    logger.info("Checking for existance of {0} dependency package".format(name))
    is_rpm = not OSCheck.is_ubuntu_family()
    
    if is_rpm:
      is_installed_cmd = ['rpm', '-q'] + [name]
      install_cmd = ['sudo', 'yum', '-y', 'install'] + [name]
    else:
      is_installed_cmd = ['dpkg', '-s'] + [name]
      install_cmd = ['sudo', 'apt-get', '-y', 'install'] + [name]
      
    try:
      Utils.os_call(is_installed_cmd, logoutput=False)
      logger.info("Package {0} is already installed. Skipping installation.".format(name))
    except OsCallFailure:
      logger.info("Package {0} is not installed. Installing it...".format(name))
      Utils.os_call(install_cmd)
    
  
class FakePropertiesHeader(object):
  """
  Hacky class to parse properties file without sections.
  see http://stackoverflow.com/questions/2819696/module-to-use-when-parsing-properties-file-in-python/2819788#2819788
  """
  FAKE_SECTION_NAME = 'section'
  def __init__(self, fp):
    self.fp = fp
    self.sechead = '[{0}]\n'.format(FakePropertiesHeader.FAKE_SECTION_NAME)
  def readline(self):
    if self.sechead:
      try: 
        return self.sechead
      finally: 
        self.sechead = None
    else: 
      return self.fp.readline()
  
class OsCallFailure(RuntimeError):
  pass

class Installer:
  def __init__(self, archive_url, root_folder, verbose, skip_dependencies):
    splited_url = archive_url.split('/')
    self.archive_name = splited_url[-1]
    self.base_url = '/'.join(splited_url[0:-1])
    self.root_folder = root_folder
    self.verbose = verbose
    self.skip_dependencies = skip_dependencies
    
  def download_files(self, files_list):
    for name in files_list:
      dirname = os.path.dirname(name)
      if dirname:
        Utils.os_call(["mkdir", "-p", dirname])
        
      url = "{0}/{1}".format(self.base_url, name)
      logger.info("Downloading {0}".format(url))
      Utils.os_call(["wget", "-O", name, url])
    
  def run(self):
    self.download_files([self.archive_name] +FILES_TO_DOWNLOAD) # [self.archive_name] +
    
    self.check_dependencies()
    self.run_script(PRERM_SCRIPT, ["remove"]) # in case we are upgrading
    self.run_script(POSTRM_SCRIPT, ["remove"]) # in case we are upgrading
    
    self.run_script(PREINST_SCRIPT, ["install"])
    self.extract_archive()
    self.run_script(POSTINST_SCRIPT, ["configure"])
    
  def check_dependencies(self):
    from os_check import OSCheck
    
    os_family = OSCheck.get_os_family()
    os_version = OSCheck.get_os_major_version()
    
    is_rpm = not OSCheck.is_ubuntu_family()
    property_prefix = RPM_DEPENDENCIES_PROPERTY if is_rpm else DEB_DEPENDENCIES_PROPERTY
    
    cp = ConfigParser.SafeConfigParser()
    with open(OS_PACKAGE_DEPENDENCIES) as fp:
      cp.readfp(FakePropertiesHeader(fp))
      
    properties = dict(cp.items(FakePropertiesHeader.FAKE_SECTION_NAME))
    
    packages_string = None
    postfixes = [os_family+str(ver) for ver in range(int(os_version),0,-1)+['']]+['']
    for postfix in postfixes:
      property_name = property_prefix + postfix
      if property_name in properties:
        packages_string = properties[property_name]
        break
      
    if packages_string is None:
      err_msg = "No os dependencies found. "
      if self.skip_dependencies:
        logger.warn(err_msg)
      else:
        raise Exception(err_msg)
    
    packages_string = re.sub('Requires\s*:','',packages_string)
    packages_string = re.sub('\\\\n','',packages_string)
    packages_string = re.sub('\s','',packages_string)
    packages_string = re.sub('[()]','',packages_string)
    
    if self.skip_dependencies:
      var = raw_input("Please confirm you have the following packages installed {0} (y/n): ".format(packages_string))
      if var.lower() != "y" and var.lower() != "yes":
        raise Exception("User canceled the installation.")
      return
    
    pacakges = packages_string.split(',')
    
    for package in pacakges:
      split_parts = re.split('[><=]', package)
      package_name = split_parts[0]
      Utils.install_package(package_name)
    
  def run_script(self, script_name, args):
    bash_args = []
    if self.verbose:
      bash_args.append("-x")
      
    Utils.os_call(["bash"] + bash_args + [script_name] + args, env={ROOT_FOLDER_ENV_VARIABLE: self.root_folder}, logoutput=True)
    

class TargzInstaller(Installer):
  def extract_archive(self):
    Utils.os_call(['tar','--no-same-owner', '-xvf', self.archive_name, '-C', self.root_folder+os.sep], logoutput=False)


class Runner:
  def parse_opts(self):
    parser = OptionParser(usage=USAGE, description=DESCRIPTION)
    parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
                      help="sets output level to more detailed")
    parser.add_option("-r", "--root-folder", dest="root_folder", default="/",
                      help="root folder to install Ambari to. E.g.: /opt")
    parser.add_option("-d", "--dependencies-skip", dest="skip_dependencies", action="store_true",
                  help="the script won't install the package dependencies. Please make sure to install them manually.")
    
    (self.options, args) = parser.parse_args()
    
    if len(args) != 1:
      help = parser.print_help()
      sys.exit(1)
      
    self.url = args[0]
    
  @staticmethod
  def setup_logger(verbose):
    logging_level = logging.DEBUG if verbose else logging.INFO
    logger.setLevel(logging_level)

    formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
    stdout_handler = logging.StreamHandler(sys.stdout)
    stdout_handler.setLevel(logging_level)
    stdout_handler.setFormatter(formatter)
    logger.addHandler(stdout_handler)
    
  def run(self):
    self.parse_opts()
    Runner.setup_logger(self.options.verbose)
    Utils.verbose = self.options.verbose
    
    # TODO: check if ends with tar.gz?
    targz_installer = TargzInstaller(self.url, self.options.root_folder, self.options.verbose, self.options.skip_dependencies)
    targz_installer.run()
      
if __name__ == '__main__':
  Runner().run()