#-*- coding: utf-8 -*-

# Copyright 2012-2016 Mir Calculate. http://www.calculate-linux.org
#
#  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.
from calculate.core.server.local_call import Display, Methods

from sudsds import WebFault
from sudsds.transport import TransportError

from client_class import Client_suds
import traceback as tb
import time, logging
import os, sys
import threading, urllib2
from pid_information import client_list_methods
from cert_func import client_post_auth, client_post_request, client_get_cert,\
                    client_post_cert, get_password_from_daemon, clear_password
from sid_func import session_clean, client_session_info, client_session_list
from cert_verify import get_CRL, VerifyError

import M2Crypto, OpenSSL
from calculate.core.datavars import DataVarsCore
from client_class import HTTPSClientCertTransport
from methods_func import call_method, get_method_argparser, parse, get_view
from function import MessageReceiver, MessageDispatcher, clear, _print, \
    get_view_params
from calculate.lib.utils.files import makeDirectory, readLinesFile
from calculate.lib.cl_lang import setLocalTranslate
setLocalTranslate('cl_console3',sys.modules[__name__])


def get_message_receiver(client):
    return MessageReceiver(client, MessageDispatcher(Methods()), Display())


def get_entire_message_receiver(client, pid):
    return MessageReceiver.from_entire(client, pid,
                                       MessageDispatcher(Methods()), Display())

def client_signal(client):
    Vars = DataVarsCore()
    Vars.importCore()
    Vars.flIniFile()
    try:
        client_active = Vars.Get('core.cl_core_client_active_period')
    except:
        client_active = 15
    while True:
        if os.path.exists(client.SID_FILE) :
            fi = open(client.SID_FILE, 'r')
            temp = fi.read()
            fi.close()
            sid = int(temp)
        else:
            sid = 0
        try:
            client.service.active_client(sid)
        except:
            print _('no connection to the server!')
            raise Exception(1)
        time.sleep(float(client_active))

class StoppableThread(threading.Thread):
    def __init__(self):
        super(StoppableThread, self).__init__()
        self._stop = threading.Event()
        self._pause = threading.Event()
        self._paused = threading.Event()

    def run(self):
        l = ['|','/','-','\\','|','/','-','\\']
        i = 0
        while True:
            for i in l:
                sys.stdout.write("\r\r" + i)
                sys.stdout.flush()
                time.sleep(.1)
                while self.paused() and not self.stopped():
                    self._paused.set()
                if self.stopped():
                    sys.stdout.write("\b")
                    sys.stdout.flush()
                    return 0
                    

    def pause(self):
        self._pause.set()
        while not self._paused.is_set():
            self._paused.clear()
            sys.stdout.write("\r")
            sys.stdout.flush()

    def resume(self):
        self._pause.clear()

    def stop(self):
        self._stop.set()

    def stopped(self):
        return self._stop.isSet()

    def paused(self):
        return self._pause.isSet()

def connect_with_cert(cert, path_to_cert, url, args, wait_thread, clVarsCore,
                      crypto_Error, Connect_Error):
    flag_thread_start = False
    cert_name = cert
    CERT_FILE = os.path.join(path_to_cert, cert_name + '.crt')
    CERT_KEY = os.path.join(path_to_cert, cert_name + '.key')
    if not os.path.isfile(CERT_FILE) or not os.path.isfile(CERT_KEY):
        Connect_Error = 1
        return (None, 1, crypto_Error, False, None)
    client = None

    bio = M2Crypto.BIO.openfile(CERT_KEY)
    rsa = M2Crypto.m2.rsa_read_key(bio._ptr(),lambda *unused: None)
    if not rsa:
        port = args.port or clVarsCore.Get('core.cl_core_port')
        store_passwd = get_password_from_daemon(args.host, args.port,
                                                wait_thread)
    if 'store_passwd' in locals():
        key_passwd = store_passwd
    else:
        key_passwd = None
    try:
        ca_certs = os.path.join(path_to_cert, 'ca/ca_root.crt')
        client = Client_suds(url, transport=HTTPSClientCertTransport \
             (CERT_KEY, CERT_FILE, path_to_cert, password=key_passwd,
              ca_certs = ca_certs, wait_thread = wait_thread))
        if not wait_thread.isAlive():
            wait_thread = StoppableThread()
            flag_thread_start = True
            wait_thread.start()
        client.wsdl.services[0].setlocation(url)
        client.set_parameters (path_to_cert, CERT_FILE, CERT_KEY)
        wait_thread.stop()
        client_post_cert(client, clVarsCore)
        Connect_Error = 0
    except VerifyError, e:
        Connect_Error = 1
    except OpenSSL.crypto.Error, e:
        Connect_Error = 1
        crypto_Error = 1
    except urllib2.URLError, e:
        Connect_Error = 1
    except Exception, e:
        if e.message == 3:
            wait_thread.stop()
            sys.exit(1)
        Connect_Error = 1
    if flag_thread_start:
        wait_thread.stop()
    return (client, Connect_Error, crypto_Error, 
            True if 'store_passwd' in locals() else False,
            e if 'e' in locals() else None)

