# 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 and int64 types conversion tests."""

import abc
import argparse
import itertools
import os
import struct
import sys

from mako import exceptions
import numpy as np

from templates import template_dir
from modules import utils

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

# pylint: disable=bad-whitespace,line-too-long,bad-continuation
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        = ['0x8010000000000000', # Negative minimum normalized
                               '0x0010000000000000'] # Positive minimum normalized

# Double +/-inf
DOUBLE_FLOAT_INFS           = ['0xc7effffff0000000', # Negative overflow (-inf)
                               '0x47effffff0000000'] # Positive overflow (+inf)

DOUBLE_FLOAT_VALUES         = ['0xc7efffffefffffff', # Negative maximum normalized
                               '0xc170000000000000', # -16777216.0
                               '0xc014000000000000', # -5.0
                               '0xbfff25ce60000000', # -1.9467300176620483
                               '0xb810000000000000', # Negative minimum normalized
                               '0xb69fffffffffffff', # Negative underflow
                               '0x369fffffffffffff', # Positive underflow
                               '0x3810000000000000', # Positive minimum normalized
                               '0x3fff25ce60000000', # +1.9467300176620483
                               '0x4014000000000000', # +5.0
                               '0x4170000000000000', # +16777216.0
                               '0x47efffffefffffff'] # Positive maximum normalized

DOUBLE_UINT_VALUES          = ['0xbfeccccccccccccd', # -0.9
                               #'0x8010000000000000', # Negative minimum normalized          -- Already checked
                               #'0x800fffffffffffff', # Negative maximum denormalized        -- Already checked
                               #'0x8000000000000001', # Negative minimum denormalized        -- Already checked
                               #'0x8000000000000000', # Negative minimum        (-0)         -- Already checked
                               #'0x0000000000000000', # Positive minimum        (+0)         -- Already checked
                               '0x3fff25ce60000000', # +1.9467300176620483
                               '0x4014000000000000', # +5.0
                               '0x4170000000000000', # +16777216.0
                               '0x41dfffffffc00000', # Signed int low frontier (+2147483647)
                               '0x41e0000000000000', # Signed int up frontier  (+2147483648)
                               '0x41efffffffe00000'] # Maximum                 (+4294967295)

DOUBLE_INT_VALUES           = ['0xc1e0000000000000', # Minimum          (-2147483648)
                               '0xc170000000000000', # -16777216.0
                               '0xc014000000000000', # -5.0
                               '0xbfff25ce60000000', # -1.9467300176620483
                               #'0x8000000000000000', # Negative minimum        (-0)         -- Already checked
                               #'0x0000000000000000', # Minimum                 (+0)         -- Already checked
                               '0x3fff25ce60000000', # +1.9467300176620483
                               '0x4014000000000000', # +5.0
                               '0x4170000000000000', # +16777216.0
                               '0x41dfffffffc00000'] # Maximum          (+2147483647)

DOUBLE_BOOL_VALUES          = [#'0x8010000000000000', # Minimum negative True value -- Already checked
                               #'0x0000000000000000', # False                       -- Already checked
                               #'0x0010000000000000', # Minimum positive True value -- Already checked
                              ]

FLOAT_INFS                  = ['0xff800000', # -inf
                               '0x7f800000'] # +inf

FLOAT_NEG_ZERO              = ['0x80000000'] # Negative underflow (-0.0)

FLOAT_POS_ZERO              = ['0x00000000'] # Positive underflow (+0.0)

FLOAT_VALUES                = ['0xff7fffff', # Negative maximum normalized
                               '0xcb800000', # -16777216.0
                               '0xc0a00000', # -5.0
                               '0xbff92e73', # -1.9467300176620483
                               '0x80800000', # Negative minimum normalized
                               #'0x807fffff', # Negative maximum denormalized -- Denormalized may be flushed to 0
                               #'0x80000001', # Negative minimum denormalized -- Denormalized may be flushed to 0
                               #'0x00000001', # Positive minimum denormalized -- Denormalized may be flushed to 0
                               #'0x007fffff', # Positive maximum denormalized -- Denormalized may be flushed to 0
                               '0x00800000', # Positive minimum normalized
                               '0x3ff92e73', # +1.9467300176620483
                               '0x40a00000', # +5.0
                               '0x4b800000', # +16777216.0
                               '0x7f7fffff'] # Positive maximum normalized

