# -*- coding: utf-8 -*-
# vim: set et sts=2:
##############################################################################
#
# Copyright (c) 2012 Vifib SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contract a Free Software
# Service Company
#
# 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, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################


import argparse
import base64
import ConfigParser
import getpass
import logging
import os
import shutil
import stat
import sys
import tempfile
import urllib2


class SlapError(Exception):
  """
  Slap error
  """
  def __init__(self, message):
    self.msg = message

class UsageError(SlapError):
  pass

class ExecError(SlapError):
  pass



def get_login():
  """Get user id and encode it for basic identification"""
  login = raw_input("SlapOS Master Login: ")
  password = getpass.getpass()
  identification = base64.encodestring('%s:%s' % (login, password))[:-1]
  return identification


def check_login(identification, master_url_web):
  """Check if logged correctly on SlapOS Master"""
  request = urllib2.Request(master_url_web)
  # Prepare header for basic authentification
  authheader =  "Basic %s" % identification
  request.add_header("Authorization", authheader)
  home_page_url = urllib2.urlopen(request).read()
  if 'Logout' in home_page_url:
    return 1
  else:
    return 0


def get_certificates(identification, node_name, master_url_web):
  """Download certificates from SlapOS Master"""
  register_server_url = '/'.join([master_url_web, ("add-a-server/WebSection_registerNewComputer?dialog_id=WebSection_viewServerInformationDialog&dialog_method=WebSection_registerNewComputer&title={}&object_path=/erp5/web_site_module/hosting/add-a-server&update_method=&cancel_url=https%3A//www.vifib.net/add-a-server/WebSection_viewServerInformationDialog&Base_callDialogMethod=&field_your_title=Essai1&dialog_category=None&form_id=view".format(node_name))])
  request = urllib2.Request(register_server_url)
  # Prepare header for basic authentification
  authheader =  "Basic %s" % identification
  request.add_header("Authorization", authheader)  
  url = urllib2.urlopen(request)  
  page = url.read()
  return page


def parse_certificates(source):
  """Parse html gotten from SlapOS Master to make certificate and key files"""
  c_start = source.find("Certificate:")
  c_end = source.find("</textarea>", c_start)
  k_start = source.find("-----BEGIN PRIVATE KEY-----")
  k_end = source.find("</textarea>", k_start)
  return [source[c_start:c_end], source[k_start:k_end]]


def get_computer_name(certificate):
  """Parse certificate to get computer name and return it"""
  k = certificate.find("COMP-")
  i = certificate.find("/email", k)
  return certificate[k:i]

def save_former_config(config):
  """Save former configuration if found"""
  # Check for config file in /etc/opt/slapos/
  if os.path.exists('/etc/opt/slapos/slapos.cfg'):
    former_slapos_configuration = '/etc/opt/slapos'
  else:
    former_slapos_configuration = 0
  if former_slapos_configuration:
    saved_slapos_configuration = former_slapos_configuration + '.old'
    while True:
      if os.path.exists(saved_slapos_configuration):
        print "Slapos configuration detected in %s" % saved_slapos_configuration
        if saved_slapos_configuration[len(saved_slapos_configuration) - 1] != 'd' :
          saved_slapos_configuration = saved_slapos_configuration[:len(saved_slapos_configuration) - 1] \
              + str(int(saved_slapos_configuration[len(saved_slapos_configuration) - 1]) + 1 )
        else:
          saved_slapos_configuration += ".1"
      else: break
    config.logger.info("Former slapos configuration detected in %s moving to %s" % (former_slapos_configuration, saved_slapos_configuration))
    shutil.move(former_slapos_configuration, saved_slapos_configuration)

def get_slapos_conf_example():
  """
  Get slapos.cfg.example and return its path
  """
  register_server_url = "http://git.erp5.org/gitweb/slapos.core.git/blob_plain/HEAD:/slapos.cfg.example"
  request = urllib2.Request(register_server_url)
  url = urllib2.urlopen(request)  
  page = url.read()
  _, path = tempfile.mkstemp()
  slapos_cfg_example = open(path,'w')
  slapos_cfg_example.write(page)
  slapos_cfg_example.close()
  return path