def get_server_hostname(host, path_to_cert):
    compliance_file = os.path.join(path_to_cert, 'compliance_server_names')
    if not os.path.isfile(compliance_file):
        fd = open(compliance_file, 'w')
        fd.close()
    for line in readLinesFile(compliance_file):
        adress, server_hostname = line.split(' ',1)
        if adress == host:
            return server_hostname
    return None

def add_server_hostname(host, path_to_cert, server_hostname):
    try:
        compliance_file = os.path.join(path_to_cert, 'compliance_server_names')
        if not os.path.isfile(compliance_file):
            fd = open(compliance_file, 'w')
            fd.close()
        temp_file = ''
        find_flag = False
        for line in readLinesFile(compliance_file):
            adress, temp_server_hostname = line.split(' ',1)
            if adress == host:
                temp_file += "%s %s\n" %(adress, server_hostname)
                find_flag = True
            else:
                temp_file += line+'\n'
        if not find_flag:
            temp_file += "%s %s\n" %(host, server_hostname)
        fd = open(compliance_file, 'w')
        fd.write(temp_file)
        fd.close()
        return True
    except Exception, e:
        print e
        return False

def https_server(client, args, unknown_args, url, clVarsCore, wait_thread):
    client_post_auth(client)

#    sym_link = os.path.basename(sys.argv[0])
#    if sym_link != 'cl-console':
#        wait_thread.stop()
#        results = client.service.get_methods(client.sid, 'console')
#        find_flag = False
#        if hasattr (results, 'stringArray'):
#            for _array in results.stringArray:
#                if _array.string[0] == sym_link:
#                    args.method = _array.string[1]
#                    find_flag = True
#                    break
#        if not find_flag:
#            _print (_('Method not found for %s') %sym_link)

    if args.stop_consoled:
        wait_thread.stop()
        os.system('cl-consoled --stop')
        return 0

    if args.session_clean:
        wait_thread.stop()
        session_clean(client)

    if args.session_info or args.session_num_info:
        wait_thread.stop()
        client_session_info(client, args.session_num_info)
        return 0

    if args.session_list:
        wait_thread.stop()
        client_session_list(client)
        return 0

    if args.list_pid:
        wait_thread.stop()
        if args.dump:
            from pid_information import client_pid_info
            client_pid_info(client)
        else:
            from pid_information import client_list_pid
            client_list_pid(client)
        return 0

    try:
        client.frame_period = clVarsCore.Get('core.cl_core_get_frame_period')
    except:
        client.frame_period = 2

    if args.pid_res:
        wait_thread.stop()
        mr = get_entire_message_receiver(client, args.pid_res)
        if mr:
            mr.get_messages()
            if not args.keep_result:
                client.service.clear_pid_cache(client.sid, args.pid_res)
            return 0
        else:
            return 1

    if args.pid_kill:
        wait_thread.stop()
        from pid_information import client_pid_kill
        return client_pid_kill(client, args.pid_kill)

    retCode = 0
    if not args.method:
        wait_thread.stop()
        client_list_methods(client)
        return 1

    elif args.method and args.help:
        view_params = get_view_params(client, args.method + '_view',
                                      step = None, expert = True,
                                      onlyhelp = True)
        view = get_view(client, args.method, client.sid, view_params)
        wait_thread.stop()
        sys.stdout.write("\b")
        sys.stdout.flush()
        method_parser = get_method_argparser(view, args)
        method_parser.print_help()
        client.service.clear_method_cache(client.sid, args.method)

    else:
        method_result = call_method(client, args, unknown_args, wait_thread)
        mr = get_message_receiver(client)
        if method_result:
            client.no_progress = args.no_progress
            try:
                mr.analysis(method_result)
                #analysis(client, client.sid, method_result)
            except urllib2.URLError, e:
                _print (e)
            except KeyboardInterrupt:
                try:
                    print
                    mess = method_result[0][0]
                    pid = int(mess.message)
                    result = client.service.pid_kill(pid, client.sid)
                    if result in [0,2]:
                        print _('Process terminated')
                    elif result == -1:
                        print _("Certificate not found on the server")
                    elif result == -2:
                        print _("Session not matching your certificate")
                    elif result == 1:
                        print _("Failed to terminate the process")
                    mr.analysis(method_result)
                except Exception, e:
                    _print(e.message)

            try:
                mess = method_result[0][0]
                pid = int(mess.message)
            except:
                return 1
            retCode = \
                1 if int(client.service.pid_info(client.sid,pid)[0][1]) else 0
            if not args.keep_result:
                client.service.clear_pid_cache(client.sid, pid)
        client.service.clear_method_cache(client.sid, args.method)
    wait_thread.stop()
    return retCode

