# coding=utf-8
#
# Copyright © 2016 Intel Corporation
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

"""Generate fp64 vertex shader input tests."""

import abc
import argparse
import itertools
import os
import types

from templates import template_dir
from modules import utils
from modules import types as glsltypes

TEMPLATES = template_dir(os.path.basename(os.path.splitext(__file__)[0]))

# Hard limit so we don't generate useless tests that cannot be run in any existing HW.
MAX_VERTEX_ATTRIBS = 32

# pylint: disable=bad-whitespace,line-too-long
DOUBLE_INFS            = ['0xfff0000000000000',  # -inf
                          '0x7ff0000000000000']  # +inf

DOUBLE_NEG_ZERO        = ['0x8000000000000000']  # Negative underflow (-0.0)

DOUBLE_POS_ZERO        = ['0x0000000000000000']  # Positive underflow (+0.0)

# Double values causing an underflow to zero in any other type
DOUBLE_DENORMAL_VALUES = ['0x800fffffffffffff',  # Negative maximum denormalized -- Denormalized may be flushed to 0
                          '0x8000000000000001',  # Negative minimum denormalized -- Denormalized may be flushed to 0
                          '0x0000000000000001',  # Positive minimum denormalized -- Denormalized may be flushed to 0
                          '0x000fffffffffffff']  # Positive maximum denormalized -- Denormalized may be flushed to 0

DOUBLE_NORMAL_VALUES   = ['0xffefffffffffffff',  # Negative maximum normalized
                          '0xcb1e35ed24eb6496',  # -7.23401345e+53
                          '0xc8b1381a93a87849',  # -1.5e+42
                          '0xc7efffffefffffff',  # Negative maximum float normalized
                          '0xc170000000000000',  # -16777216.0
                          '0xc014000000000000',  # -5.0
                          '0xbfff25ce60000000',  # -1.9467300176620483
                          '0x8010000000000000',  # Negative minimum normalized
                          '0x0010000000000000',  # Positive minimum normalized
                          '0x3fff25ce60000000',  # +1.9467300176620483
                          '0x4014000000000000',  # +5.0
                          '0x4170000000000000',  # +16777216.0
                          '0x47efffffefffffff',  # Positive maximum float normalized
                          '0x48b1381a93a87849',  # +1.5e+42
                          '0x4b1e35ed24eb6496',  # +7.23401345e+53
                          '0x7fefffffffffffff']  # Positive maximum normalized

FLOAT_POS_ZERO         = ['0x00000000']  #  0.0

FLOAT_NORMAL_VALUES    = ['0xc21620c5',  # -3.7532
                          '0x75bc289b',  #  4.7703e32
                          '0x54c1c081',  #  6.6572e12
                          '0x878218f8',  # -1.9575e-34
                          '0x7e0857ed',  #  4.5307886e37
                          '0x2bb561bf',  #  1.2887954e-12
                          '0xff7fffff',  # Negative maximum normalized
                          '0xcb800000',  # -16777216.0
                          '0xc0a00000',  # -5.0
                          '0xbff92e73',  # -1.9467300
                          '0x80800000',  # Negative minimum normalized
                          '0x00800000',  # Positive minimum normalized
                          '0x3ff92e73',  #  1.9467300
                          '0x40a00000',  #  5.0
                          '0x4b800000',  #  16777216.0
                          '0x7f7fffff']  # Positive maximum normalized

UBYTE_VALUES           = ['0',    # Minimum
                          '127',  # Signed byte low frontier
                          '128',  # Signed byte up frontier
                          '255',  # Maximum
                          '1',
                          '5',
                          '14',
                          '23',
                          '58',
                          '91',
                          '113',
                          '135',
                          '179',
                          '185',
                          '205',
                          '207',
                          '212']

BYTE_VALUES            = ['-128',  # Minimum
                          '-5',
                          '-1',
                          '0',
                          '1',
                          '5',
                          '127',   # Maximum
                          '-125',
                          '-120',
                          '-117',
                          '-69',
                          '-24',
                          '-20',
                          '21',
                          '89',
                          '106',
                          '119']