def slapconfig(config):
  """Base Function to configure slapos in /etc/opt/slapos"""
  dry_run = config.dry_run
  # Create slapos configuration directory if needed
  slap_configuration_directory = os.path.normpath(config.slapos_configuration)

  # Make sure everybody can read slapos configuration directory:
  # Add +x to directories in path
  directory = os.path.dirname(slap_configuration_directory)
  while True:
    if os.path.dirname(directory) == directory:
      break
    # Do "chmod g+xro+xr"
    os.chmod(directory, os.stat(directory).st_mode | stat.S_IXGRP | stat.S_IRGRP | stat.S_IXOTH | stat.S_IROTH)
    directory = os.path.dirname(directory)

  if not os.path.exists(slap_configuration_directory):
    config.logger.info ("Creating directory: %s" % slap_configuration_directory)
    if not dry_run:
      os.mkdir(slap_configuration_directory, 0711)

  user_certificate_repository_path = os.path.join(slap_configuration_directory,'ssl')
  if not os.path.exists(user_certificate_repository_path):
    config.logger.info ("Creating directory: %s" % user_certificate_repository_path)
    if not dry_run:
      os.mkdir(user_certificate_repository_path, 0711)

  key_file = os.path.join(user_certificate_repository_path, 'key') 
  cert_file = os.path.join(user_certificate_repository_path, 'certificate')
  for (src, dst) in [(config.key, key_file), (config.certificate,
      cert_file)]:
    config.logger.info ("Copying to %r, and setting minimum privileges" % dst)
    if not dry_run:
      destination = open(dst,'w')
      destination.write(''.join(src))
      destination.close()
      os.chmod(dst, 0600)
      os.chown(dst, 0, 0)

  certificate_repository_path = os.path.join(slap_configuration_directory, 'ssl', 'partition_pki')
  if not os.path.exists(certificate_repository_path):
    config.logger.info ("Creating directory: %s" % certificate_repository_path)
    if not dry_run:
      os.mkdir(certificate_repository_path, 0711)
  
  # Put slapos configuration file
  slap_configuration_file_location = os.path.join(slap_configuration_directory,
                                                  'slapos.cfg')
  config.logger.info ("Creating slap configuration: %s"
                      % slap_configuration_file_location)

  # Get example configuration file
  slapos_cfg_example = get_slapos_conf_example()
  configuration_example_parser = ConfigParser.RawConfigParser()
  configuration_example_parser.read(slapos_cfg_example)  
  os.remove(slapos_cfg_example)

  # prepare slapos section
  slaposconfig = dict(
    computer_id=config.computer_id, master_url=config.master_url,
    key_file=key_file, cert_file=cert_file,
    certificate_repository_path=certificate_repository_path)
  for key in slaposconfig:
    configuration_example_parser.set('slapos', key, slaposconfig[key])

  # prepare slapformat
  slapformatconfig = dict(
    interface_name=config.interface_name,
    ipv4_local_network=config.ipv4_local_network,
    partition_amount=config.partition_number,
    create_tap=config.create_tap
    )
  for key in slapformatconfig :
    configuration_example_parser.set('slapformat', key, slapformatconfig[key])

  if not config.ipv6_interface == '':
    configuration_example_parser.set('slapformat',
                                     'ipv6_interface',
                                     config.ipv6_interface)

  if not dry_run:
    slap_configuration_file = open(slap_configuration_file_location, "w")
    configuration_example_parser.write(slap_configuration_file)

  config.logger.info ("SlapOS configuration: DONE")