FLOAT_INT64_VALUES          = ['0xcf800000', # -4294967296.0
                               '0xcb800000', # -16777216.0
                               '0xc0a00000', # -5.0
                               '0xbff92e73', # -1.9467300176620483
                               '0x80800000', # Negative minimum normalized
                               '0x807fffff', # Negative maximum denormalized
                               '0x80000001', # Negative minimum denormalized
                               '0x00000001', # Positive minimum denormalized
                               '0x007fffff', # Positive maximum denormalized
                               '0x00800000', # Positive minimum normalized
                               '0x3ff92e73', # +1.9467300176620483
                               '0x40a00000', # +5.0
                               '0x4b800000', # +16777216.0
                               '0x4f800000'] # +4294967296.0

FLOAT_UINT64_VALUES         = ['0x00000001', # Positive minimum denormalized
                               '0x007fffff', # Positive maximum denormalized
                               '0x00800000', # Positive minimum normalized
                               '0x3ff92e73', # +1.9467300176620483
                               '0x40a00000', # +5.0
                               '0x4b800000', # +16777216.0
                               '0x4f800000'] # +4294967296.0

UINT64_VALUES               = ['0', # Minimum
                               '5',
                               '2147483647', # Signed int32 low frontier
                               '2147483648', # Signed int32 up frontier
                               '4294967295', # Maximum unsigned int32
                               '4294967296',
                               '9223372036854775807', # Signed int64 low frontier
                               '9223372036854775808', # Signed int64 up frontier
                               '18446744073709551615'] # Maximum

INT64_VALUES                = ['-9223372036854775808', # Minimum
                               '-2147483649',
                               '-2147483648',
                               '-5',
                               '-1',
                               '0',
                               '1',
                               '5',
                               '2147483647', # Signed int32 low frontier
                               '2147483648', # Signed int32 up frontier
                               '4294967295', # Maximum unsigned int32
                               '4294967296',
                               '9223372036854775807'] # Maximum

UINT_VALUES                 = ['0', # Minimum
                               '5',
                               '2147483647', # Signed int low frontier
                               '2147483648', # Signed int up frontier
                               '4294967295'] # Maximum

INT_VALUES                  = ['-2147483648', # Minimum
                               '-5',
                               '-1',
                               '0',
                               '1',
                               '5',
                               '2147483647'] # Maximum

BOOL_VALUES                 = ['0', # False
                               '1'] # True
# pylint: enable=bad-whitespace,line-too-long,bad-continuation

def get_dir_name(ver, test_type):
    """Returns the directory name to save tests given a GLSL version and a
       test type.
    """

    assert isinstance(ver, str)
    assert isinstance(test_type, 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, test_type,
                        'conversion')