USHORT_VALUES          = ['0',      # Minimum
                          '32767',  # Signed short low frontier
                          '32768',  # Signed short up frontier
                          '65535',  # Maximum
                          '1',
                          '5',
                          '12610',
                          '17110',
                          '19962',
                          '23589',
                          '37265',
                          '41792',
                          '45699',
                          '47934',
                          '55916',
                          '56412',
                          '65142']

SHORT_VALUES           = ['-32768',  # Minimum
                          '-5',
                          '-1',
                          '0',
                          '1',
                          '5',
                          '32767',   # Maximum
                          '-16255',
                          '-12480',
                          '-12360',
                          '1706',
                          '5386',
                          '7315',
                          '18137',
                          '25871',
                          '26255',
                          '26472']

UINT_VALUES            = ['0',           # Minimum
                          '2147483647',  # Signed int low frontier
                          '2147483648',  # Signed int up frontier
                          '4294967295',  # Maximum
                          '1',
                          '5',
                          '1073294963',
                          '1084227584',
                          '1266679808',
                          '1421983873',
                          '2114476013',
                          '2273450232',
                          '3220778611',
                          '3231711232',
                          '3256230085',
                          '3414163456',
                          '4294967294']

INT_VALUES             = ['-2147483648',  # Minimum
                          '-5',
                          '-1',
                          '0',
                          '1',
                          '5',
                          '2147483647',   # Maximum
                          '-1038737211',
                          '-1063256064',
                          '-1074188685',
                          '-880803840',
                          '1073294963',
                          '1084227584',
                          '1266679808',
                          '1421983873',
                          '1975265435',
                          '2114476013']

GL_TYPES_VALUES        = {'double': DOUBLE_NORMAL_VALUES + DOUBLE_POS_ZERO,
                          'float': FLOAT_NORMAL_VALUES + FLOAT_POS_ZERO,
                          'ubyte': UBYTE_VALUES,
                          'byte': BYTE_VALUES,
                          'ushort': USHORT_VALUES,
                          'short': SHORT_VALUES,
                          'uint': UINT_VALUES,
                          'int': INT_VALUES}

GLSL_DSCALAR_TYPES     = [glsltypes.DOUBLE]

GLSL_DVEC_TYPES        = [glsltypes.DVEC2, glsltypes.DVEC3, glsltypes.DVEC4]

GLSL_DMAT_TYPES        = [glsltypes.DMAT2, glsltypes.DMAT2X3, glsltypes.DMAT2X4,
                          glsltypes.DMAT3X2, glsltypes.DMAT3, glsltypes.DMAT3X4,
                          glsltypes.DMAT4X2, glsltypes.DMAT4X3, glsltypes.DMAT4]

GLSL_FSCALAR_TYPES     = [glsltypes.FLOAT]

GLSL_FVEC_TYPES        = [glsltypes.VEC2, glsltypes.VEC3, glsltypes.VEC4]

GLSL_FMAT_TYPES        = [glsltypes.MAT2, glsltypes.MAT2X3, glsltypes.MAT2X4,
                          glsltypes.MAT3X2, glsltypes.MAT3, glsltypes.MAT3X4,
                          glsltypes.MAT4X2, glsltypes.MAT4X3, glsltypes.MAT4]

GLSL_ISCALAR_TYPES     = [glsltypes.INT]

GLSL_IVEC_TYPES        = [glsltypes.IVEC2, glsltypes.IVEC3, glsltypes.IVEC4]

GLSL_USCALAR_TYPES     = [glsltypes.UINT]

GLSL_UVEC_TYPES        = [glsltypes.UVEC2, glsltypes.UVEC3, glsltypes.UVEC4]


# pylint: enable=bad-whitespace,line-too-long


class TestTuple(object):
    """A float64 derived and other type derived tuple to generate the
       needed conversion tests.
    """
    @staticmethod
    def get_dir_name(ver):
        """Returns the directory name to save tests given a GLSL version."""

        assert isinstance(ver, str)
        if ver.startswith('GL_'):
            feature_dir = ver[3:].lower()
        else:
            feature_dir = 'glsl-{}.{}'.format(ver[0], ver[1:])

        return os.path.join('spec', feature_dir, 'execution',
                            'vs_in')

    def __init__(self, ver, names_only):
        assert isinstance(ver, str)
        assert isinstance(names_only, bool)

        self._ver = ver
        self._names_only = names_only

    @abc.abstractmethod
    def generate(self):
        """Generate the GLSL parser tests."""


