#############################################################################
##
## Copyright (C) 2023 Riverbank Computing Limited.
## Copyright (C) 2006 Thorsten Marek.
## All right reserved.
##
## This file is part of PyQt.
##
## You may use this file under the terms of the GPL v2 or the revised BSD
## license as follows:
##
## "Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are
## met:
##   * Redistributions of source code must retain the above copyright
##     notice, this list of conditions and the following disclaimer.
##   * Redistributions in binary form must reproduce the above copyright
##     notice, this list of conditions and the following disclaimer in
##     the documentation and/or other materials provided with the
##     distribution.
##   * Neither the name of the Riverbank Computing Limited nor the names
##     of its contributors may be used to endorse or promote products
##     derived from this software without specific prior written
##     permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
##
#############################################################################


import sys
import re

from .as_string import as_string
from .indenter import write_code
from .misc import Literal, moduleMember
from .proxy_metaclass import ProxyMetaclass


i18n_strings = []
i18n_context = ""

def i18n_print(string):
    i18n_strings.append(string)

def i18n_void_func(name):
    def _printer(self, *args):
        i18n_print("%s.%s(%s)" % (self, name, ", ".join(map(as_string, args))))
    return _printer

def i18n_func(name):
    def _printer(self, rname, *args):
        i18n_print("%s = %s.%s(%s)" % (rname, self, name, ", ".join(map(as_string, args))))
        return Literal(rname)

    return _printer

def strict_getattr(module, clsname):
    cls = getattr(module, clsname)
    if issubclass(cls, LiteralProxyClass):
        raise AttributeError(cls)
    else:
        return cls
    

class i18n_string(object):
    def __init__(self, string, disambig):
        self.string = string
        self.disambig = disambig

    def __str__(self):
        if self.disambig is None:
            return '_translate("%s", %s)' % (i18n_context, as_string(self.string))

        return '_translate("%s", %s, %s)' % (i18n_context, as_string(self.string), as_string(self.disambig))


# Classes with this flag will be handled as literal values. If functions are
# called on these classes, the literal value changes.
# Example:
# the code
# >>> QSize(9,10).expandedTo(...)
# will print just that code.
AS_ARGUMENT = 0x02

# Classes with this flag may have members that are signals which themselves
# will have a connect() member.
AS_SIGNAL = 0x01

# ATTENTION: currently, classes can either be literal or normal. If a class
# should need both kinds of behaviour, the code has to be changed.

class ProxyClassMember(object):
    def __init__(self, proxy, function_name, flags):
        self.proxy = proxy
        self.function_name = function_name
        self.flags = flags
        
    def __str__(self):
        return "%s.%s" % (self.proxy, self.function_name)
    
    def __call__(self, *args):
        if self.function_name == 'setProperty':
            str_args = (as_string(args[0]), as_string(args[1]))
        else:
            str_args = map(as_string, args)

        func_call = "%s.%s(%s)" % (self.proxy,
                                   self.function_name,
                                   ", ".join(str_args))
        if self.flags & AS_ARGUMENT:
            self.proxy._uic_name = func_call
            return self.proxy
        else:
            needs_translation = False
            for arg in args:
                if isinstance(arg, i18n_string):
                    needs_translation = True
            if needs_translation:
                i18n_print(func_call)
            else:
                if self.function_name == 'connect':
                    func_call += ' # type: ignore'

                write_code(func_call)                       

    def __getattribute__(self, attribute):
        """ Reimplemented to create a proxy connect() if requested and this
        might be a proxy for a signal.
        """

        try:
            return object.__getattribute__(self, attribute)
        except AttributeError:
            if attribute == 'connect' and self.flags & AS_SIGNAL:
                return ProxyClassMember(self, attribute, 0)

            raise

    def __getitem__(self, idx):
        """ Reimplemented to create a proxy member that should be a signal that
        passes arguments.  We handle signals without arguments before we get
        here and never apply the index notation to them.
        """

        return ProxySignalWithArguments(self.proxy, self.function_name, idx)


class ProxySignalWithArguments(object):
    """ This is a proxy for (what should be) a signal that passes arguments.
    """

    def __init__(self, sender, signal_name, signal_index):
        self._sender = sender
        self._signal_name = signal_name

        # Convert the signal index, which will be a single argument or a tuple
        # of arguments, to quoted strings.
        if isinstance(signal_index, tuple):
            self._signal_index = ','.join(["'%s'" % a for a in signal_index])
        else:
            self._signal_index = "'%s'" % signal_index

    def connect(self, slot):
        write_code("%s.%s[%s].connect(%s) # type: ignore" % (self._sender, self._signal_name, self._signal_index, slot))


class ProxyBase(metaclass=ProxyMetaclass):
    """ A base class for proxies using Python v3 syntax for setting the
    meta-class.
    """


