# -*- 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.loaded_methods import LoadedMethods

from soaplib.serializers.primitive import String, Integer, Boolean, string_encoding
from soaplib.serializers.clazz import Array, ClassSerializer
from soaplib.serializers import nillable_value, nillable_element
from soaplib.serializers import string_to_xml
from soaplib.service import rpc
import sys
import pickle
import os
from calculate.lib.datavars import SourceReadonlyVariable
from calculate.core.server.core_interfaces import CoreServiceInterface

_ = lambda x: x
from calculate.lib.cl_lang import setLocalTranslate

setLocalTranslate('cl_core3', sys.modules[__name__])

#####
# API
#####
class CommonInfo(ClassSerializer):
    """
    Common info for all Info classes

    cl_templates_locate - templates location
    cl_dipatch_conf - update config method
    cl_verbose_set - display appling templates
    """
    cl_templates_locate = Array(String)
    cl_dispatch_conf = String
    cl_verbose_set = Boolean

    Default = Array(String)
    CheckAll = Boolean

class LazyString(object):
    pass

def lazyable(cl):
    def to_xml(cls, value, tns, parent_elt, name='retval'):
        if isinstance(value, LazyString):
            value = str(value)
        if not isinstance(value, unicode):
            value = unicode(value, string_encoding)

        string_to_xml(cls, value, tns, parent_elt, name)

    cl.to_xml = classmethod(nillable_value(to_xml))
    return cl

String = lazyable(String)


class DataVarsSerializer(ClassSerializer):
    """Serializer for datavars types"""

    def elementByType(self, typeobj):
        """Get element by variable type, given for table or not"""
        elementMap = {'table': 'table',
                      "string": "input",
                      "bool": "check",
                      "boolauto": "check_tristate",
                      "choice": "combo",
                      "choiceedit": "comboEdit",
                      "choiceedit-list": "multichoice_add",
                      "choice-list": "multichoice",
                      "bool-list": "multichoice",
                      "list": "multichoice_add",
                      "password": "password",
                      "onepassword": "password",
                      "need-onepassword": "password",
                      "need-password": "password"}
        return elementMap.get(typeobj, 'input')

    def arrayString(self, value):
        if type(value) in (list, tuple):
            return [""] + map(lambda x: (",".join(x)
                                         if type(x) in (list, tuple)
                                         else x),
                              value)
        else:
            return value

    def arrayArrayString(self, value):
        if type(value) in (list, tuple):
            return map(self.arrayString, value)
        else:
            return value

    def getChoice(self, var_obj):
        if ("choice" in var_obj.type or "file" in var_obj.type or
                    var_obj.element == "radio"):
            choice, comments = var_obj.parent.ChoiceAndComments(var_obj.name)
            return self.arrayString(choice), self.arrayString(comments)
        else:
            return None, None


class ChoiceValue(DataVarsSerializer):
    typefield = String
    values = Array(String)
    comments = Array(String)
    onChanged = Array(String)

    def __init__(self, dv=None, varObj=None, readOnly=False, **kwargs):
        if dv:
            super(ChoiceValue, self).__init__()
            if not readOnly:
                self.values, self.comments = self.getChoice(varObj)
            elif isinstance(varObj, SourceReadonlyVariable):
                items = varObj.getMapHumanReadable().items()
                if items:
                    self.values, self.comments = zip(*items)
                else:
                    self.values, self.comments = [], []
                self.values = self.arrayString(self.values)
                self.comments = self.arrayString(self.comments)
            if varObj.mode == 'w':
                self.typefield = (varObj.element or
                                  self.elementByType(varObj.type))
            else:
                if readOnly:
                    self.typefield = "readonly"
                else:
                    self.typefield = "text"
        else:
            super(ChoiceValue, self).__init__(**kwargs)

    def elementByType(self, typeobj):
        """Get element by variable type, given for table or not"""
        if "-list" in typeobj:
            typeobj = typeobj.replace("-list", "", 1)
        elif "list" in typeobj:
            typeobj = typeobj.replace("list", "", 1)
        return DataVarsSerializer.elementByType(self,
                                                typeobj)


