#!/usr/bin/env python
# -*- coding: utf-8 -*-

__credits__ = """
    Copyright (C) 2012 YunoHost

    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
    """
__author__  = 'Kload <kload@kload.fr>'
__version__ = '2.0 beta1'

import os
import sys
import argparse
import gettext
import json
if not __debug__:
    import traceback

gettext.install('YunoHost')

try:
    from yunohost import YunoHostError, YunoHostLDAP, str_to_func, colorize, pretty_print_dict, display_error, connect_services, disconnect_services
except ImportError:
    sys.stderr.write('Error: Yunohost CLI Require YunoHost lib\n')
    sys.exit(1)

"""
Category/actions/arguments dictionnary


Except for general_arguments, this dictionary contains 3 levels
as in this sample command line :

    yunohost   monitor    info    --cpu --ram
       ^          ^         ^          ^
    (script) | category | action | parameters


Above example will lead to the function 'monitor_info(args)'
in the file 'yunohost_monitor.py' with 'cpu' and 'ram'
stored in args dictionnary.

Usage:
    You can add a category at the first level, action at the second one,
    and arguments at the third one.
    If a connexion is needed for the action, don't forget to add it to
    the action parameters (ldap, repo, dns or firewall).

Documentation:
    You can see all arguments settings at the argparse documentation:
    http://docs.python.org/dev/library/argparse.html
    #argparse.ArgumentParser.add_argument

    Don't forget to turn argument as dictionnary ('setting' : 'value')

"""
action_map = { 
    #############################
    #       General args        #
    #############################
    'general_arguments' : {
        '-v' : {
            'full'      : '--version',
            'help'      : _("Display %(prog)s version"),
            'action'    : 'version',
            'version'   : '%(prog)s ' + __version__,
        },
    },
    #############################
    #           User            #
    #############################
    'user' : {
        'category_help' : _("Manage users"),
        'actions'       : {
            ### user_list()
            'list' : {
                'action_help'   : _("List users"),
                'connections'   : ['ldap'],
                'arguments'     : {
                   '--fields' : {
                        'help'      : _("fields to fetch"),
                        'nargs'     : '+',
                   },
                   '-f' : {
                        'full'      : '--filter',
                        'help'      : _("LDAP filter used to search"),
                   },
                   '-l' : {
                        'full'      : '--limit',
                        'help'      : _("Maximum number of user fetched"), 
                   },
                   '-o' : {
                        'full'      : '--offset',
                        'help'      : _("Starting number for user fetching"), 
                   },
                }
            },
            ### user_create()
            'create' : {
                'action_help'   : _("Create user"),
                'connections'   : ['ldap'],
                'arguments'     : {
                    '-u' : {
                        'full'      : '--username',
                        'help'      : _("Must be unique"),
                    },
                    '-f' : {
                        'full'      : '--firstname',
                    },
                    '-l' : {
                        'full'      : '--lastname',
                    },
                    '-m' : {
                        'full'      : '--mail',
                        'help'      : _("Main mail address, must be unique"),
                    },
                    '-p' : {
                        'full'      : '--password',
                    }, 
                }
            },
            ### user_delete()
            'delete' : {
                'action_help'   : _("Delete user"),
                'connections'   : ['ldap'],
                'arguments'     : {
                    'users' : {
                        'help'      : _("Username of users to delete"),
                        'nargs'     : '+',
                    },
                }
            },
            ### user_update()
            'update' : {
                'action_help'   : _("Update user informations"),
                'connections'   : ['ldap'],
                'arguments'     : {
                    'user' : {
                        'help'      : _("Username of user to update"),
                    },
                    '-f' : {
                        'full'      : '--firstname',
                    },
                    '-l' : {
                        'full'      : '--lastname',
                    },
                    '-m' : {
                        'full'      : '--mail',
                    },
                    '-cp' : {
                        'full'      : '--change-password',
                        'help'      : _("New password to set"),
                        'metavar'   : 'PASSWORD',
                    },
                    '--add-mailforward' : {
                        'help'      : _("Mailforward addresses to add"),
                        'nargs'     : '+',
                        'metavar'   : 'MAIL',
                    },
                    '--remove-mailforward' : {
                        'help'      : _("Mailforward addresses to remove"),
                        'nargs'     : '+',
                        'metavar'   : 'MAIL',
                    },
                    '-add-mailalias' : {
                        'help'      : _("Mail aliases to add"),
                        'nargs'     : '+',
                        'metavar'   : 'MAIL',
                    },
                    '-remove-mailalias' : {
                        'help'      : _("Mail aliases to remove"),
                        'nargs'     : '+',
                        'metavar'   : 'MAIL',
                    },
                }
            },
            ### user_info()
            'info'      : {
                'action_help'   : _("Get user informations"),
                'connections'   : ['ldap'],
                'arguments'     : {
                    'user' : {
                        'nargs'     : '?',
                    }, 
                    '-m' : {
                        'full'      : '--mail',
                    }, 
                    '-cn' : {
                        'full'      : '--fullname',
                    }, 
                }
            },
        }
    },
    #############################
    #          Domain           #
    #############################
    'domain' : {
        'category_help' : _("Manage domains"),
        'actions'       : {
            ### domain_list()
            'list' : {
                'action_help'   : _("List domains"),
                'connections'   : ['ldap'],
                'arguments'     : {
                    '-f' : {
                        'full'      : '--filter',
                        'help'      : _("LDAP filter used to search"),
                    },
                    '-l' : {
                        'full'      : '--limit',
                        'help'      : _("Maximum number of domain fetched"), 
                    },
                    '-o' : {
                        'full'      : '--offset',
                        'help'      : _("Starting number for domain fetching"), 
                   },
                }
            },
            ### domain_add()
            'add' : {
                'action_help'   : _("Create a custom domain"),
                'connections'   : ['ldap'],
                'arguments'     : {
                    'domain' : {
                        'help'      : _("Domain name to add"),
                    }
                }
            },
            ### domain_remove()
            'remove' : {
                'action_help'   : _("Delete domains"),
                'connections'   : ['ldap'],
                'arguments'     : {
                    'domain' : {
                        'help'      : _("Domain(s) to delete"),
                        'nargs'     : '+',
                    },
                }
            },
            ### domain_info()
            'info' : {
                'action_help'   : _("Get domain informations"),
                'connections'   : ['ldap'],
                'arguments'     : {
                    'domain' : {}
                }
            },
            ### domain_renewcert()
            'renewcert' : {
                'action_help'   : _("Renew domain certificate"),
                'arguments'     : {
                    'domain' : {}
                }
            },
        }
    },
    #############################
    #            App            #
    #############################
    'app' : {
        'category_help' : _("Manage apps"),
        'actions'       : {
            ### app_list()
            'list' : {
                'action_help'   : _("List apps"),
                'connections'   : ['ldap'],
                'arguments'     : {
                    '--fields' : {
                        'help'      : _("fields to fetch"),
                        'nargs'     : '+',
                    },
                    '-f' : {
                        'full'      : '--filter',
                        'help'      : _("LDAP filter used to search"),
                    },
                    '-l' : {
                        'full'      : '--limit',
                        'help'      : _("Maximum number of app fetched"), 
                    },
                    '-o' : {
                        'full'      : '--offset',
                        'help'      : _("Starting number for app fetching"), 
                   },
                }
            },
            ### app_install() TODO: Write help
            'install' : {
                'action_help'   : _("Install apps"),
                'connections'   : ['ldap', 'dns'],
                'arguments'     : {
                    'app' : {
                        'nargs'     : '+',
                    },
                    '-d' : {
                        'full'      : '--domain',
                    },
                    '-p' : {
                        'full'      : '--path',
                    },
                    '-l' : {
                        'full'      : '--label',
                    },
                    '--public' : {
                        'action'    : 'store_true',
                    },
                    '--protected' : {
                        'action'    : 'store_true',
                    },
                }
            },
            ### app_remove() TODO: Write help
            'remove' : {
                'action_help'   : _("Remove app"),
                'connections'   : ['ldap', 'dns'],
                'arguments'     : {
                    'app' : {
                        'help'      : _("App(s) to delete"),
                        'nargs'     : '+',
                    },
                }
            },
            ### app_upgrade()
            'upgrade' : {
                'action_help'   : _("Upgrade app"),
                'connections'   : ['ldap'],
                'arguments'     : {
                    'app' : {
                        'help'      : _("App(s) to upgrade (default all)"),
                        'nargs'     : '*',
                    },
                }
            },
            ### app_info() TODO: Write help
            'info' : {
                'action_help'   : _("Get app informations"),
                'connections'   : ['ldap'],
                'arguments'     : {
                    'app' : {},
                }
            },
            ### app_addaccess() TODO: Write help
            'addaccess' : {
                'action_help'   : _("Grant access right to users (everyone by default)"),
                'connections'   : ['ldap'],
                'arguments'     : {
                    'app' : {
                        'nargs'     : '+',
                    },
                    '-u' : {
                        'full'      : '--user',
                        'nargs'     : '+',
                    },
                }
            },
            ### app_removeaccess() TODO: Write help
            'removeaccess' : {
                'action_help'   : _("Revoke access right to users (everyone by default)"),
                'connections'   : ['ldap'],
                'arguments'     : {
                    'app' : {
                        'nargs'     : '+',
                    },
                    '-u' : {
                        'full'      : '--user',
                        'nargs'     : '+',
                    },
                }
            },
        }
    },
    #############################
    #        Repository         #
    #############################
    'repo' : {
        'category_help' : _("Manage app repositories"),
        'actions'       : {
            ### repo_list()
            'list' : {
                'action_help'   : _("List repositories"),
                'connections'   : ['repo'],
                'arguments'     : {
                    '-f' : {
                        'full'      : '--filter',
                        'help'      : _("LDAP filter used to search"),
                    },
                    '-l' : {
                        'full'      : '--limit',
                        'help'      : _("Maximum number of repository fetched"), 
                    },
                    '-o' : {
                        'full'      : '--offset',
                        'help'      : _("Starting number for repository fetching"), 
                   },
                }
            },
            ### repo_add()
            'add' : {
                'action_help'   : _("Add app repository"),
                'connections'   : ['repo'],
                'arguments'     : {
                    'url' : {
                        'help'      : _("URL of the repository"),
                    },
                    '-n' : {
                        'full'      : '--name',
                        'help'      : _("Unique name of the repository"),
                    },
                }
            },
            ### repo_remove()
            'remove' : {
                'action_help'   : _("Remove repository"),
                'connections'   : ['repo'],
                'arguments'     : {
                    'repo' : {
                        'help'      : _("Name or URL of the repository"),
                    },
                }
            },
            'update' : {
                'action_help'   : _("Update app list from the repositories"),
                'connections'   : ['repo'],
            },
        }
    },
    #############################
    #          Monitor          #
    #############################
    'monitor' : {
        'category_help' : _("Monitoring functions"),
        'actions'       : {
            'info': {
                'action_help'   : _("Check System"),
                'arguments'     : {
                    '-m' : {
                        'full'      : '--memory',
                        'help'      : _("Check Memory"),
                        'action'    : 'store_true',
                    },
		            '-c' : {
                        'full'      : '--cpu',
                        'help'      : _("Check CPU"),
	                    'action'    : 'store_true',		
                    },
                    '-d' : {
                        'full'      : '--disk',
                        'help'      : _("Check Disk"),
                        'action'    : 'store_true',
                    },
                    '-i' : {
                        'full'      : '--ifconfig',
                        'help'      : _("Ifconfig"),
                        'action'    : 'store_true',
                    },
                    '-u' : {
                        'full'      : '--uptime',
                        'help'      : _("Show Uptime"),
                        'action'    : 'store_true',
                    },
					'-p' : {
                        'full'      : '--process',
                        'help'      : _("Show Process Account"),
                        'action'    : 'store_true',
                    },
                }
            },
        }
    },
    #############################
    #         Firewall          #
    #############################
    'firewall' : {
        'category_help' : _("Manage firewall rules"),
        'actions'       : {
            ### firewall_list()
            'list' : {
                'action_help'   : _("List all firewall rules"),
                'connections'   : ['firewall'],
            },
            ### firewall_allow()
            'allow' : {
                'action_help'   : _("Allow connection port/protocol"),
                'connections'   : ['firewall'],
                'arguments'     : {
                    'port' : {
                        'help'      : _("Port to open"),
                    },
                    'protocol' : {
                        'help'      : _("Protocol associated with port"),
                        'choices'   : ['UDP', 'TCP', 'Both'],
                    },
                    'name' : {
                        'help'      : _("Reference name of the rule"),
                    },
                }
            },
            ### firewall_disallow()
            'disallow' : {
                'action_help'   : _("Disallow connection"),
                'connections'   : ['firewall'],
                'arguments'     : {
                    'name' : {
                        'help'      : _("Reference name of the rule to delete"),
                    },
                }
            },
        }
    },
    #############################
    #         Firewall          #
    #############################
    'firewall' : {
        'category_help' : _("Manage firewall rules"),
        'actions'       : {
            ### firewall_list()
            'list' : {
                'action_help'   : _("List all firewall rules"),
                'connections'   : ['firewall'],
            },
            ### firewall_allow()
            'allow' : {
                'action_help'   : _("Allow connection port/protocol"),
                'connections'   : ['firewall'],
                'arguments'     : {
                    'port' : {
                        'help'      : _("Port to open"),
                    },
                    'protocol' : {
                        'help'      : _("Protocol associated with port"),
                        'choices'   : ['UDP', 'TCP', 'Both'],
                    },
                    'name' : {
                        'help'      : _("Reference name of the rule"),
                    },
                }
            },
            ### firewall_disallow()
            'disallow' : {
                'action_help'   : _("Disallow connection"),
                'connections'   : ['firewall'],
                'arguments'     : {
                    'name' : {
                        'help'      : _("Reference name of the rule to delete"),
                    },
                }
            },
        }
    },
    #############################
    #           Tools           #
    #############################
    'tools' : {
        'category_help' : _("Specific tools"),
        'actions'       : {}
    },
}

