#########################################################################
# Copyright 2011 Cloud Sidekick
#
# Licensed 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.
#########################################################################

# This file is a derived work from Eucalyptus 
# euca2ools/euca2ools/commands/eucacommands.py
# released under the BSD license
# original copyright: 
#        (c) 2009-2011, Eucalyptus Systems, Inc.`
# original authors:
#       Neil Soman neil@eucalyptus.com
#       Mitch Garnaat mgarnaat@eucalyptus.com

import getopt
import os
import sys
import textwrap
import urlparse
import hashlib
import base64
import hmac
import urllib
import urllib2
import httplib
import json
from urlparse import urlparse
from datetime import datetime
from catoclient.param import Param

try:
    import xml.etree.cElementTree as ET
except (AttributeError, ImportError):
    import xml.etree.ElementTree as ET


class CatoCommand(object):

    Description = ''
    API = ''
    Examples = ''
    Info = ''
    StandardOptions = [
                       Param(name='access_key',
                             short_name='A', long_name='access-key',
                             doc="A defined username.",
                             optional=True),
                       Param(name='secret_key',
                             short_name='S', long_name='secret-key',
                             doc="A valid password.",
                             optional=True),
                       Param(name='config_file',
                             short_name='C', long_name='config',
                             doc="""Read credentials and URL from the specified json formatted config file. If a config file and 
                                    -A, -U or -S flags are used on the same command, the flag option parameters take precendence""",
                             optional=True),
                       Param(name='output_format', short_name='F', long_name='format',
                             doc='The output format.  (default=text, values=xml/json.)',
                             optional=True, ptype='string', choices=['text', 'json', 'xml']),
                       Param(name='output_delimiter', short_name='L', long_name='output_delimiter',
                             doc='Delimiter for "Text" output format. (Default is TAB)',
                             optional=True, ptype='string'),
                       Param(name='debug', short_name='D', long_name='debug',
                             doc='Turn on debugging output.',
                             optional=True, ptype='boolean'),
                       Param(name='help', short_name='H', long_name='help',
                             doc='Display this help message.',
                             optional=True, ptype='boolean'),
                       Param(name='url', short_name='U', long_name='url',
                             doc='URL of the REST API endpoint. E.g.: http://address:port',
                             optional=True),
                       Param(name='force', long_name='force',
                             doc='Force "yes" on "Are you sure?" prompts.',
                             optional=True, ptype='boolean'),
                       Param(name='noheader', long_name='noheader',
                             doc='For "text" output format, omit the column header.',
                             optional=True, ptype='boolean'),
                       Param(name='dumpdoc', long_name='dumpdoc',
                             doc='Writes documentation for the command in Markdown format.',
                             optional=True, ptype='boolean'),
                       Param(name='api', long_name='api',
                             doc='Identifies the API endpoint associated with this command.',
                             optional=True, ptype='boolean')
                       ]
    Options = []
    Args = []

    def __init__(self, debug=False):
        self.access_key = None
        self.secret_key = None
        self.url = None
        self.config_file_name = None
        self.debug = 0
        self.force = False
        self.set_debug(debug)
        self.cmd_name = os.path.basename(sys.argv[0])
        self.process_cli_args()
        
        # if there's a config file, we read it.
        # any required values not explicitly specified on the command line,
        # are read from the config file.
        # there's a default file ".catoclient.conf", and you can override with the "config_file" argument
        configargs = None
        cfn = None
        if self.config_file_name:
            cfn = self.config_file_name
        else:
            cfn = "%s/.catoclient.conf" % os.path.expanduser("~")
        
        try:
            with open(cfn, 'r') as f_in:
                if f_in:
                    configargs = json.loads(f_in.read())
        except IOError:
            # if the file doesn't exist, warn and exit (but continue if there's no default config file).
            if cfn != "%s/.catoclient.conf" % os.path.expanduser("~"):
                print("The specified config file (%s) could not be found." % cfn)
                self.error_exit()
            else:
                if self.debug:
                    print("The default config file (%s) could not be found." % cfn)
                    
        except ValueError:
            # if the format of either file is bad, bark about it
            print("The specified config file (%s) json format is invalid." % cfn)
            self.error_exit()
 
        if configargs:
            # loop through the settings
            for k, v in configargs.items():
                if hasattr(self, k):
                    if not getattr(self, k):
                        setattr(self, k, v)
                        
        # since the args can come from different sources, we have to explicitly check the required ones.
        if not self.url:
            print("URL is required, either via --url or in a config file.")
            self.error_exit()
        if not self.access_key:
            print("Access Key is required, either via --access-key or in a config file.")
            self.error_exit()
        if not self.secret_key:
            print("Secret Key is required, either via --secret-key or in a config file.")
            self.error_exit()

    def set_debug(self, debug=False):
        if debug:
            self.debug = 2

    def set_force(self, force=True):
        if force:
            self.force = True

    def process_cli_args(self):
        try:
            (opts, args) = getopt.gnu_getopt(sys.argv[1:],
                                             self.short_options(),
                                             self.long_options())
        except getopt.GetoptError, e:
            print(e)
            sys.exit(1)
        for (name, value) in opts:
            if name in ('-H', '--help'):
                self.usage()
                sys.exit()
            elif name == '--dumpdoc':
                self.dumpdoc()
                sys.exit()
            elif name == '--api':
                print self.API
                sys.exit()
            elif name in ('-D', '--debug'):
                self.set_debug(True)
            elif name in ('-C', '--config'):
                self.config_file_name = value
            else:
                option = self.find_option(name)
                if option:
                    try:
                        value = option.convert(value)
                    except:
                        msg = '%s should be of type %s' % (option.long_name,
                                                           option.ptype)
                        self.display_error_and_exit(msg)