#!/usr/bin/env python

# setup-environment - sets up your juju environment interactively
#
# Copyright 2012 Canonical Ltd. All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys
import os
import os.path
import yaml
import logging
import math
from base64 import b64encode
import re
from tempfile import NamedTemporaryFile
from subprocess import call

logging.basicConfig(format='%(asctime)-15s %(name)s %(message)s', level=logging.INFO)
logger = logging.getLogger('juju-jitsu')

class ValidatorError(ValueError):
    pass

class ProviderQuestion(object):
    def __init__(self, label, text, required=False, default=None, validator=None, qtype=str):
        self.label = label
        self.text = text
        self.required = required
        self.default = default
        self.validator = validator
        self.qtype = qtype
        if self.qtype is bool and type(self.default) is not bool:
            raise ValueError('default (%s) must match qtype (%s)' % (type(self.default), self.qtype))
        self.answer = None

    def ask(self):
        answer = None
        while answer is None:
            sys.stdout.write("%50s %24s " % (self.text[:50], self.label))
            if self.default is not None:
                sys.stdout.write("[%s]" % self.default) 
            sys.stdout.write(': ')
            answer = sys.stdin.readline().strip()
            if len(answer) == 0:
                answer = None
            if answer is None:
                if self.required and self.default is None:
                    sys.stdout.write("\n%s is required\n\n" % self.label)
                    continue
                elif self.default is None:
                    break
                else:
                    answer = self.default
            else:
                answer = yaml.load(answer)
                if self.validator is not None:
                    try:
                        self.validator(answer)
                    except ValidatorError,e:
                        sys.stdout.write("\n%s failed validation: %s\n\n" % (answer, e))
                        answer = None
                if type(answer) is not self.qtype:
                    sys.stdout.write(
                            "\n%s is type %s, must be %s\n\n" % (
                                answer, str(type(answer)), str(self.qtype)))
                    answer = None
        if type(answer) is str and len(answer) and answer[0] == '~':
            answer = os.path.expanduser(answer)
        self.answer = answer


NAME_RE = re.compile('^[a-zA-Z][A-Za-z0-9\-]*$')

def name_validator(value):
    if not NAME_RE.match(value):
        raise ValidatorError('Must be alphanumeric and start with an alpha.')


def generate_random_sequence(length):
    return b64encode(os.urandom(int(math.ceil(0.75*length))),'-_')[:length]


GLOBAL_QUESTIONS = [
        ProviderQuestion('default-series', 'Default "series", a.k.a. release codename of Ubuntu',
                required=True, default='precise', validator=name_validator),
        ProviderQuestion('admin-secret', 'Zookeeper Secret',
                required=True, default=generate_random_sequence(32))
        ]


class Provider(object):
    def __init__(self):
        self.questions = GLOBAL_QUESTIONS
        self.provider_type = None
        self.name = None

    def ask_for_values(self):
        for q in self.questions:
            q.ask()

    @property
    def values(self):
        values = {}
        for q in self.questions:
            if q.answer is not None:
                values[q.label] = q.answer
        return values


class Ec2Provider(Provider):
    def __init__(self):
        super(Ec2Provider,self).__init__()
        self.provider_type = 'ec2'
        self.questions.append(ProviderQuestion('control-bucket', 'S3 Bucket to store data in',
                    default='juju-jitsu-%s' % generate_random_sequence(32)))
        self.questions.append(ProviderQuestion('access-key', '(AWS_ACCESS_KEY_ID)',
                    default=os.environ.get('AWS_ACCESS_KEY_ID')))
        self.questions.append(ProviderQuestion('secret-key', '(AWS_SECRET_ACCESS_KEY)',
                    default=os.environ.get('AWS_SECRET_ACCESS_KEY')))
        self.questions.append(ProviderQuestion('default-instance-type', 'Default Instance Type (m1.small, c1.medium, etc'))
        self.questions.append(ProviderQuestion('default-image-id', 'Default AMI'))
        self.questions.append(ProviderQuestion('region', 'EC2 Region'))
        self.questions.append(ProviderQuestion('ec2-uri', 'EC2 URI'))
        self.questions.append(ProviderQuestion('s3-uri', 'S3 URI'))
        self.questions.append(ProviderQuestion('ssl-hostname-verification', 'Verify SSL Hostnames(recommended!)',qtype=bool, default=True))


class LocalProvider(Provider):
    def __init__(self):
        super(LocalProvider,self).__init__()
        self.provider_type = 'local'
        self.questions.append(ProviderQuestion('data-dir', 'local dir to store logs/directory structure/charm bundles', required=True, default='~/.juju/data'))


def get_environments():
    eyaml = os.path.expanduser('~/.juju/environments.yaml')
    try:
        with open(eyaml, 'r') as eyaml_file:
            return yaml.safe_load(eyaml_file)
    except IOError:
        pass
    return {'environments': {}}


def prompt_name():
    q = ProviderQuestion('name','Name for environment',required=True,
            validator=name_validator)
    q.ask()
    return q.answer


def prompt_provider():
    options=['ec2','local']

    def validator(value):
        if value not in options:
            raise ValidatorError('Must be one of (%s)' % ','.join(options))

    q = ProviderQuestion('type','What provider do you want to use? (%s)' % (','.join(options)),
        required=True,
        default='local',
        validator=validator)
    q.ask()
    if q.answer == 'ec2':
        return Ec2Provider()
    elif q.answer == 'local':
        return LocalProvider()
    else:
        raise RuntimeError('validator failed, provider answer = %s' % (q.answer))

def main():
    environments = get_environments()

    name = prompt_name()
    provider = prompt_provider()
    provider.ask_for_values()

    environments['environments'][name] = { 'type': provider.provider_type }
    environments['environments'][name].update(provider.values)

    print yaml.dump(environments, default_flow_style=False)

    juju_home=os.path.expanduser('~/.juju')
    eyaml_name=os.path.join(juju_home, 'environments.yaml')

    print """
Do you want to

  [s]ave this to %s
  [d]iff with existing %s
  [q]uit
""" % (eyaml_name, eyaml_name)
    answer = None

    while answer is None:
        sys.stdout.write("[sdq]: ")
        answer = sys.stdin.readline().strip()

        if answer not in ['s','d','q']:
            answer = None

        if answer == 's':
            with NamedTemporaryFile(dir=juju_home, prefix='.environments.yaml.',delete=False) as new_env:
                yaml.dump(environments, new_env, default_flow_style=False)
                if os.path.exists(os.path.join(juju_home,'environments.yaml')):
                    c=0
                    backup_path = lambda c: '%s.%d' % (eyaml_name, c)
                    while os.path.exists(backup_path(c)):
                        c += 1
                    logger.info('Backing up %s to %s' % (eyaml_name, backup_path(c)))
                    os.rename(eyaml_name, backup_path(c))
                new_env.flush()
                os.rename(new_env.name, eyaml_name)
        elif answer == 'd':
            with NamedTemporaryFile(dir=juju_home, prefix='.environments.yaml.',delete=False) as new_env:
                yaml.dump(environments, new_env, default_flow_style=False)
                new_env.flush()
                call(['diff','-u',eyaml_name, new_env.name])
            answer = None
        else: # answer == q or None
            pass


if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print "\n"
        sys.exit(1)
    except Exception, e:
        logger.exception(e)
        sys.exit(1)