def parse_dict(action_map):
    """
    Turn action dictionnary to parser, subparsers and arguments
    
    Keyword arguments:
        action_map -- Multi-level dictionnary of categories/actions/arguments list
    
    Returns: 
        Namespace of args

    """
    # Intialize parsers
    parsers = subparsers_category = subparsers_action = {}
    parsers['general'] = argparse.ArgumentParser()
    subparsers = parsers['general'].add_subparsers()

    # Add general arguments
    for arg_name, arg_params in action_map['general_arguments'].items():
        if arg_params['full']:
            arg_fullname = arg_params['full']
            del arg_params['full']
            parsers['general'].add_argument(arg_name, arg_fullname, **arg_params)
        else:
            parsers['general'].add_argument(arg_name, **arg_params)

    del action_map['general_arguments'] 

    # Split categories into subparsers
    for category, category_params in action_map.items():
        if 'category_help' not in category_params: category_params['category_help'] = ''
        subparsers_category[category] = subparsers.add_parser(category, help=category_params['category_help'])
        subparsers_action[category] = subparsers_category[category].add_subparsers()
        # Split actions
        if 'actions' in category_params:
            for action, action_params in category_params['actions'].items():
                if 'action_help' not in action_params: action_params['action_help'] = ''
                parsers[category + '_' + action] = subparsers_action[category].add_parser(action, help=action_params['action_help'])
                # Set the action s related function
                parsers[category + '_' + action].set_defaults(
                    func=str_to_func('yunohost_' + category + '.' 
                                     + category + '_' + action)) 
                # Add arguments
                if 'arguments' in action_params:
                    for arg_name, arg_params in action_params['arguments'].items():
                        if 'full' in arg_params:
                            arg_fullname = arg_params['full']
                            del arg_params['full']
                            parsers[category + '_' + action].add_argument(arg_name, arg_fullname, **arg_params)
                        else:
                            parsers[category + '_' + action].add_argument(arg_name, **arg_params)

    return parsers['general'].parse_args()

    
def main(action_map):
    """ 
    Main instructions 
   
    Parse the action_dict and execute the action-specific function,
    then print json or pretty result if executed in a tty :)

    Returns:
        int -- 0 or error code
    
    """ 
    args = parse_dict(action_map)
    connections = connect_services(action_map)
    
    try:
        if connections:
            result = args.func(vars(args), connections)
        else:
            result = args.func(vars(args))
    except TypeError, error:
        print error
        print(_("Not (yet) implemented function"))
        return 1
    except YunoHostError, error:
        display_error(error)
        return error.code
    else: 
        if os.isatty(1):
            pretty_print_dict(result)
        else:
            print(json.dumps(result))
    finally:
        disconnect_services(connections)

    return 0        

if __name__ == '__main__':
    sys.exit(main(action_map))