class Table(DataVarsSerializer):
    head = Array(String)
    fields = Array(String)  # name fields in params object
    body = Array(Array(String))
    values = Array(ChoiceValue)
    onClick = String
    addAction = String
    # шаг на который нужно прейти при выполнении onClick
    step = String
    # количество записей в таблице
    records = String

    def __init__(self, dv=None, briefmode=None,
                 varObj=None, head=None, body=None, values=None,
                 fields=None, onClick=None, addAction=None, step=None,
                 records=None):
        super(Table, self).__init__()
        if dv:
            self.head = []
            self.body = []
            self.values = []
            # get information about all table columns
            self.writable = True
            for i, col in enumerate(varObj.source):
                # get columns variable obj
                varSource = dv.getInfo(col)
                # invalidate columns vars for uncompatible table
                if varSource.uncompatible():
                    dv.Invalidate(col, onlySet=True)
                # header label
                self.head.append(varSource.label or varSource.name)
                # if column writable then add ChoiceValue info
                if varSource.mode == 'w' or i == 0:
                    self.values.append(ChoiceValue(dv, varSource))
                    if varSource.mode == 'r' and i == 0:
                        self.writable = False
                else:
                    self.values.append(
                        ChoiceValue(dv, varSource, readOnly=True))
            # get table body
            # empty value at start add for fix data transfer
            self.body = self.arrayArrayString(
                dv.Get(varObj.name, humanreadable=True if briefmode else False))
        else:
            self.head = head
            self.fields = fields
            self.body = body
            self.values = values
            self.onClick = onClick
            self.addAction = addAction
            self.step = step or "0"
            self.records = records or "0"


class Option(DataVarsSerializer):
    shortopt = String
    longopt = String
    metavalue = String
    syntax = String
    help = String

    def __init__(self, optlist, metaval, helpval, syntax=""):
        super(Option, self).__init__()
        self.help = helpval
        self.metavalue = metaval
        self.syntax = syntax
        for val in optlist:
            if val.startswith('--'):
                self.longopt = val
            else:
                self.shortopt = val


class Field(DataVarsSerializer):
    name = String  # varname from Datavars
    label = String  # label for GUI
    type = String  # data type of variable
    opt = Option  # opt for cmdline
    help = String  # help for cmdline (GUI?)
    element = String  # type element
    guitype = String # addon guitype
    choice = Array(String)  # value (combobox,comboedit)
    listvalue = Array(String)  # current listvalue
    default = Boolean  # default value or False
    value = String  # current value
    tablevalue = Table  # current table value
    uncompatible = String  # message for uncompatibility variable
    comments = Array(String)  # comments for choice

    def __init__(self, dv=None, varObj=None, expert=False, briefmode=False,
                 inbrief=False, groupVars=(), onlyhelp=False, **kwargs):
        """
        dv - datavars, varObj - current variable, expert - expert variable flag,
        briefmode - view request for brief, inbrief - variable palced in brief,
        """
        if dv:
            super(Field, self).__init__()
            self.name = varObj.name
            self.type = varObj.type
            if varObj.opt:
                self.opt = Option(varObj.opt, varObj.metavalue, varObj.help,
                                  varObj.syntax)
            self.help = varObj.help
            self.element = varObj.element or self.elementByType(self.type)
            self.label = str(varObj.label or varObj.name)
            self.guitype = varObj.guitype
            if (not onlyhelp or varObj.syntax or self.type == "table" or
                        self.type == "bool"):
                if inbrief:
                    self.uncompatible = ""
                else:
                    self.uncompatible = dv.Uncompatible(varObj.name)
                if self.uncompatible:
                    # блок используется для отмены пометки о несовместимости
                    # если несовместимая переменная, и та из-за которой
                    # она является несовместимой находятся  на одной и той же
                    # странице (например доступность MBR зависит от опции UEFI,
                    # они находятся на одной и той же странице)
                    for var in varObj.reqUncompat:
                        if not briefmode and var in groupVars:
                            if not dv.Uncompatible(var.name):
                                self.uncompatible = ""
                                break
                    else:
                        dv.Invalidate(varObj.name, onlySet=True)
                if expert:
                    self.default = not varObj.wasSet
                self.choice, self.comments = self.getChoice(varObj)
                if self.type == "table":
                    self.tablevalue = Table(dv=dv, briefmode=briefmode,
                                            varObj=varObj)
                    if self.tablevalue.writable:
                        self.type = "writable"
                else:
                    full_varname = "%s.%s" % (varObj.section, varObj.name)
                    value = dv.Get(full_varname)
                    if type(value) in (list, tuple):
                        if briefmode and "choice" not in self.type:
                            self.value = dv.Get(full_varname,
                                                humanreadable=True)
                        self.listvalue = self.arrayString(value)
                    else:
                        if briefmode:  # and not "choice" in self.type:
                            self.value = dv.Get(full_varname,
                                                humanreadable=True)
                        else:
                            self.value = value
                            # if self.value:
                            #    self.default = self.value
        else:
            super(Field, self).__init__(**kwargs)


