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

# Copyright 2012-2015 Calculate Ltd. 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.
import pickle

import sys
import termios
import os
from os import path
from fcntl import ioctl
from array import array
import threading

from calculate.lib.utils.colortext import get_terminal_print
from calculate.lib.utils.colortext.palette import TextState
from calculate.lib.utils.text import tableReport
from calculate.lib.cl_print import color_print
from calculate.lib.datavars import VariableError, CriticalError
from calculate.lib.cl_lang import setLocalTranslate
from calculate.core.server.api_types import FieldAdapter
from calculate.lib.utils.tools import ignore
from calculate.lib.utils.files import makeDirectory
from calculate.core.result_viewer import ResultViewer
from calculate.core.server.loaded_methods import LoadedMethods
from calculate.core.result_viewer_gui import ProgressGui, ErrorGui, WarningGui
from gen_pid import ProcessStatus

from methods_func import (get_method_argparser, collect_object,
                          check_result_msg, get_param_pwd, _print,
                          display_error)
from api_types import ViewInfo
from cert_cmd import parse
from methods_func import GotErrorField
from func import shortTraceback, CommonMethods, CommonLink

_ = lambda x: x
setLocalTranslate('cl_core3', sys.modules[__name__])
from itertools import izip, ifilter


class LocalCall(object):
    method_status = ProcessStatus.NotFound
    no_progress = None
    gui_progress = None
    gui_warning = None
    no_questions = None

    def startprocess(self, sid, target=None, method=None, method_name=None,
                     auto_delete=False, args_proc=()):
        """ start process """
        if "LANG" in os.environ:
            curThread = threading.currentThread()
            curThread.lang = os.environ["LANG"]
        com = target(self.no_progress, self.gui_progress, self.gui_warning,
                     self.no_questions)
        if len(com.__class__.__bases__) > 1 and \
                hasattr(com.__class__.__bases__[1], '__init__'):
            com.__class__.__bases__[1].__init__(com)
        com.method_name = method_name
        com.method_status = ProcessStatus.Worked
        if getattr(com, method)(*args_proc):
            self.method_status = ProcessStatus.SuccessFinished
        else:
            self.method_status = ProcessStatus.FailedFinished
        com.method_status = self.method_status
        self.del_pid_file(os.getpid(), com.clVars)
        return 0

    def del_pid_file(self, pid, clVars=None):
        if clVars:
            pids = clVars.Get('core.cl_core_pids_path')
        else:
            pids = '/tmp'
        pid_file = path.join(pids, '%d.pid' % pid)
        with ignore(OSError):
            if path.exists(pid_file):
                os.unlink(pid_file)

    class Common(CommonMethods, CommonLink):
        """ class to interact with the processes """

        def __init__(self, no_progress, gui_progress, gui_warning,
                     no_questions):
            self.pid = 0
            self.method_name = ""
            self.method_status = ProcessStatus.Worked
            self.color_print = color_print()
            self.result_viewer = ResultViewer()
            if no_questions:
                self.result_viewer.set_no_questions()
            if no_progress:
                self.result_viewer.set_no_progress()
            if gui_progress:
                self.result_viewer = ErrorGui(ProgressGui(self.result_viewer))
            if gui_warning:
                self.result_viewer = WarningGui(self.result_viewer)
            self.set_link(self.result_viewer)

        def pauseProcess(self):
            self.method_status = ProcessStatus.Paused
            self.writeFile()

        def resumeProcess(self):
            self.method_status = ProcessStatus.Worked
            self.writeFile()

        def writeFile(self):
            """ write data in file """
            from calculate.core.server.gen_pid import ProcessMode

            if os.getuid():
                return
            pid = os.getpid()
            pids = self.clVars.Get('core.cl_core_pids_path')
            # пропустить создание файла если идет сборка пакета
            if self.clVars.Get('cl_ebuild_phase'):
                return
            build_id = ""
            try:
                from calculate.builder.variables.action import Actions

                if self.clVars.Get('cl_action') in Actions.All:
                    build_id = self.clVars.Get('builder.cl_builder_id')
            except Exception:
                pass
            if not os.path.exists(pids):
                makeDirectory(pids)
            pid_file = path.join(pids, '%d.pid' % pid)
            try:
                with open(pid_file, 'w') as f:
                    d = {'name': self.method_name,
                         'mode': ProcessMode.LocalCall,
                         'os_pid': pid,
                         'status': self.method_status,
                         'id': build_id}
                    pickle.dump(d, f)
            except (IOError, OSError) as e:
                print str(e)
                print _("Failed to write the PID file %s!") % pid_file

        def isInteractive(self):
            """
            Check interactive ability
            """
            return sys.stdin.isatty()