def main(wait_thread):
    parser = parse()
    args, unknown_args = parser.parse_known_args()
    wait_thread.start()

#    if os.path.basename(sys.argv[0]) != 'cl-console':
#        args.method = '_temp_'
#        args.host = 'localhost'

    if not args.method and args.help:
        wait_thread.stop()
        sys.stdout.write('\r')
        sys.stdout.flush()
        parser.print_help()
        return 0

    if not args.method:
        if unknown_args:
            wait_thread.stop()
            sys.stdout.write('\r')
            sys.stdout.flush()
            args = parser.parse_args()

    logging.basicConfig(level=logging.FATAL)
    logging.getLogger('sudsds.client').setLevel(logging.FATAL)
    logging.getLogger('sudsds.transport').setLevel(logging.FATAL)
    logging.getLogger('sudsds.transport.http').setLevel(logging.FATAL)
    logging.getLogger('sudsds.umx.typed').setLevel(logging.ERROR)

    clVarsCore = DataVarsCore()
    clVarsCore.importCore()
    clVarsCore.flIniFile()

    homePath = clVarsCore.Get('ur_home_path')

    port = args.port or clVarsCore.GetInteger('core.cl_core_port')
    host = args.host
    path_to_cert = args.path_to_cert
    if not path_to_cert:
        path_to_cert = clVarsCore.Get('core.cl_client_cert_dir')
    path_to_cert = path_to_cert.replace("~",homePath)

    for dirs in ['', 'ca', 'trusted']:
        dir_path = os.path.join(path_to_cert, dirs)
        if not os.path.isdir(dir_path):
            if not makeDirectory(dir_path):
                wait_thread.stop()
                sys.stdout.write('\r')
                sys.stdout.flush()
                print _("Failed to create directory %s") %dir_path
                return 1

    if args.update_crl:
        wait_thread.stop()
        getCRL = threading.Thread(target=get_CRL, args = (path_to_cert, ))
        getCRL.start()
        getCRL.join()
        print 'CRL updated'
        return 0

    if args.by_host:
        wait_thread.stop()
        client_post_request (path_to_cert, args)
        return 0
    if args.from_host:
        wait_thread.stop()
        client_get_cert (path_to_cert, args)
        return 0

    url = "https://%s:%s/?wsdl" %(host, port)

    clear()
    serv_hostname = get_server_hostname(host, path_to_cert)
    get_name_flag = False
    if serv_hostname:
        Connect_Error = 1
        crypto_Error = 0
        client, Connect_Error, crypto_Error, passwd_flag, e = \
                    connect_with_cert (serv_hostname, path_to_cert, url, args,
                         wait_thread, clVarsCore, crypto_Error, Connect_Error)
        if not wait_thread.isAlive():
            wait_thread = StoppableThread()
            wait_thread.start()
        get_name_flag = True
        if Connect_Error:
            if crypto_Error and passwd_flag:
                wait_thread.stop()
                sys.stdout.write('\r')
                sys.stdout.flush()
                print _('Invalid password')
                # delete password from daemon list
                clear_password(host, port)
            elif e:
                wait_thread.stop()
                if type(e.message) != int:
                    print _('Error: '), e
            get_name_flag = False

    if get_name_flag:
        try:
            client.port = port
            return_val = 1
            try:
                return_val = https_server(client, args, unknown_args, url, \
                                          clVarsCore, wait_thread)
            except urllib2.URLError, e:
                print _('Error: '), e
            except KeyboardInterrupt:
                wait_thread.stop()
                red = '\n'+'\033[31m * \033[0m'
                print red + _("Manually interrupted")
            except Exception, e:
                wait_thread.stop()
                if type(e.message) != int:
                    if e.message:
                        print e.message
                    elif e.args:
                        print e