class GroupField(DataVarsSerializer):
    name = String
    fields = Array(Field)
    prevlabel = String
    nextlabel = String
    last = Boolean

    def __init__(self, name="", fields=list(), prevlabel="",
                 nextlabel="", last=False, dv=None, info=None,
                 expert=False, brief=False, onlyhelp=False):
        super(GroupField, self).__init__()
        self.last = last
        if dv:
            self.name = info['name']
            if not onlyhelp:
                self.nextlabel = info['next_label']
                self.prevlabel = _("Previous")
            else:
                self.nextlabel = None
                self.prevlabel = None
            self.fields = []

            # get all variables with deps which using in this group
            groupDepVars = []
            from itertools import chain

            for varname in chain(info['normal'], info['expert']):
                for var in dv.getRequired(varname):
                    if var not in groupDepVars:
                        groupDepVars.append(var)

            if brief:
                uniqBrief = list(info['brief']) + [x for x in
                                                   info['brief_force']
                                                   if x not in info['brief']]
                for varname in uniqBrief:
                    varObj = dv.getInfo(varname)
                    inbrief = varname in info['brief_force']
                    self.fields.append(Field(dv=dv, inbrief=inbrief,
                                             briefmode=brief,
                                             varObj=varObj,
                                             groupVars=groupDepVars,
                                             onlyhelp=onlyhelp))
            else:
                uniqBrief = []
            for varname in (x for x in info['normal'] if x not in uniqBrief):
                inbrief = brief and varname in info['brief_force']
                if brief and 'hide' in info and varname in info['hide']:
                    continue
                varObj = dv.getInfo(varname)
                self.fields.append(Field(dv=dv, inbrief=inbrief,
                                         briefmode=brief,
                                         varObj=varObj,
                                         groupVars=groupDepVars,
                                         onlyhelp=onlyhelp))
            if info['expert']:
                for varname in (x for x in info['expert'] if
                                x not in uniqBrief):
                    if expert is True or expert is None and dv.getInfo(
                            varname).wasSet:
                        self.fields.append(Field(name="expert",
                                                 element="expert",
                                                 label=str(info['expert_label']),
                                                 value="open"))
                        for varname_ in info['expert']:
                            inbrief = brief and varname_ in info['brief_force']
                            if (brief and 'hide' in info and
                                        varname_ in info['hide']):
                                continue
                            varObj = dv.getInfo(varname_)
                            self.fields.append(Field(dv=dv, expert=True,
                                                     inbrief=inbrief,
                                                     briefmode=brief,
                                                     varObj=varObj,
                                                     groupVars=groupDepVars,
                                                     onlyhelp=onlyhelp))
                        break
                else:
                    if expert is False:
                        for varname in info['expert']:
                            vn = varname.rpartition('.')[2]
                            dv.Invalidate(vn, True)
                    if not onlyhelp:
                        self.fields.append(Field(name="expert",
                                                 element="expert",
                                                 label=str(info['expert_label']),
                                                 value="close",
                                                 onlyhelp=onlyhelp))
        else:
            self.name = name
            self.fields = fields
            self.nextlabel = nextlabel