class TestTuple(object):
    """A float64 derived and other type derived tuple to generate the
       needed conversion tests.
    """

    @staticmethod
    def float_to_hex(fvalue):
        """Returns the hexadecimal representation from a float32 value."""
        assert isinstance(fvalue, np.float32)
        return hex(struct.unpack('<I', struct.pack('<f', fvalue))[0])

    @staticmethod
    def double_to_hex(fvalue):
        """Returns the hexadecimal representation from a float64 value."""
        assert isinstance(fvalue, float)
        return hex(struct.unpack('<Q', struct.pack('<d', fvalue))[0]).rstrip("L")

    @staticmethod
    def hex_to_float(hstr):
        """Returns a float32 value from its hexadecimal representation."""
        assert isinstance(hstr, str)
        return struct.unpack('<f', struct.pack('<I', int(hstr, 16)))[0]

    @staticmethod
    def hex_to_double(hstr):
        """Returns a float64 value from its hexadecimal representation."""

        assert isinstance(hstr, str)
        return struct.unpack('<d', struct.pack('<Q', int(hstr, 16)))[0]

    @staticmethod
    def float_hex_to_double_hex(hstr):
        """Returns the float64 hexadecimal representation from a float32
           hexadecimal representation.
        """
        assert isinstance(hstr, str)
        double_value = TestTuple.hex_to_float(hstr)
        return TestTuple.double_to_hex(double_value)

    @staticmethod
    def float_hex_to_inv_double_hex(hstr):
        """Returns the inverted float64 hexadecimal representation from a
           float32 hexadecimal representation.
        """
        assert isinstance(hstr, str)
        temp = TestTuple.hex_to_float(hstr)
        double_value = np.divide(1.0, temp)
        return TestTuple.double_to_hex(double_value)

    @staticmethod
    def float_hex_to_int64_str(hstr):
        """Returns the int64 string representation from a float32
           hexadecimal representation.
        """
        assert isinstance(hstr, str)
        x = TestTuple.hex_to_float(hstr)
        if x > np.iinfo(np.dtype('int64')).max:
            return str(np.iinfo(np.dtype('int64')).max)
        if x < np.iinfo(np.dtype('int64')).min:
            return str(np.iinfo(np.dtype('int64')).min)
        return str(int(x))

    @staticmethod
    def float_hex_to_uint64_str(hstr):
        """Returns the uint64 string representation from a float32
           hexadecimal representation.
        """
        assert isinstance(hstr, str)
        x = TestTuple.hex_to_float(hstr)
        if x > np.iinfo(np.dtype('uint64')).max:
            return str(np.iinfo(np.dtype('uint64')).max)
        if x < np.iinfo(np.dtype('uint64')).min:
            return str(np.iinfo(np.dtype('uint64')).min)
        return str(int(x))

    @staticmethod
    def int_str_to_bool_str(istr):
        """Returns a bool/integer string from an (arbitrary size) integet string."""
        assert isinstance(istr, str)
        return str(int(bool(int(istr))))

    @staticmethod
    def int_str_to_float_hex(istr):
        """Returns a float32 hexadecimal representation from an (arbitrary size) integer string."""
        assert isinstance(istr, str)
        return TestTuple.float_to_hex(np.float32(int(istr)))

    @staticmethod
    def int_str_to_double_hex(istr):
        """Returns a float64 hexadecimal representation from an (arbitrary size) integer string."""
        assert isinstance(istr, str)
        return TestTuple.double_to_hex(float(istr))

    @staticmethod
    def int_str_to_double_str(istr):
        """Returns a float64 string from an (arbitrary size) integer string."""
        assert isinstance(istr, str)
        return str(float(istr))

    @staticmethod
    def int_str_to_int32_str(istr):
        """Returns an int32 string from an (arbitrary size) integer string."""
        x = int(istr) & (2**32 - 1)
        if x >= 2**31:
            x -= 2**32
        return str(x)

    @staticmethod
    def int_str_to_uint32_str(istr):
        """Returns an uint32 string from an (arbitrary size) integer string."""
        x = int(istr) & (2**32 - 1)
        return str(x)

    @staticmethod
    def int_str_to_int64_str(istr):
        """Returns an int64 string from an (arbitrary size) integer string."""
        x = int(istr) & (2**64 - 1)
        if x >= 2**63:
            x -= 2**64
        return str(x)

    @staticmethod
    def int_str_to_uint64_str(istr):
        """Returns an uint64 string from an (arbitrary size) integer string."""
        x = int(istr) & (2**64 - 1)
        return str(x)

    @staticmethod
    def int_str_to_type_str(target_type):
        """Returns a function for converting an int string to a string for the given type."""
        assert target_type in ('d', 'i64', 'u64')
        if target_type == 'd':
            return TestTuple.int_str_to_double_str
        elif target_type == 'i64':
            return TestTuple.int_str_to_int64_str
        elif target_type == 'u64':
            return TestTuple.int_str_to_uint64_str

    @staticmethod
    def double_hex_to_bool_str(hstr):
        """Returns a bool string from a float64 hexadecimal representation."""
        assert isinstance(hstr, str)
        bool_double = TestTuple.hex_to_double(hstr)
        return '1' if bool_double != 0.0 else '0'

    @staticmethod
    def double_hex_to_int_str(hstr):
        """Returns an int32 string from a float64 hexadecimal
           representation.
        """
        assert isinstance(hstr, str)
        int_double = TestTuple.hex_to_double(hstr)
        if int_double > np.iinfo(np.dtype('int32')).max:
            return str(np.iinfo(np.dtype('int32')).max)
        if int_double < np.iinfo(np.dtype('int32')).min:
            return str(np.iinfo(np.dtype('int32')).min)
        return str(int(int_double))

    @staticmethod
    def double_hex_to_uint_str(hstr):
        """Returns an uint32 string from a float64 hexadecimal
           representation.
        """
        assert isinstance(hstr, str)
        uint_double = TestTuple.hex_to_double(hstr)
        if uint_double > np.iinfo(np.dtype('uint32')).max:
            return str(np.iinfo(np.dtype('uint32')).max)
        if uint_double < np.iinfo(np.dtype('uint32')).min:
            return str(np.iinfo(np.dtype('uint32')).min)
        return str(int(uint_double))

    @staticmethod
    def double_hex_to_float_hex(hstr):
        """Returns the float32 hexadecimal representation from a float64
           hexadecimal representation.
        """
        assert isinstance(hstr, str)
        float_double = np.float32(TestTuple.hex_to_double(hstr))
        return TestTuple.float_to_hex(float_double)

    @staticmethod
    def double_hex_to_inv_float_hex(hstr):
        """Returns the inverted float32 hexadecimal representation from a
           float64 hexadecimal representation.
        """
        assert isinstance(hstr, str)
        temp = np.divide(1.0, TestTuple.hex_to_double(hstr))
        float_double = np.float32(temp)
        return TestTuple.float_to_hex(float_double)

    @staticmethod
    def double_hex_to_int64_str(hstr):
        """Returns the int64 string representation from a float64
           hexadecimal representation.
        """
        assert isinstance(hstr, str)
        x = TestTuple.hex_to_double(hstr)
        if x > np.iinfo(np.dtype('int64')).max:
            return str(np.iinfo(np.dtype('int64')).max)
        if x < np.iinfo(np.dtype('int64')).min:
            return str(np.iinfo(np.dtype('int64')).min)
        return str(int(x))

    @staticmethod
    def double_hex_to_uint64_str(hstr):
        """Returns the uint64 string representation from a float64
           hexadecimal representation.
        """
        assert isinstance(hstr, str)
        x = TestTuple.hex_to_double(hstr)
        if x > np.iinfo(np.dtype('uint64')).max:
            return str(np.iinfo(np.dtype('uint64')).max)
        if x < np.iinfo(np.dtype('uint64')).min:
            return str(np.iinfo(np.dtype('uint64')).min)
        return str(int(x))

    def __init__(self, ver, stage,
                 first_dimension, second_dimension,
                 basic_type, target_type, names_only):
        assert stage in ('vert', 'geom', 'frag')
        assert first_dimension in ('1', '2', '3', '4')
        assert second_dimension in ('1', '2', '3', '4')
        assert isinstance(names_only, bool)

        self._ver = ver
        self._stage = stage
        self._basic_type = basic_type
        self._target_type = target_type
        self._names_only = names_only
        self._target_full_type = ''
        self._conversion_type = ''
        self._uniform_type = ''
        self._amount = int(first_dimension) * int(second_dimension)
        self._filenames = []
        self._extensions = []

        if ver.startswith('GL_'):
            if basic_type == 'd' or target_type == 'd':
                self._extensions.append('GL_ARB_gpu_shader_fp64')
            if basic_type in ('i64', 'u64') or target_type in ('i64', 'u64'):
                self._extensions.append('GL_ARB_gpu_shader_int64')

        if first_dimension != '1':
            dimensional_type = 'mat' + first_dimension
            if first_dimension != second_dimension:
                dimensional_type += 'x' + second_dimension
        elif second_dimension != '1':
            dimensional_type = 'vec' + second_dimension
        else:
            dimensional_type = ''

        if dimensional_type == '':
            if basic_type == 'b':
                self._conversion_type = 'bool'
                self._uniform_type = 'int'
            elif basic_type == 'i':
                self._conversion_type = 'int'
            elif basic_type == 'u':
                self._conversion_type = 'uint'
            elif basic_type == 'f':
                self._conversion_type = 'float'
            elif basic_type == 'd':
                self._conversion_type = 'double'
            elif basic_type == 'i64':
                self._conversion_type = 'int64_t'
            elif basic_type == 'u64':
                self._conversion_type = 'uint64_t'
            if self._uniform_type == '':
                self._uniform_type = self._conversion_type
            if target_type == 'd':
                self._target_full_type = 'double'
            elif target_type == 'i64':
                self._target_full_type = 'int64_t'
            elif target_type == 'u64':
                self._target_full_type = 'uint64_t'
        else:
            self._conversion_type = (basic_type if basic_type != 'f' else '') + dimensional_type
            if basic_type == 'b':
                self._uniform_type = 'i' + dimensional_type
            else:
                self._uniform_type = self._conversion_type
            self._target_full_type = target_type + dimensional_type

    @abc.abstractmethod
    def _gen_to_target(self):
        """Generates the test files for conversions to float64."""

    @abc.abstractmethod
    def _gen_from_target(self):
        """Generates the test files for conversions from float64."""

    @property
    def filenames(self):
        """Returns the test file names this tuple will generate."""
        if self._filenames == []:
            tmp = self._names_only
            self._names_only = True
            self.generate_test_files()
            self._names_only = tmp
        return self._filenames

    def generate_test_files(self):
        """Generate the GLSL parser tests."""
        self._filenames = []

        self._gen_to_target()
        self._gen_from_target()


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

    @staticmethod
    def all_tests(names_only):
        """Returns all the possible contained conversion test instances."""

        assert isinstance(names_only, bool)
        stages = ['vert', 'geom', 'frag']
        dimensions = ['1', '2', '3', '4']
        basic_types = ['b', 'u', 'i', 'f', 'd', 'i64', 'u64']
        target_types = ['d', 'i64', 'u64']
        glsl_ver = ['GL_ARB_gpu_shader_int64', 'GL_ARB_gpu_shader_fp64', '400']

        if not names_only:
            test_types = ['compiler', 'execution']
            for ver, test_type in itertools.product(glsl_ver, test_types):
                utils.safe_makedirs(get_dir_name(ver, test_type))

        for ver, stage, first_dimension, second_dimension, basic_type, target_type in itertools.product(
                glsl_ver,
                stages,
                dimensions,
                dimensions,
                basic_types,
                target_types):
            has_int64 = basic_type in ('i64', 'u64') or target_type in ('i64', 'u64')
            if (not (first_dimension != '1' and
                     (second_dimension == '1' or basic_type not in ('f', 'd') or target_type != 'd')) and
                (basic_type not in target_types or basic_type < target_type) and
                ((ver == 'GL_ARB_gpu_shader_int64') == has_int64)):
                yield RegularTestTuple(ver, stage,
                                       first_dimension, second_dimension,
                                       basic_type, target_type, names_only)

    def __init__(self, ver, stage,
                 first_dimension, second_dimension,
                 basic_type, target_type, names_only):
        assert ver in ('GL_ARB_gpu_shader_int64', 'GL_ARB_gpu_shader_fp64', '400')
        assert basic_type in ('b', 'u', 'i', 'f', 'd', 'i64', 'u64')
        assert target_type in ('d', 'i64', 'u64')
        assert not (first_dimension != '1' and
                    (second_dimension == '1' or basic_type not in ('f', 'd') or target_type != 'd'))
        super(RegularTestTuple, self).__init__(ver, stage,
                                               first_dimension, second_dimension,
                                               basic_type, target_type, names_only)

    def _gen_comp_test(self, from_type, to_type, converted_from):
        filename = os.path.join(
            get_dir_name(self._ver, 'compiler'),
            '{}-conversion-implicit-{}-{}-bad.{}'.format(self._stage, from_type, to_type,
                                                         self._stage))

        self._filenames.append(filename)

        if not self._names_only:
            with open(filename, 'w') as test_file:
                try:
                    test_file.write(TEMPLATES.get_template(
                        'compiler.{}.mako'.format(self._stage)).render_unicode(
                            ver=self._ver,
                            extensions=self._extensions,
                            from_type=from_type,
                            to_type=to_type,
                            converted_from=converted_from))
                except:
                    print(exceptions.text_error_template().render(), file=sys.stderr)
                    raise

    def _gen_exec_test(self, from_type, to_type,
                       uniform_from_type, uniform_to_type,
                       explicit, converted_from, conversions):
        filename = os.path.join(
            get_dir_name(self._ver, 'execution'),
            '{}-conversion-{}-{}-{}.shader_test'.format(self._stage, explicit,
                                                        from_type, to_type))

        self._filenames.append(filename)

        if not self._names_only:
            with open(filename, 'w') as test_file:
                try:
                    test_file.write(TEMPLATES.get_template(
                        'execution.{}.shader_test.mako'.format(self._stage)).render_unicode(
                            ver=self._ver,
                            extensions=self._extensions,
                            amount=self._amount,
                            from_type=from_type,
                            to_type=to_type,
                            converted_from=converted_from,
                            uniform_from_type=uniform_from_type,
                            uniform_to_type=uniform_to_type,
                            conversions=conversions))
                except:
                    print(exceptions.text_error_template().render(), file=sys.stderr)
                    raise

    def _gen_to_target(self):
        converted_from = 'from'
        explicit = 'implicit'

        if self._basic_type == 'b':
            conversion_values = BOOL_VALUES
            conversion_function = TestTuple.int_str_to_type_str(self._target_type)
        elif self._basic_type == 'i':
            conversion_values = INT_VALUES
            conversion_function = TestTuple.int_str_to_type_str(self._target_type)
        elif self._basic_type == 'u':
            conversion_values = UINT_VALUES
            conversion_function = TestTuple.int_str_to_type_str(self._target_type)
        elif self._basic_type == 'f':
            if self._target_type == 'd':
                conversion_values = FLOAT_INFS + FLOAT_NEG_ZERO + FLOAT_POS_ZERO + FLOAT_VALUES
                conversion_function = TestTuple.float_hex_to_double_hex
            elif self._target_type == 'i64':
                conversion_values = FLOAT_POS_ZERO + FLOAT_INT64_VALUES
                conversion_function = TestTuple.float_hex_to_int64_str
            elif self._target_type == 'u64':
                conversion_values = FLOAT_POS_ZERO + FLOAT_UINT64_VALUES
                conversion_function = TestTuple.float_hex_to_uint64_str
        elif self._basic_type == 'd':
            if self._target_type == 'i64':
                conversion_values = DOUBLE_DENORMAL_VALUES + DOUBLE_NORMAL_VALUES + DOUBLE_INT_VALUES
                conversion_function = TestTuple.double_hex_to_int64_str
            elif self._target_type == 'u64':
                conversion_values = DOUBLE_DENORMAL_VALUES + DOUBLE_NORMAL_VALUES + DOUBLE_UINT_VALUES
                conversion_function = TestTuple.double_hex_to_uint64_str
        elif self._basic_type in ('i64', 'u64'):
            if self._basic_type == 'i64':
                conversion_values = INT64_VALUES
            else:
                conversion_values = UINT64_VALUES
            conversion_function = TestTuple.int_str_to_type_str(self._target_type)

        if (self._basic_type == 'b' or
            (self._basic_type in ('f', 'd') and self._target_type in ('i64', 'u64')) or
            (self._basic_type == 'u' and self._target_type == 'i64')):
            explicit = 'explicit'
            self._gen_comp_test(self._conversion_type, self._target_full_type,
                                converted_from)
            converted_from = self._target_full_type + '(from)'

        conversions = []
        for value in conversion_values:
            to_value = conversion_function(value)
            item = {'from': value, 'to': to_value}
            conversions.append(item)

        self._gen_exec_test(self._conversion_type, self._target_full_type,
                            self._uniform_type, self._target_full_type,
                            explicit, converted_from, conversions)

    def _gen_from_target(self):
        converted_from = 'from'
        explicit = 'implicit'

        if self._target_type == 'd':
            if self._basic_type == 'b':
                conversion_values = DOUBLE_INFS + DOUBLE_NORMAL_VALUES + DOUBLE_BOOL_VALUES
                conversion_function = TestTuple.double_hex_to_bool_str
            elif self._basic_type == 'i':
                conversion_values = DOUBLE_DENORMAL_VALUES + DOUBLE_NORMAL_VALUES + DOUBLE_INT_VALUES
                conversion_function = TestTuple.double_hex_to_int_str
            elif self._basic_type == 'u':
                conversion_values = DOUBLE_DENORMAL_VALUES + DOUBLE_NORMAL_VALUES + DOUBLE_UINT_VALUES
                conversion_function = TestTuple.double_hex_to_uint_str
            elif self._basic_type == 'f':
                conversion_values = DOUBLE_INFS + DOUBLE_FLOAT_INFS + DOUBLE_FLOAT_VALUES
                conversion_function = TestTuple.double_hex_to_float_hex
            conversion_values = DOUBLE_NEG_ZERO + DOUBLE_POS_ZERO + conversion_values
        elif self._target_type in ('i64', 'u64'):
            if self._target_type == 'i64':
                conversion_values = INT64_VALUES
            elif self._target_type == 'u64':
                conversion_values = UINT64_VALUES

            if self._basic_type == 'b':
                conversion_function = TestTuple.int_str_to_bool_str
            elif self._basic_type == 'i':
                conversion_function = TestTuple.int_str_to_int32_str
            elif self._basic_type == 'u':
                conversion_function = TestTuple.int_str_to_uint32_str
            elif self._basic_type == 'f':
                conversion_function = TestTuple.int_str_to_float_hex
            elif self._basic_type == 'd':
                conversion_function = TestTuple.int_str_to_double_hex
            elif self._basic_type == 'i64':
                conversion_function = TestTuple.int_str_to_int64_str
            elif self._basic_type == 'i':
                conversion_function = TestTuple.int_str_to_uint64_str

        if self._basic_type != 'd':
            self._gen_comp_test(self._target_full_type, self._conversion_type,
                                converted_from)

            converted_from = self._conversion_type + '(from)'
            explicit = 'explicit'
        else:
            assert self._target_type in ('i64', 'u64')

        conversions = []
        for value in conversion_values:
            to_value = conversion_function(value)
            item = {'from': value, 'to': to_value}
            conversions.append(item)

        self._gen_exec_test(self._target_full_type, self._conversion_type,
                            self._target_full_type, self._uniform_type,
                            explicit, converted_from, conversions)