def cout_progress(string=None):
    try:
        h, w = array('h', ioctl(sys.stderr, termios.TIOCGWINSZ, '\0' * 8))[:2]
    except IOError:
        return
    sys.stdout.write('\r' + (' ' * w))
    if string:
        sys.stdout.write('\r' + string)
    else:
        sys.stdout.write('\r')
    sys.stdout.flush()


def local_method(metaclass, args, unknown_args):
    """
    Call method from metaclass, check method existing.

    Generate help, for method, run method by 'call_method'.
    """
    import os

    sym_link = os.path.basename(sys.argv[0])
    if sym_link != 'cl-core':
        if sym_link in LoadedMethods.conMethods.keys():
            args.method = LoadedMethods.conMethods[sym_link][0]
        else:
            _print(_("Method not found for %s") % sym_link)
            sys.exit(1)

    if args.list_methods:
        for k, v in sorted(LoadedMethods.conMethods.items(),
                           key=lambda x: x[1]):
            name, user, title = v
            print "%s - %s" % (name, title)
        return 0

    colorPrint = color_print()
    metaObject = metaclass()
    method_name = args.method
    method_view_name = method_name + '_view'
    if args.method and args.help:
        view_obj = ViewInfo()
        view_obj.step = None
        view_obj.expert = True
        view_obj.brief = None
        view_obj.onlyhelp = True
        try:
            view = getattr(metaObject, method_view_name)(0, view_obj)
        except AttributeError:
            colorPrint.printERROR(_('Method not found: ') + method_view_name)
            return 1
        try:
            method_parser = get_method_argparser(view, args, cl_core=True)
        except Exception:
            # import traceback
            # for i in apply(traceback.format_exception, sys.exc_info()):
            #    sys.stderr.write(i)
            # sys.stderr.flush()
            metaObject.clear_cache(0, method_name)
            return 1
        method_parser.print_help()
    else:
        try:
            call_method(metaObject, args, unknown_args, colorPrint)
            metaObject.clear_cache(0, method_name)
            return metaObject.method_status
        except (ValueError, CriticalError) as e:
            colorPrint.printERROR(str(e))
            # colorPrint.printERROR(shortTraceback(*sys.exc_info()))
        except (KeyboardInterrupt, EOFError):
            colorPrint.printERROR(_('Manually interrupted'))
        except (GotErrorField,):
            pass
        except Exception:
            colorPrint.printERROR(shortTraceback(*sys.exc_info()))
            pass
            #            print 'Error: ', e
    metaObject.clear_cache(0, method_name)