class ViewInfo(DataVarsSerializer):
    groups = Array(GroupField)
    has_brief = Boolean

    def __init__(self, datavars=None, step=None, expert=None, allsteps=False,
                 brief=None, brief_label=None, has_brief=False, groups=list(),
                 viewparams=None):
        super(ViewInfo, self).__init__()
        onlyhelp = False
        if viewparams:
            # for compatible specifing step by param
            step = viewparams.step if step is None else step
            expert = viewparams.expert if expert is None else expert
            brief = viewparams.brief if brief is None else brief
            onlyhelp = viewparams.onlyhelp
        self.has_brief = has_brief

        if datavars:
            self.groups = []
            varGroups = datavars.getGroups()
            lastGroup = len(varGroups) - 1
            # interate all vars group
            if step in (0, -1, None) or allsteps:
                briefData = datavars.getBrief()
                self.groups.append(self.stepGroup(varGroups, brief_label,
                                                  help_value=briefData.get(
                                                      "help", None),
                                                  next_value=briefData.get(
                                                      "next", None),
                                                  image_value=briefData.get(
                                                      "image", "")))
            for i, groupInfo in enumerate(varGroups):
                if step in (None, -1) or step == i:
                    self.groups.append(GroupField(dv=datavars, info=groupInfo,
                                                  last=(lastGroup == i),
                                                  expert=expert,
                                                  brief=brief,
                                                  onlyhelp=onlyhelp))
                    if groupInfo['custom_buttons']:
                        for but in groupInfo['custom_buttons']:
                            if len(but) > 4:
                                listval = but[4]
                            else:
                                listval = None
                            if len(but) > 5:
                                f = but[5]
                                if f is None or callable(f) and f(datavars.Get):
                                    enable = True
                                else:
                                    enable = False
                            else:
                                enable = True
                            self.groups[-1].fields.append(Field(
                                name=but[0], label=but[1], value=but[2],
                                element=but[3], listvalue=listval,
                                guitype=None if enable else "readonly"))

        else:
            self.groups = groups

    def stepGroup(self, groupInfo, brief_label, help_value=None,
                  next_value=None, image_value=""):
        """Step group"""
        return GroupField(fields=[
            Field(name="Steps",
                  element="table",
                  label=brief_label,
                  type='steps',
                  help=help_value,
                  value=next_value,
                  tablevalue=Table(
                      head=[i['name'] for i in groupInfo],
                      fields=[i.get('image', '')
                              for i in groupInfo] + [image_value],
                      body=[self.arrayString(
                          list(i['normal']) + [""] + list(i['expert']))
                            for i in groupInfo]))])

        # element = ['table', 'radio', 'combo', 'comboEdit', 'multichoice', \
        # 'multichoice_add', 'check', 'check_tristate', 'expert', 'input']


class ViewParams(ClassSerializer):
    """
    Struct for _view methods
    """
    step = Integer  # number of group variables
    expert = Boolean  # request expert variables
    brief = Boolean  # request brief variables
    onlyhelp = Boolean  # request params for only help
    help_set = Boolean  # set cl_help_set to on
    conargs = Array(String)  # set cl_console_args
    dispatch_usenew = Boolean  # set cl_dispatch_conf to usenew
    clienttype = String  # type of client "gui","console"


#########
# MESSAGE
#########
class ReturnedMessage(ClassSerializer):
    type = String
    field = String
    message = String
    expert = Boolean
    field_obj = Field

    def __init__(self, type=None, field=None, message=None,
                 expert=False, field_obj=None):
        super(ReturnedMessage, self).__init__()
        self.type = type
        self.field = field
        self.message = message
        self.expert = expert
        if field_obj:
            self.field_obj = Field(dv=field_obj.parent, varObj=field_obj)
        else:
            self.field_obj = None


class Message(ClassSerializer):
    type = String
    message = String
    id = Integer
    result = Boolean
    onlyShow = String
    default = String

    def __init__(self, type='normal', message=None, id=None,
                 result=None, onlyShow=None, default=None):
        super(Message, self).__init__()
        self.type = type
        self.message = message
        self.id = id
        self.result = result
        self.onlyShow = onlyShow
        self.default = default


class ReturnProgress(ClassSerializer):
    percent = Integer
    short_message = String
    long_message = String
    control = Integer

    def __init__(self, percent=0, short_message=None, long_message=None,
                 control=None):
        super(ReturnProgress, self).__init__()
        self.percent = percent
        self.short_message = short_message
        self.long_message = long_message
        self.control = control


class Frame(ClassSerializer):
    values = Array(Message)