class ProxyClass(ProxyBase):
    flags = 0

    def __init__(self, object_name, ctor_args=None, ctor_kwargs=None,
            is_attribute=False, no_instantiation=True):
        if object_name:
            if is_attribute:
                object_name = 'self.' + object_name

            self._uic_name = object_name
        else:
            self._uic_name = "Unnamed"

        if not no_instantiation:
            args = [] if ctor_args is None else list(map(str, ctor_args))

            if ctor_kwargs is not None:
                for k, v in ctor_kwargs.items():
                    args.append(k + '=' + str(v))

            fun_call = '%s(%s)' % \
                    (moduleMember(self.module, self.__class__.__name__),
                    ', '.join(args))

            if object_name:
                fun_call = '%s = %s' % (object_name, fun_call)

            write_code(fun_call)
    
    def __str__(self):
        return self._uic_name

    def __getattribute__(self, attribute):
        try:
            return object.__getattribute__(self, attribute)
        except AttributeError:
            return ProxyClassMember(self, attribute, self.flags)


class LiteralProxyClass(ProxyClass):
    """LiteralObject(*args) -> new literal class

    a literal class can be used as argument in a function call

    >>> class Foo(LiteralProxyClass): pass
    >>> str(Foo(1,2,3)) == "Foo(1,2,3)"
    """
    flags = AS_ARGUMENT

    def __init__(self, *args):
        self._uic_name = "%s(%s)" % \
                     (moduleMember(self.module, self.__class__.__name__),
                      ", ".join(map(as_string, args)))
        

class ProxyNamespace(ProxyBase):
    pass


# These are all the Qt classes used by pyuic6 in their namespaces. If a class
# is missing, the compiler will fail, normally with an AttributeError.
#
# For adding new classes:
#     - utility classes used as literal values do not need to be listed
#       because they are created on the fly as subclasses of LiteralProxyClass
#     - classes which are *not* QWidgets inherit from ProxyClass and they
#       have to be listed explicitly in the correct namespace. These classes
#       are created via a ProxyQObjectCreator
#     - new QWidget-derived classes have to inherit from qtproxies.QWidget
#       If the widget does not need any special methods, it can be listed
#       in _qwidgets

class QtCore(ProxyNamespace):
    class Qt(ProxyNamespace):
        pass

    ## connectSlotsByName and connect have to be handled as class methods,
    ## otherwise they would be created as LiteralProxyClasses and never be
    ## printed
    class QMetaObject(ProxyClass):
        @classmethod
        def connectSlotsByName(cls, *args):
            ProxyClassMember(cls, "connectSlotsByName", 0)(*args)

    class QObject(ProxyClass):
        flags = AS_SIGNAL

        def metaObject(self):
            class _FakeMetaObject(object):
                def className(*args):
                    return self.__class__.__name__
            return _FakeMetaObject()

        def objectName(self):
            return self._uic_name.split(".")[-1]


class QtGui(ProxyNamespace):
    class QIcon(ProxyClass):
        class fromTheme(ProxyClass): pass

    class QConicalGradient(ProxyClass): pass
    class QLinearGradient(ProxyClass): pass
    class QRadialGradient(ProxyClass): pass
    class QBrush(ProxyClass): pass
    class QPainter(ProxyClass): pass
    class QPalette(ProxyClass): pass
    class QFont(ProxyClass): pass
    class QFontDatabase(ProxyClass): pass
    # QActions inherit from QObject for the meta-object stuff and the hierarchy
    # has to be correct since we have a isinstance(x, QtWidgets.QLayout) call
    # in the UI parser.
    class QAction(QtCore.QObject): pass
    class QActionGroup(QtCore.QObject): pass


# These sub-class QWidget but aren't themselves sub-classed.
_qwidgets = ('QCalendarWidget', 'QDialogButtonBox', 'QDockWidget', 'QGroupBox',
        'QLineEdit', 'QMainWindow', 'QMenuBar', 'QProgressBar', 'QStatusBar',
        'QToolBar', 'QWizardPage')