class RegularTestTuple(TestTuple):
    """Derived class for conversion tests using regular values within the
       edges of the used types.
    """

    @staticmethod
    def create_in_types_array(*types_arrays):
        """Creates vertex input combinations."""

        for product_item in itertools.product(*types_arrays):
            yield product_item

    @staticmethod
    def create_tests(glsl_vers, in_types_array, gl_types,
                     position_orders, arrays_array, names_only):
        """Creates combinations for flat qualifier tests."""

        assert isinstance(glsl_vers, list)
        assert isinstance(in_types_array, types.GeneratorType)
        assert isinstance(gl_types, list)
        assert isinstance(position_orders, list)
        assert isinstance(arrays_array, list)
        assert isinstance(names_only, bool)

        if not names_only:
            for ver in glsl_vers:
                utils.safe_makedirs(TestTuple.get_dir_name(ver))

        for in_types, position_order, arrays, ver in itertools.product(
                in_types_array,
                position_orders,
                arrays_array,
                glsl_vers):
            num_vs_in = 1  # We use an additional vec3 piglit_vertex input
            for idx, in_type in enumerate(in_types):
                num_vs_in += (in_type.columns or 1) * arrays[idx] * \
                    (2 if in_type.type.name == 'double' and in_type.rows in [3, 4] else 1)
                # dvec* and dmat* didn't appear in GLSL until 4.20
                if (in_type.type.name == 'double' and not in_type.scalar) and ver == '410':
                    ver = '420'
            # Skip the test if it needs too many inputs
            if num_vs_in > MAX_VERTEX_ATTRIBS:
                continue

            yield ver, in_types, gl_types, position_order, arrays, num_vs_in, names_only

    @staticmethod
    def all_tests(names_only):
        """Creates all the combinations for flat qualifier tests."""

        assert isinstance(names_only, bool)

        # We need additional directories for GLSL 420
        if not names_only:
            utils.safe_makedirs(TestTuple.get_dir_name('420'))
        for test_args in RegularTestTuple.create_tests(
                ['GL_ARB_vertex_attrib_64bit', '410'],
                RegularTestTuple.create_in_types_array(
                    itertools.chain(GLSL_USCALAR_TYPES, GLSL_UVEC_TYPES),
                    itertools.chain(GLSL_ISCALAR_TYPES, GLSL_IVEC_TYPES),
                    itertools.chain(GLSL_DSCALAR_TYPES, GLSL_DVEC_TYPES, GLSL_DMAT_TYPES)),
                ['ubyte', 'short', 'double'],
                [1, 2, 3, 4],
                [[1, 1, 1]],
                names_only):
            yield RegularTestTuple(*test_args)
        for test_args in RegularTestTuple.create_tests(
                ['GL_ARB_vertex_attrib_64bit', '410'],
                RegularTestTuple.create_in_types_array(
                    itertools.chain(GLSL_ISCALAR_TYPES, GLSL_IVEC_TYPES),
                    itertools.chain(GLSL_DSCALAR_TYPES, GLSL_DVEC_TYPES, GLSL_DMAT_TYPES)),
                ['byte', 'double'],
                [1, 2, 3],
                [[1, 1]],
                names_only):
            yield RegularTestTuple(*test_args)
        for test_args in RegularTestTuple.create_tests(
                ['GL_ARB_vertex_attrib_64bit', '410'],
                RegularTestTuple.create_in_types_array(
                    itertools.chain(GLSL_USCALAR_TYPES, GLSL_UVEC_TYPES),
                    itertools.chain(GLSL_DSCALAR_TYPES, GLSL_DVEC_TYPES, GLSL_DMAT_TYPES)),
                ['ushort', 'double'],
                [1, 2, 3],
                [[1, 1]],
                names_only):
            yield RegularTestTuple(*test_args)
        for test_args in RegularTestTuple.create_tests(
                ['GL_ARB_vertex_attrib_64bit', '410'],
                RegularTestTuple.create_in_types_array(
                    itertools.chain(GLSL_DSCALAR_TYPES, GLSL_DVEC_TYPES, GLSL_DMAT_TYPES),
                    itertools.chain(GLSL_FSCALAR_TYPES, GLSL_FVEC_TYPES, GLSL_FMAT_TYPES)),
                ['double', 'float'],
                [1, 2, 3],
                [[1, 1], [1, 3], [5, 1], [5, 3]],
                names_only):
            yield RegularTestTuple(*test_args)
        for test_args in RegularTestTuple.create_tests(
                ['GL_ARB_vertex_attrib_64bit', '410'],
                RegularTestTuple.create_in_types_array(
                    itertools.chain(GLSL_DSCALAR_TYPES, GLSL_DVEC_TYPES, GLSL_DMAT_TYPES),
                    itertools.chain(GLSL_ISCALAR_TYPES, GLSL_IVEC_TYPES)),
                ['double', 'int'],
                [1, 2, 3],
                [[1, 1], [1, 3], [5, 1], [5, 3]],
                names_only):
            yield RegularTestTuple(*test_args)
        for test_args in RegularTestTuple.create_tests(
                ['GL_ARB_vertex_attrib_64bit', '410'],
                RegularTestTuple.create_in_types_array(
                    itertools.chain(GLSL_DSCALAR_TYPES, GLSL_DVEC_TYPES, GLSL_DMAT_TYPES),
                    itertools.chain(GLSL_USCALAR_TYPES, GLSL_UVEC_TYPES)),
                ['double', 'uint'],
                [1, 2, 3],
                [[1, 1], [1, 3], [5, 1], [5, 3]],
                names_only):
            yield RegularTestTuple(*test_args)
        for test_args in RegularTestTuple.create_tests(
                ['GL_ARB_vertex_attrib_64bit', '410'],
                RegularTestTuple.create_in_types_array(
                    itertools.chain(GLSL_FSCALAR_TYPES, GLSL_FVEC_TYPES, GLSL_FMAT_TYPES),
                    itertools.chain(GLSL_DSCALAR_TYPES, GLSL_DVEC_TYPES, GLSL_DMAT_TYPES)),
                ['float', 'double'],
                [1, 2, 3],
                [[1, 1], [1, 2], [3, 1], [3, 2]],
                names_only):
            yield RegularTestTuple(*test_args)
        for test_args in RegularTestTuple.create_tests(
                ['GL_ARB_vertex_attrib_64bit', '410'],
                RegularTestTuple.create_in_types_array(
                    itertools.chain(GLSL_ISCALAR_TYPES, GLSL_IVEC_TYPES),
                    itertools.chain(GLSL_DSCALAR_TYPES, GLSL_DVEC_TYPES, GLSL_DMAT_TYPES)),
                ['int', 'double'],
                [1, 2, 3],
                [[1, 1], [1, 2], [3, 1], [3, 2]],
                names_only):
            yield RegularTestTuple(*test_args)
        for test_args in RegularTestTuple.create_tests(
                ['GL_ARB_vertex_attrib_64bit', '410'],
                RegularTestTuple.create_in_types_array(
                    itertools.chain(GLSL_USCALAR_TYPES, GLSL_UVEC_TYPES),
                    itertools.chain(GLSL_DSCALAR_TYPES, GLSL_DVEC_TYPES, GLSL_DMAT_TYPES)),
                ['uint', 'double'],
                [1, 2, 3],
                [[1, 1], [1, 2], [3, 1], [3, 2]],
                names_only):
            yield RegularTestTuple(*test_args)
        for test_args in RegularTestTuple.create_tests(
                ['GL_ARB_vertex_attrib_64bit', '410'],
                RegularTestTuple.create_in_types_array(
                    itertools.chain(GLSL_DSCALAR_TYPES, GLSL_DVEC_TYPES, GLSL_DMAT_TYPES),
                    itertools.chain(GLSL_DSCALAR_TYPES, GLSL_DVEC_TYPES, GLSL_DMAT_TYPES)),
                ['double', 'double'],
                [1, 2, 3],
                [[1, 1], [1, 2], [3, 1], [3, 2]],
                names_only):
            yield RegularTestTuple(*test_args)
        for test_args in RegularTestTuple.create_tests(
                ['GL_ARB_vertex_attrib_64bit', '410'],
                RegularTestTuple.create_in_types_array(
                    itertools.chain(GLSL_DSCALAR_TYPES, GLSL_DVEC_TYPES, GLSL_DMAT_TYPES)),
                ['double'],
                [1, 2],
                [[1], [5]],
                names_only):
            yield RegularTestTuple(*test_args)

    def __init__(self, ver, in_types, gl_types, position_order, arrays, num_vs_in, names_only):
        assert ver in ('GL_ARB_vertex_attrib_64bit', '410', '420')
        assert isinstance(in_types, tuple)
        assert isinstance(gl_types, list)
        assert len(gl_types) == len(in_types)
        assert isinstance(position_order, int)
        assert (position_order > 0) and (position_order - 1 <= len(in_types))
        assert isinstance(arrays, list)
        assert isinstance(num_vs_in, int) and (num_vs_in <= MAX_VERTEX_ATTRIBS)
        super(RegularTestTuple, self).__init__(ver, names_only)

        self._in_types = in_types
        self._gl_types = gl_types
        self._position_order = position_order
        self._arrays = arrays
        self._num_vs_in = num_vs_in

    def generate(self):
        """Generate GLSL parser tests."""
        filename = os.path.join(TestTuple.get_dir_name(self._ver), 'vs-input')
        for idx, in_type in enumerate(self._in_types):
            if idx == self._position_order - 1:
                filename += '-position'
            filename += '-{}_{}{}'.format(
                self._gl_types[idx], in_type.name, '_array{}'.format(
                    self._arrays[idx]) if self._arrays[idx] - 1 else '')
        if self._position_order > len(self._in_types):
            filename += '-position'
        filename += '.shader_test'

        if not self._names_only:
            with open(filename, 'w') as test_file:
                test_file.write(TEMPLATES.get_template(
                    'regular.shader_test.mako').render_unicode(
                        ver=self._ver,
                        in_types=self._in_types,
                        gl_types=self._gl_types,
                        position_order=self._position_order,
                        arrays=self._arrays,
                        num_vs_in=self._num_vs_in,
                        gl_types_values=GL_TYPES_VALUES))

        print(filename)