# get and send client messages
class CoreWsdl(CoreServiceInterface):
    perm_denied = [Message(type='error', message='403 Forbidden')]

    def callAction(self, sid, info, logicClass=None,
                   method_name=None, actionClass=None,
                   callbackRefresh=lambda sid, dv: True,
                   invalidators=None, depend_methods=()):
        """
        Общий алгоритм вызова действия
        """
        if not logicClass:
            logicClass = {}
        dv = self.get_cache(sid, method_name, "vars")
        try:
            if not dv:
                dv = getattr(self, "%s_vars" % method_name)()
            else:
                callbackRefresh(sid, dv)
                dv.processRefresh()
            checkonly = False
            checkall = False
            if info and hasattr(info, "CheckOnly"):
                checkonly = info.CheckOnly
            if info and not hasattr(info, "CheckOnly"):
                checkall = True
            elif info and hasattr(info, "CheckAll"):
                checkall = info.CheckAll
            errors = map(lambda x: ReturnedMessage(**x),
                         dv.checkGroups(info,
                                        allvars=checkall or not checkonly,
                                        invalidators=invalidators))
            # if dv.Get('cl_env_debug_set') == 'on':
            #    dv.printGroup(info)
            if errors:
                return errors
            if checkonly:
                returnmess = ReturnedMessage(type='', message=None)
                return [returnmess]
            if not actionClass:
                return []
            objs = {}
            from calculate.core.server.func import CommonLink

            if isinstance(logicClass, dict):
                for k, v in logicClass.items():
                    objs[k] = type("Logic", (CommonLink, v, object), {})()
            install_meth = type("CommonCore", (self.Common,
                                               actionClass, object), {})
            pid = self.startprocess(sid, target=install_meth,
                                    method="run",
                                    method_name=method_name,
                                    args_proc=(objs, dv,))
            returnmess = ReturnedMessage(type='pid', message=pid)
            returnmess.type = "pid"
            returnmess.message = pid
            self.clear_cache(sid, method_name)
            cache_cleared = [method_name]
            clear_list = list(LoadedMethods.methodDepends[method_name])
            while clear_list:
                method_clear_name = clear_list.pop()
                if method_clear_name not in cache_cleared:
                    clear_list.extend(
                        LoadedMethods.methodDepends[method_clear_name])
                    self.clear_cache_method(method_clear_name)
                    cache_cleared.append(method_clear_name)
            dv = None
            return [returnmess]
        finally:
            if dv:
                # print "Set cache", dv.Get('ur_unix_group_name_exists')
                self.set_cache(sid, method_name, "vars", dv, smart=False)

    def fixInstallLocalization(self, sid, dv):
        """
        Метод смены локализации интерфейса на лету (во время выбора
        параметров метода)
        """
        if "--start" not in sys.argv:
            return False
        import threading

        curThread = threading.currentThread()
        curThread.lang = dv.Get('install.os_install_locale_lang')
        currentLang = self.get_cache(sid, "install", "lang")
        if currentLang != curThread.lang:
            dv.clearGroups()
            self.install_vars(dv)
            dv.reinit()
            return True
        else:
            return False

    # verification of compliance certificate and process (pid)
    def check_cert_pid(self, sid, pid):
        import threading

        curThread = threading.currentThread()
        cert = curThread.client_cert

        from cert_cmd import find_cert_id

        cert_id = find_cert_id(cert, self.data_path, self.certbase)
        cert_id = int(cert_id)
        if cert_id == 0:
            return 0

        # session file
        if not os.path.exists(self.sids):
            os.system('mkdir %s' % self.sids)

        check = 0
        try:
            fd = open(self.sids_file, 'r')
        except IOError:
            return 0
        while 1:
            try:
                # read all on one record
                list_sid = pickle.load(fd)
            except (EOFError, KeyError, IOError):
                break
            # find session id in sids file
            if cert_id == int(list_sid[1]):
                if sid == int(list_sid[0]):
                    check = 1

        if check == 0:
            return 0
        fd = open(self.sids_pids, 'r')
        while 1:
            try:
                # read out on 1 record
                list_pid = pickle.load(fd)
            except (EOFError, KeyError, IOError):
                break
            if sid == int(list_pid[0]):
                if pid == int(list_pid[1]):
                    fd.close()
                    return 1
        fd.close()
        return 0

    # send to client all new message
    def process_messages(self, pid, client_type):
        result = []
        while len(self.glob_frame_list[pid]) > \
                self.glob_process_dict[pid]['counter']:
            item = self.glob_process_dict[pid]['counter']
            only_show = self.glob_frame_list[pid][item].onlyShow
            if (not client_type or
                    not only_show or only_show == client_type):
                result.append(self.glob_frame_list[pid][item])
            self.glob_process_dict[pid]['counter'] += 1
        return result

    # send to client new message from frame
    def client_get_frame(self, sid, pid, client_type):
        if self.check_cert_pid(sid, pid):
            return self.process_messages(pid, client_type)
        return self.perm_denied

    # send to client new message from frame
    def client_get_entire_frame(self, sid, pid):
        if self.check_cert_pid(sid, pid):
            try:
                results = self.glob_frame_list[pid]
            except (AttributeError, KeyError, IndexError):
                return self.perm_denied
            len_glob_frame_list = len(self.glob_frame_list[pid])
            self.glob_process_dict[pid]['counter'] = len_glob_frame_list
            return results
        return self.perm_denied

    def client_get_table(self, sid, pid, id):
        if self.check_cert_pid(sid, pid):
            return self.glob_table_dict[pid][id]
        return self.perm_denied

    def client_get_progress(self, sid, pid, id):
        if self.check_cert_pid(sid, pid):
            return self.glob_progress_dict[pid][id]
        return ReturnProgress(0, control=403)

    # get message from client
    def client_send_message(self, sid, pid, text):
        if self.check_cert_pid(sid, pid):
            self.glob_process_dict[pid]['answer'] = text
            return Message(type='normal', message="")
        return self.perm_denied

    @rpc(Integer, Integer, String, _returns=Array(Message))
    # @Dec.check_permissions()
    def get_frame(self, sid, pid, client_type):
        return self.client_get_frame(sid, pid, client_type)

    @rpc(Integer, Integer, _returns=Array(Message))
    def get_entire_frame(self, sid, pid):
        return self.client_get_entire_frame(sid, pid)

    @rpc(Integer, Integer, Integer, _returns=ReturnProgress)
    # @Dec.check_permissions()
    def get_progress(self, sid, pid, id):
        return self.client_get_progress(sid, pid, id)

    @rpc(Integer, Integer, Integer, _returns=Table)
    # @Dec.check_permissions()
    def get_table(self, sid, pid, id):
        return self.client_get_table(sid, pid, id)

    # get messages, transferred from client
    @rpc(Integer, Integer, String, _returns=Message)
    # @Dec.check_permissions()
    def send_message(self, sid, pid, text):
        return self.client_send_message(sid, pid, text)