def call_method(metaObject, args, unknown_args, colorPrint):
    """
    Function for call method through metaObject and args
    """
    method_name = args.method
    stdin_passwd = args.stdin_passwd
    method_view_name = method_name + '_view'
    metaObject.no_progress = args.no_progress
    metaObject.gui_progress = args.gui_progress
    metaObject.gui_warning = args.gui_warning
    metaObject.no_questions = False
    view_obj = ViewInfo()
    view_obj.step = None
    view_obj.expert = True
    view_obj.brief = None
    view_obj.onlyhelp = True

    try:
        view = getattr(metaObject, method_view_name)(0, view_obj)
    except AttributeError:
        colorPrint.printERROR(_('Method not found: ') + method_name)
        return None
    method_parser = get_method_argparser(view, args, cl_core=True)
    param_object = create_param_object(view)
    try:
        unknown_args = method_parser.fixBoolVariables(unknown_args)
        args, unknown_args = method_parser.parse_known_args(unknown_args)
        no_questions = args.no_questions
        metaObject.no_questions = no_questions
    except SystemExit:
        return 1
    except Exception:
        import traceback

        for i in apply(traceback.format_exception, sys.exc_info()):
            sys.stderr.write(i)
        sys.stderr.flush()
        raise
    for i in unknown_args:
        if i.startswith('-'):
            if i in parse(True).parse_known_args()[1]:
                _print(_('Unknown parameter'), i)
                return 1
        else:
            _print(_('Unknown argument'), i)
            return 1

    param_object, steps = collect_object(None, param_object, view, args,
                                         stdin_passwd=stdin_passwd)
    if view.has_brief:
        setattr(param_object, 'CheckOnly', True)
        check_res = {}
        while True:
            method_result = getattr(metaObject, method_name)(0, param_object)
            if not method_result:
                print _('Method not available')
                return None

            if method_result[0].type and method_result[0].type != "pid":
                check_res = check_result_msg(method_result, view, check_res,
                                             args)
                if not check_res:
                    return None
                else:
                    param_object = get_param_pwd(check_res, view,
                                                 param_object,
                                                 stdin_passwd=stdin_passwd)
            else:
                break

        view_obj = ViewInfo()
        view_obj.step = None
        view_obj.expert = True
        view_obj.brief = True
        view_obj.onlyhelp = False
        try:
            view = getattr(metaObject, method_view_name)(0, view_obj)
        except AttributeError:
            colorPrint.printERROR(_('Method not found: ') + method_name)

        print_brief(view, steps.label)
        for group in view.groups:
            for field in group.fields:
                if "error" in field.name:
                    return None
        if not no_questions:
            if stdin_passwd:
                colorPrint.printERROR("Could not use the interactive mode. "
                                      "Use option '-f' for run the process.")
                return None
            try:
                ask = ResultViewer().askConfirm(_("Run process?"))
            except KeyboardInterrupt:
                ask = "no"
            if ask.lower() in ['n', 'no']:
                colorPrint.printERROR(_('Manually interrupted'))
                return None

    setattr(param_object, 'CheckOnly', False)
    try:
        method_result = getattr(metaObject, method_name)(0, param_object)
    except VariableError, e:
        _print(e)
        return None
    if not method_result:
        colorPrint.printERROR(_('method unavailable'))
        return None
    for ReturnedMessage in method_result:
        if ReturnedMessage.type and ReturnedMessage.type != "pid":
            display_error(ReturnedMessage, args, view.groups)
            # params_text = ''
            # for Group in view.groups:
            #    for field in Group.fields:
            #        if field.name == ReturnedMessage.field:
            #            params_text += getErrorOnParam(args, field)
            # colorPrint.printERROR('\r' + params_text % \
            #                      str(ReturnedMessage.message))
            return None
    return method_result


def create_param_object(view):
    param_object = type('collect_object', (object,), {})
    param_object.CheckAll = True
    param_object._type_info = {}
    for Group in view.groups:
        if not Group.fields:
            continue
        for field in Group.fields:
            setattr(param_object, field.name, None)
            param_object._type_info[field.name] = None
    return param_object


def print_brief(view, brief_label):
    for Group in view.groups:
        if Group.name:
            if not Group.fields:
                continue
        print_brief_group(Group.fields, Group.name)


class ColorTable(tableReport):
    def __init__(self, head, body, printer, head_printer=None,
                 line_printer=None, body_printer=None):
        super(ColorTable, self).__init__(None, head, body, colSpan=0)
        self.default_printer = printer
        self.line_printer = line_printer or printer
        self.head_printer = head_printer or printer
        self.body_printer = body_printer or printer
        self.head = head
        self.body = body


class Display(object):
    def __init__(self):
        self._print = get_terminal_print(color_print().defaultPrint)

    def print_info(self, label, value):
        GREEN = TextState.Colors.GREEN
        self.display_asterisk(GREEN)
        self._print(_("%s: ") % label)
        WHITE = TextState.Colors.WHITE
        self._print.foreground(WHITE)(value)
        self._print("\n")

    def print_label(self, label):
        GREEN = TextState.Colors.GREEN
        self.display_asterisk(GREEN)
        self._print(_("%s: ") % label)
        self._print("\n")

    def display_asterisk(self, color):
        self._print(" ")
        self._print.foreground(color).bold("*")
        self._print(" ")

    def print_error(self, message):
        RED = TextState.Colors.RED
        self.display_asterisk(RED)
        self._print(message)
        self._print("\n")

    def print_warning(self, message):
        YELLOW = TextState.Colors.YELLOW
        self.display_asterisk(YELLOW)
        self._print(message)
        self._print("\n")

    def print_table(self, data, head):
        WHITE = TextState.Colors.WHITE
        ColorTable(head, data, self._print,
                   body_printer=self._print.foreground(
                       WHITE).clone()).printReport(False)
        # sys.stdout.write('%s\n' % printTable(data, head))

    def print_group(self, label):
        self._print(label)
        self._print("\n")