class ColumnsTestTuple(TestTuple):
    """Derived class for conversion tests using regular values within the
       edges of the used types.
    """

    @staticmethod
    def all_tests(names_only):
        """Creates all the combinations for flat qualifier tests."""

        assert isinstance(names_only, bool)
        glsl_vers = ['GL_ARB_vertex_attrib_64bit', '420']

        if not names_only:
            for ver in glsl_vers:
                utils.safe_makedirs(TestTuple.get_dir_name(ver))

        for mat in GLSL_DMAT_TYPES:
            for columns in itertools.product(range(2), repeat=mat.columns):
                if (0 not in columns) or (1 not in columns):
                    continue
                for ver in glsl_vers:
                    yield ColumnsTestTuple(ver, mat, columns, names_only)

    def __init__(self, ver, mat, columns, names_only):
        assert ver in ('GL_ARB_vertex_attrib_64bit', '420')
        super(ColumnsTestTuple, self).__init__(ver, names_only)

        self._mat = mat
        self._columns = columns

    def generate(self):
        """Generate GLSL parser tests."""

        filename = os.path.join(TestTuple.get_dir_name(self._ver),
                                'vs-input-columns-{}'.format(self._mat.name))
        for idx, column in enumerate(self._columns):
            if column == 1:
                filename += '-{}'.format(idx)
        filename += '.shader_test'

        if not self._names_only:
            with open(filename, 'w') as test_file:
                test_file.write(TEMPLATES.get_template(
                    'columns.shader_test.mako').render_unicode(
                        ver=self._ver,
                        mat=self._mat,
                        columns=self._columns,
                        dvalues=GL_TYPES_VALUES['double']))

        print(filename)


def main():
    """Main function."""

    parser = argparse.ArgumentParser(
        description="Generate non-flat interpolation qualifier tests with fp64 types")
    parser.add_argument(
        '--names-only',
        dest='names_only',
        action='store_true',
        default=False,
        help="Don't output files, just generate a list of filenames to stdout")
    args = parser.parse_args()

    for test in itertools.chain(RegularTestTuple.all_tests(args.names_only),
                                ColumnsTestTuple.all_tests(args.names_only)):
        test.generate()


if __name__ == '__main__':
    main()