class QtWidgets(ProxyNamespace):
    class QApplication(QtCore.QObject):
        @staticmethod
        def translate(uiname, text, disambig):
            return i18n_string(text or "", disambig)

    class QSpacerItem(ProxyClass): pass
    class QSizePolicy(ProxyClass): pass
    class QButtonGroup(QtCore.QObject): pass
    class QLayout(QtCore.QObject): pass
    class QGridLayout(QLayout): pass
    class QBoxLayout(QLayout): pass
    class QHBoxLayout(QBoxLayout): pass
    class QVBoxLayout(QBoxLayout): pass
    class QFormLayout(QLayout): pass
    
    class QWidget(QtCore.QObject):
        def font(self):
            return Literal("%s.font()" % self)

        def minimumSizeHint(self):
            return Literal("%s.minimumSizeHint()" % self)

        def sizePolicy(self):
            sp = LiteralProxyClass()
            sp._uic_name = "%s.sizePolicy()" % self
            return sp

    class QDialog(QWidget): pass
    class QColorDialog(QDialog): pass
    class QFileDialog(QDialog): pass
    class QFontDialog(QDialog): pass
    class QInputDialog(QDialog): pass
    class QMessageBox(QDialog): pass
    class QWizard(QDialog): pass

    class QAbstractSlider(QWidget): pass
    class QDial(QAbstractSlider): pass
    class QScrollBar(QAbstractSlider): pass
    class QSlider(QAbstractSlider): pass

    class QMenu(QWidget):
        def menuAction(self):
            return Literal("%s.menuAction()" % self)

    class QTabWidget(QWidget):
        def addTab(self, *args):
            text = args[-1]

            if isinstance(text, i18n_string):
                i18n_print("%s.setTabText(%s.indexOf(%s), %s)" % \
                        (self._uic_name, self._uic_name, args[0], text))
                args = args[:-1] + ("", )

            ProxyClassMember(self, "addTab", 0)(*args)

        def indexOf(self, page):
            return Literal("%s.indexOf(%s)" % (self, page))

    class QComboBox(QWidget): pass
    class QFontComboBox(QComboBox): pass

    class QAbstractSpinBox(QWidget): pass
    class QDoubleSpinBox(QAbstractSpinBox): pass
    class QSpinBox(QAbstractSpinBox): pass

    class QDateTimeEdit(QAbstractSpinBox): pass
    class QDateEdit(QDateTimeEdit): pass
    class QTimeEdit(QDateTimeEdit): pass

    class QFrame(QWidget): pass
    class QLabel(QFrame): pass
    class QLCDNumber(QFrame): pass
    class QSplitter(QFrame): pass
    class QStackedWidget(QFrame): pass

    class QToolBox(QFrame):
        def addItem(self, *args):
            text = args[-1]

            if isinstance(text, i18n_string):
                i18n_print("%s.setItemText(%s.indexOf(%s), %s)" % \
                        (self._uic_name, self._uic_name, args[0], text))
                args = args[:-1] + ("", )

            ProxyClassMember(self, "addItem", 0)(*args)

        def indexOf(self, page):
            return Literal("%s.indexOf(%s)" % (self, page))

        def layout(self):
            return QtWidgets.QLayout('%s.layout()' % self)

    class QAbstractScrollArea(QFrame):
        def viewport(self):
            return QtWidgets.QWidget('%s.viewport()' % self)

    class QGraphicsView(QAbstractScrollArea): pass
    class QMdiArea(QAbstractScrollArea): pass
    class QPlainTextEdit(QAbstractScrollArea): pass
    class QScrollArea(QAbstractScrollArea): pass

    class QTextEdit(QAbstractScrollArea): pass
    class QTextBrowser(QTextEdit): pass

    class QAbstractItemView(QAbstractScrollArea): pass
    class QColumnView(QAbstractItemView): pass
    class QHeaderView(QAbstractItemView): pass
    class QListView(QAbstractItemView): pass

    class QTableView(QAbstractItemView):
        def horizontalHeader(self):
            return QtWidgets.QHeaderView('%s.horizontalHeader()' % self)

        def verticalHeader(self):
            return QtWidgets.QHeaderView('%s.verticalHeader()' % self)

    class QTreeView(QAbstractItemView):
        def header(self):
            return QtWidgets.QHeaderView('%s.header()' % self)

    class QUndoView(QListView): pass

    class QListWidgetItem(ProxyClass): pass

    class QListWidget(QListView):
        setSortingEnabled = i18n_void_func("setSortingEnabled")
        isSortingEnabled = i18n_func("isSortingEnabled")
        item = i18n_func("item")

    class QTableWidgetItem(ProxyClass): pass

    class QTableWidget(QTableView):
        setSortingEnabled = i18n_void_func("setSortingEnabled")
        isSortingEnabled = i18n_func("isSortingEnabled")
        item = i18n_func("item")
        horizontalHeaderItem = i18n_func("horizontalHeaderItem")
        verticalHeaderItem = i18n_func("verticalHeaderItem")

    class QTreeWidgetItem(ProxyClass):
        def child(self, index):
            return QtWidgets.QTreeWidgetItem('%s.child(%i)' % (self, index))

    class QTreeWidget(QTreeView):
        setSortingEnabled = i18n_void_func("setSortingEnabled")
        isSortingEnabled = i18n_func("isSortingEnabled")

        def headerItem(self):
            return QtWidgets.QWidget('%s.headerItem()' % self)

        def topLevelItem(self, index):
            return QtWidgets.QTreeWidgetItem(
                    '%s.topLevelItem(%i)' % (self, index))

    class QAbstractButton(QWidget): pass
    class QCheckBox(QAbstractButton): pass
    class QRadioButton(QAbstractButton): pass
    class QToolButton(QAbstractButton): pass

    class QPushButton(QAbstractButton): pass
    class QCommandLinkButton(QPushButton): pass
    class QKeySequenceEdit(QWidget): pass

    # Add all remaining classes.
    for _class in _qwidgets:
        if _class not in locals():
            locals()[_class] = type(_class, (QWidget, ), {})