class InformationElement(object):
    def __init__(self, field, display):
        self.value = ""
        self.label = ""
        self.display = display

    @classmethod
    def from_field(cls, field, display):
        if field.type == 'steps':
            return None
        map_elements = {'input': ValueInfo,
                        'openfile': ValueInfo,
                        'combo': ChoiceInfo,
                        'comboEdit': ChoiceInfo,
                        'radio': ChoiceInfo,
                        'file': ChoiceInfo,
                        'multichoice': MultiChoiceInfo,
                        'multichoice_add': MultiChoiceInfo,
                        'selecttable': MultiChoiceInfo,
                        'selecttable_add': MultiChoiceInfo,
                        'error': ErrorInfo,
                        'check': CheckInfo,
                        'check_tristate': CheckInfo,
                        'table': TableInfo
                        }
        if field.element in map_elements:
            return map_elements[field.element](field, display)
        return None

    def show(self):
        self.display.print_info(self.label, self.value)


class ValueInfo(InformationElement):
    def __init__(self, field, display):
        super(ValueInfo, self).__init__(field, display)
        self.value = field.value or ''
        self.label = field.label


class CheckInfo(InformationElement):
    def __init__(self, field, display):
        super(CheckInfo, self).__init__(field, display)
        self.label = field.label
        map_answer = {'on': _('yes'), 'off': _("no"), 'auto': _('auto')}
        self.value = map_answer.get(field.value, field.value)


class ChoiceInfo(InformationElement):
    def __init__(self, field, display):
        super(ChoiceInfo, self).__init__(field, display)
        self.label = field.label or ''
        if field.choice and field.comments:
            map_comment = dict(zip(field.choice, field.comments))
            self.value = map_comment.get(field.value, field.value) or ''
        else:
            self.value = field.value if field.value else ''


class MultiChoiceInfo(InformationElement):
    def __init__(self, field, display):
        super(MultiChoiceInfo, self).__init__(field, display)
        self.label = field.label or ''
        if field.listvalue:
            value = field.listvalue
            # удалить пустой первый элемент (особенности wsdl)
            if value and not value[0]:
                value.pop(0)
            if field.choice and field.comments:
                map_comment = dict(zip(field.choice, field.comments))
            else:
                map_comment = {}
            self.value = ", ".join([map_comment.get(x, x) or '' for x in value])
        else:
            self.value = field.value or ""


class ErrorInfo(InformationElement):
    def __init__(self, field, display):
        super(ErrorInfo, self).__init__(field, display)
        self.label = field.label

    def show(self):
        self.display.print_error(self.label)


class TableInfo(InformationElement):
    """
    Табличная информация
    """

    def map_row(self, row, typedata):
        map_answer = {'on': _('yes'), 'off': _("no"), 'auto': _('auto')}
        for cell, typefield in izip(row, typedata):
            if typefield in ['check', 'check_tristate']:
                yield map_answer.get(cell, cell) or ""
            elif "password" in typefield:
                yield "***"
            else:
                yield cell or ""

    def __init__(self, field, display):
        super(TableInfo, self).__init__(field, display)
        self.label = field.label
        self.head = field.tablevalue.head

        # удаление первого элемента строки (для wsdl)
        body = [x[1:] if x and not x[0] else x for x in field.tablevalue.body]

        if not filter(None, map(lambda x: x, body)):
            self.body = None
        else:
            type_values = [x.typefield for x in field.tablevalue.values]
            self.body = [list(self.map_row(x, type_values)) for x in body]

    def show(self):
        if self.body:
            self.display.print_label(self.label)
            self.display.print_table(self.body, self.head)


def print_brief_group(Fields, group_name):
    display = Display()
    show_group = True
    try:
        for element in ifilter(None,
                               (InformationElement.from_field(
                                   FieldAdapter.from_detect(x),
                                   display)
                                for x in Fields if not x.uncompatible)):
            if show_group:
                display.print_group(group_name)
                show_group = False
            element.show()
    except Exception:
        import traceback

        traceback.print_exc()
        raise


class Methods(LocalCall.Common, object):
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(Methods, cls).__new__(
                cls, *args, **kwargs)
        return cls._instance

    def __init__(self):
        LocalCall.Common.__init__(self, False, False, False, False)