class WsdlAdapter(object):
    adapted_class = None

    def __init__(self, source):
        self.source = source

    @classmethod
    def from_detect(cls, source):
        if isinstance(source, (cls.adapted_class, WsdlAdapter)):
            return source
        else:
            return cls(source)

    def __getattr__(self, item):
        return getattr(self.source, item)

    @staticmethod
    def Array(field, field_type):
        def wrapper(self):
            if getattr(self.source, field):
                return [field_type(x)
                        for x in getattr(getattr(self.source, field),
                                         field_type.__name__[:-7])]
            else:
                return []

        return property(wrapper)

    @staticmethod
    def StringArray(field):
        def wrapper(self):
            source_field = getattr(self.source, field)
            return source_field.string if source_field else []

        return property(wrapper)


class ChoiceValueAdapter(WsdlAdapter):
    adapted_class = ChoiceValue

    values = WsdlAdapter.StringArray("values")

    comments = WsdlAdapter.StringArray("comments")

    onChanged = WsdlAdapter.StringArray("onChanged")


class TableAdapter(WsdlAdapter):
    adapted_class = Table

    fields = WsdlAdapter.StringArray("fields")
    head = WsdlAdapter.StringArray("head")

    @classmethod
    def get_matrix(cls, value):
        if hasattr(value, 'stringArray'):
            return [row.string
                    for row in value.stringArray
                    if hasattr(row, "string")]
        elif isinstance(value, list):
            return value
        return []

    @property
    def body(self):
        return self.get_matrix(self.source.body)

    values = WsdlAdapter.Array("values", ChoiceValueAdapter)


class FieldAdapter(WsdlAdapter):
    adapted_class = Field

    choice = WsdlAdapter.StringArray("choice")
    listvalue = WsdlAdapter.StringArray("listvalue")
    comments = WsdlAdapter.StringArray("comments")

    @property
    def tablevalue(self):
        return TableAdapter(self.source.tablevalue)


class GroupFieldAdapter(WsdlAdapter):
    adapted_class = GroupField

    fields = WsdlAdapter.Array("fields", FieldAdapter)


class ViewInfoAdapter(WsdlAdapter):
    adapted_class = ViewInfo

    groups = WsdlAdapter.Array("groups", GroupFieldAdapter)


class ArrayReturnedMessage(WsdlAdapter):
    @classmethod
    def from_detect(cls, source):
        if isinstance(source, (list, tuple)):
            return source
        else:
            return source.ReturnedMessage