#                    tb.print_exc()
                return 1
            wait_thread.stop()
            return return_val
        except WebFault, f:
            print _("Exception: %s") %f
            _print (f.fault)
        except TransportError, te:
            print _("Exception: %s") %te
        except KeyboardInterrupt:
            wait_thread.stop()
            red = '\n'+'\033[31m * \033[0m'
            print red + _("Manually interrupted")
        except Exception, e:
            print _("Exception: %s") %e
            tb.print_exc()
        wait_thread.stop()

    try:
        client = Client_suds(url, \
            transport = HTTPSClientCertTransport(None,None, path_to_cert))
        client.wsdl.services[0].setlocation(url)
        server_host_name = client.service.get_server_host_name()
        if not add_server_hostname(host, path_to_cert, server_host_name):
            print 'compliance_file write error!'
        del (client)
    except urllib2.URLError, e:
        wait_thread.stop()
        print '\b' + _('Failed to connect')+':', e
        return 1
    except KeyboardInterrupt:
        wait_thread.stop()
        red = '\n'+'\033[31m * \033[0m'
        print red + _("Manually interrupted")

    try:
        import glob
        all_cert_list = glob.glob(os.path.join(path_to_cert, '*.crt'))
        fit_cert_list = []
        for client_cert_path in all_cert_list:
            client_cert = client_cert_path.replace(path_to_cert, '')
            client_cert_name = client_cert.replace('.crt', '')
            if server_host_name.endswith(client_cert_name):
                fit_cert_list.append(client_cert_name)
        fit_cert_list.sort(key = len)
        Connect_Error = 1
        crypto_Error = 0
        e = None

        for i in range (0, len(fit_cert_list)):
            cert_name = fit_cert_list.pop()
            client, Connect_Error, crypto_Error, passwd_flag, e = \
                        connect_with_cert (cert_name, path_to_cert, url, args,
                        wait_thread, clVarsCore, crypto_Error, Connect_Error)
            if not wait_thread.isAlive():
                wait_thread = StoppableThread()
                wait_thread.start()

            if Connect_Error == 0:
                break

        #If the certificate file misses
        if Connect_Error:
            if crypto_Error and passwd_flag:
                wait_thread.stop()
                sys.stdout.write('\r')
                sys.stdout.flush()
                print _('Invalid password')
                # delete password from daemon list
                clear_password(host, port)
                return 1
            elif e:
                wait_thread.stop()
                if type(e.message) != int:
                    print _('Error: '), e
                return 1

            CERT_FILE = None
            CERT_KEY = None
            client = Client_suds(url, transport = HTTPSClientCertTransport \
                                         (CERT_KEY, CERT_FILE, path_to_cert))
            client.wsdl.services[0].setlocation(url)
            client.set_parameters (path_to_cert, CERT_FILE, CERT_KEY)

        client.port = port
        return_val = 1
        try:
            return_val = https_server(client, args, unknown_args, url, \
                                      clVarsCore, wait_thread)
        except urllib2.URLError, e:
            print _('Error: '), e
        except KeyboardInterrupt:
            wait_thread.stop()
            red = '\n'+'\033[31m * \033[0m'
            print red + _("Manually interrupted")
        except Exception, e:
            wait_thread.stop()
            if type(e.message) != int:
                if e.message:
                    print e.message
                elif e.args:
                    print e
#                tb.print_exc()
            return 1
        wait_thread.stop()
        return return_val
#----------------------------------------------------
    except WebFault, f:
        print _("Exception: %s") %f
        _print (f.fault)
    except TransportError, te:
        print _("Exception: %s") %te
    except KeyboardInterrupt:
        wait_thread.stop()
        red = '\n'+'\033[31m * \033[0m'
        print red + _("Manually interrupted")
    except Exception, e:
        print _("Exception: %s") %e
        tb.print_exc()
    wait_thread.stop()