class ZeroSignTestTuple(TestTuple):
    """Derived class for conversion tests using the float32 and float64
       +/-0.0 values.
    """

    @staticmethod
    def all_tests(names_only):
        """Returns all the possible zero sign conversion test instances."""

        assert isinstance(names_only, bool)
        stages = ['vert', 'geom', 'frag']
        dimensions = ['1', '2', '3', '4']
        basic_types = ['f']
        glsl_ver = ['410', '420']

        if not names_only:
            for ver in glsl_ver:
                utils.safe_makedirs(get_dir_name(ver, 'execution'))

        for ver, stage, first_dimension, second_dimension, basic_type in itertools.product(
                glsl_ver,
                stages,
                dimensions,
                dimensions,
                basic_types):
            if not (first_dimension != '1' and second_dimension == '1'):
                yield ZeroSignTestTuple(ver, stage,
                                        first_dimension, second_dimension,
                                        basic_type, names_only)

    def __init__(self, ver, stage,
                 first_dimension, second_dimension,
                 basic_type, names_only):
        assert ver in ('410', '420')
        assert basic_type == 'f'
        assert not (first_dimension != '1' and second_dimension == '1')
        super(ZeroSignTestTuple, self).__init__(ver, stage,
                                                first_dimension, second_dimension,
                                                basic_type, 'd', names_only)

    def __gen_zero_sign_exec_test(self, from_type, to_type,
                                  uniform_from_type, uniform_to_type,
                                  explicit, converted_from, conversions):
        filename = os.path.join(
            get_dir_name(self._ver, 'execution'),
            '{}-conversion-{}-{}-{}-zero-sign.shader_test'.format(self._stage, explicit,
                                                                  from_type, to_type))

        self._filenames.append(filename)

        if not self._names_only:
            with open(filename, 'w') as test_file:
                try:
                    test_file.write(TEMPLATES.get_template(
                        'execution-zero-sign.{}.shader_test.mako'.format(
                            self._stage)).render_unicode(
                                ver=self._ver,
                                extensions=self._extensions,
                                amount=self._amount,
                                from_type=from_type,
                                to_type=to_type,
                                converted_from=converted_from,
                                uniform_from_type=uniform_from_type,
                                uniform_to_type=uniform_to_type,
                                conversions=conversions))
                except:
                    print(exceptions.text_error_template().render(), file=sys.stderr)
                    raise

    def _gen_to_target(self):
        if self._ver == '410':
            conversion_values = FLOAT_POS_ZERO
        elif self._ver == '420':
            conversion_values = FLOAT_NEG_ZERO

        conversions = []
        for value in conversion_values:
            to_value = TestTuple.float_hex_to_inv_double_hex(value)
            item = {'from': value, 'to': to_value}
            conversions.append(item)

        self.__gen_zero_sign_exec_test(self._conversion_type, self._target_full_type,
                                       self._uniform_type, self._target_full_type,
                                       'implicit', 'from', conversions)

    def _gen_from_target(self):
        if self._ver == '410':
            conversion_values = DOUBLE_POS_ZERO
        elif self._ver == '420':
            conversion_values = DOUBLE_NEG_ZERO

        conversions = []
        for value in conversion_values:
            to_value = TestTuple.double_hex_to_inv_float_hex(value)
            item = {'from': value, 'to': to_value}
            conversions.append(item)

        self.__gen_zero_sign_exec_test(self._target_full_type, self._conversion_type,
                                       self._target_full_type, self._uniform_type,
                                       'explicit', self._conversion_type + '(from)', conversions)


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

    parser = argparse.ArgumentParser(
        description="Generate shader tests that check the conversions from and "
        "to fp64")
    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()

    np.seterr(divide='ignore')

    for test in (list(RegularTestTuple.all_tests(args.names_only)) +
                 list(ZeroSignTestTuple.all_tests(args.names_only))):
        test.generate_test_files()
        for filename in test.filenames:
            print(filename)


if __name__ == '__main__':
    main()