# Class containing all parameters needed for configuration
class Config:
  def setConfig(self, option_dict, node_name):
    """
    Set options given by parameters.
    """
    # Set options parameters
    for option, value in option_dict.__dict__.items():
      setattr(self, option, value)
    self.node_name = node_name

    # Define logger for register
    self.logger = logging.getLogger('Register')
    self.logger.setLevel(logging.DEBUG)
    # create console handler and set level to debug
    self.ch = logging.StreamHandler()
    self.ch.setLevel(logging.INFO)
    # create formatter
    self.formatter = logging.Formatter('%(levelname)s - %(message)s')
    # add formatter to ch
    self.ch.setFormatter(self.formatter)
    # add ch to logger
    self.logger.addHandler(self.ch)

  def COMPConfig(self, slapos_configuration, computer_id, certificate, key):
    self.slapos_configuration = slapos_configuration
    self.computer_id = computer_id
    self.certificate = certificate
    self.key = key

  def displayUserConfig(self):
    self.logger.debug("Computer Name: %s" % self.node_name)
    self.logger.debug("Master URL: %s" % self.master_url)
    self.logger.debug("Number of partition: %s" % self.partition_number)
    self.logger.info("Using Interface %s" % self.interface_name)
    self.logger.debug("Ipv4 sub network: %s" % self.ipv4_local_network)
    self.logger.debug("Ipv6 Interface: %s" %self.ipv6_interface)

def register(config):
  """Register new computer on SlapOS Master and generate slapos.cfg"""
  # Get User identification and check them
  if config.login == None :
    while True :
      print ("Please enter your SlapOS Master login")
      user_id = get_login()
      if check_login(user_id, config.master_url_web):
        break
      config.logger.warning ("Wrong login/password")
  else:
    if config.password == None :
      config.password = getpass.getpass()
    user_id = base64.encodestring('%s:%s' % (config.login, config.password))[:-1]
    if not check_login(user_id, config.master_url_web):
      config.logger.error ("Wrong login/password")
      return 1

  # Get source code of page having certificate and key
  certificate_key = get_certificates(user_id, config.node_name, config.master_url_web)
  # Parse certificate and key and get computer id
  certificate_key = parse_certificates(certificate_key)
  certificate = certificate_key[0]
  key = certificate_key[1]
  COMP = get_computer_name(certificate)
  # Getting configuration parameters
  slapos_configuration = '/etc/opt/slapos/'
  config.COMPConfig(slapos_configuration=slapos_configuration,
                   computer_id=COMP,
                   certificate = certificate,
                   key = key
                   )
  # Save former configuration
  if not config.dry_run:
    save_former_config(config)
  # Prepare Slapos Configuration
  slapconfig(config)

  print "Node has successfully been configured as %s." % COMP
  return 0


def main():
  "Run default configuration."

  ap = argparse.ArgumentParser(usage='usage: slapos node %s NODE_NAME [options]' % sys.argv[0])

  ap.add_argument('node_name',
                  help='Name of the node')

  ap.add_argument('--interface-name',
                  help="Interface name to access internet",
                  default='eth0')

  ap.add_argument('--master-url',
                  help="URL of SlapOS master",
                  default='https://slap.vifib.com')

  ap.add_argument('--master-url-web',
                  help="URL of SlapOS Master webservice to register certificates",
                  default='https://www.slapos.org')

  ap.add_argument('--partition-number',
                  help="Number of partition on computer",
                  default='10',
                  type=int)

  ap.add_argument('--ipv4-local-network',
                  help="Base of ipv4 local network",
                  default='10.0.0.0/16')

  ap.add_argument('--ipv6-interface',
                  help="Interface name to get ipv6",
                  default='')

  ap.add_argument('--login',
                  help="User login on SlapOS Master webservice",
                  default=None)

  ap.add_argument('--password',
                  help="User password on SlapOs Master webservice",
                  default=None)

  ap.add_argument('-t', '--create-tap',
                  help='Will trigger creation of one virtual "tap" interface per '
                       'Partition and attach it to primary interface. Requires '
                       'primary interface to be a bridge. defaults to false. '
                       'Needed to host virtual machines.',
                  default=False,
                  action="store_true")

  ap.add_argument('-n', '--dry-run',
                  help="Simulate the execution steps",
                  default=False,
                  action="store_true")

  options = ap.parse_args()


  if options.password != None and options.login == None :
    self.error("Please enter your login with your password")


  try:
    config = Config()
    config.setConfig(options, options.node_name)
    return_code = register(config)
  except UsageError, err:
    print >> sys.stderr, err.msg
    print >> sys.stderr, "For help use --help"
    return_code = 16
  except ExecError, err:
    print >> sys.stderr, err.msg
    return_code = 16
  except SystemExit, err:
    # Catch exception raise by optparse
    return_code = err

  sys.exit(return_code)