# coding=utf-8
# Copyright (c) 2015-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 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 tests for variable index writes.

This creates a TestParams object for each invocation (using a factory to reduce
the number of duplicate objects), and then passes that into a mako template.
The template then formats that information into a shader_test for either the
fragment shader stage or the vertex shader stage.

"""

from __future__ import (
    print_function, absolute_import, division, unicode_literals
)
import copy
import itertools
import os

from six.moves import range  # pylint: disable=redefined-builtin

from modules import utils, glsl
from templates import template_dir

_TEMPLATES = template_dir(os.path.basename(os.path.splitext(__file__)[0]))
_VS_TEMPLATE = _TEMPLATES.get_template('vs.shader_test.mako')
_FS_TEMPLATE = _TEMPLATES.get_template('fs.shader_test.mako')
_DIRNAME = os.path.join('spec', 'glsl-{}', 'execution', 'variable-indexing')


class TestParams(object):
    """Object representing all of the parameters of a single test instance.

    Provides all of the values using lazy properties, which tie in with the
    ParamsFactory to store all of the values, speeding up run times.

    """
    def __init__(self, mode, array_dim, matrix_dim, index_value, col,
                 value_type, glsl_version):
        # pylint: disable=too-many-arguments
        assert array_dim in [0, 3]
        assert matrix_dim in [2, 3, 4]

        self.mode = mode
        self.array_dim = array_dim
        self.matrix_dim = matrix_dim
        self.index_value = index_value
        self.col = '[{}]'.format(col)
        self.value_type = value_type
        self.version = glsl.Version(glsl_version)

    @utils.lazy_property
    def varying_comps(self):
        if self.array_dim != 0:
            return self.matrix_dim**2 * self.array_dim
        else:
            return self.matrix_dim**2

    @utils.lazy_property
    def base_type(self):
        if int(self.version) >= 120:
            return 'mat{0}x{0}'.format(self.matrix_dim)
        else:
            return 'mat{}'.format(self.matrix_dim)

    @utils.lazy_property
    def type(self):
        if self.array_dim != 0 and int(self.version) >= 120:
            return '{}[{}]'.format(self.base_type, self.array_dim)
        else:
            return self.base_type

    @utils.lazy_property
    def dim(self):
        if self.array_dim != 0 and int(self.version) < 120:
            return '[{}]'.format(self.array_dim)
        else:
            return ''

    @utils.lazy_property
    def row(self):
        if self.value_type == 'float':
            return '[row]'
        else:
            return ''

    @utils.lazy_property
    def idx(self):
        if self.array_dim != 0:
            return '[{}]'.format(self.index_value)
        else:
            return ''

    @utils.lazy_property
    def test_vec(self):
        if self.matrix_dim == 2:
            return ["0.803161418975390", "0.852987140792140"]
        elif self.matrix_dim == 3:
            return ["0.681652305322399", "0.210426138878113",
                    "0.185916924650237"]
        elif self.matrix_dim == 4:
            return ["0.0394868046587045", "0.8922408276905568",
                    "0.3337495624366961", "0.8732295730825839"]

    @utils.lazy_property
    def test_exp(self):
        if self.matrix_dim == 2:
            return ["0.708718134966688", "1.452243795483797"]
        elif self.matrix_dim == 3:
            return ["0.610649606928364", "0.711906885823636",
                    "0.312244778977868"]
        elif self.matrix_dim == 4:
            return ["1.03935908892461", "1.18846180713529", "1.10078681232072",
                    "1.72434439561820"]

    @utils.lazy_property
    def test_mat(self):
        if self.matrix_dim == 2:
            return [["0.241498998195656", "0.861223395812970"],
                    ["0.603473877011433", "0.891622340451180"]]
        elif self.matrix_dim == 3:
            return [
                ["0.493944462129466", "0.722190133917966", "0.239853948232558"],
                ["0.550143078409278", "0.591962645398579", "0.467616286531193"],
                ["0.850846377186973", "0.511303112962423", "0.270815003356504"]
            ]
        elif self.matrix_dim == 4:
            return [["0.922040144261674", "0.158053783109488",
                     "0.357016429866574", "0.836368810383957"],
                    ["0.560251913703792", "0.171634921595771",
                     "0.602494709909111", "0.693273570571311"],
                    ["0.350720358904176", "0.912192627475775",
                     "0.688544081259531", "0.913891056231967"],
                    ["0.442058176039301", "0.829835836794679",
                     "0.365674411003021", "0.879197364462782"]]

    @utils.lazy_property
    def test_sizes(self):
        if self.array_dim == 0:
            return [1]
        elif self.index_value == 'index':
            return list(range(1, self.array_dim + 1))
        else:
            return [2]

    @utils.lazy_property
    def test_columns(self):
        if self.col == '[col]':
            return list(range(1, self.matrix_dim + 1))
        return [2]

    @utils.lazy_property
    def test_rows(self):
        if self.value_type == 'float':
            return list(range(1, self.matrix_dim + 1))
        return [1]

    @utils.lazy_property
    def test_type(self):
        # shader_runner always uses matDxD format
        return 'mat{0}x{0}'.format(self.matrix_dim)

    def test_matrix(self, column, row):
        """Generate the matrix used in a test section.

        This will take the matrix used by the test, and replace specific values
        with sentinal values, and return the matrix as a string.

        """
        bad = ['666.0', '777.0', '888.0', '999.0']
        mat = copy.deepcopy(self.test_mat)

        if self.value_type == 'float':
            mat[column][row] = bad[0]
        else:
            mat[column] = bad[0:self.matrix_dim]

        ret = ''
        for c in mat:
            ret += ' '.join(c)
            ret += ' '
        return ret

    @utils.lazy_property
    def formated_version(self):
        # Note: GLSLVersion.float() does division by 100
        return '{:.2f}'.format(float(self.version))


class ParamsFactory(object):  # pylint: disable=too-few-public-methods
    """A factory class that provides TestParam objects.

    This cuts the number of new objects created by roughly 3/5.

    """
    def __init__(self):
        self.__stored = {}

    def get(self, *args):
        args = tuple(args)
        try:
            return self.__stored[args]
        except KeyError:
            params = TestParams(*args)
            self.__stored[args] = params
            return params


def make_vs(name, params):
    """Create a vertex shader test."""
    dirname = _DIRNAME.format(params.formated_version)
    utils.safe_makedirs(dirname)
    with open(os.path.join(dirname, name), 'w') as f:
        f.write(_VS_TEMPLATE.render_unicode(params=params))
    print(name)


def make_fs(name, params):
    """Create a fragment shader test."""
    dirname = _DIRNAME.format(params.formated_version)
    utils.safe_makedirs(dirname)
    with open(os.path.join(dirname, name), 'w') as f:
        f.write(_FS_TEMPLATE.render_unicode(params=params))
    print(name)


def main():
    """The main function."""
    # Various choices that can be put together to produce a single test.
    iter_ = itertools.product(
        ['110', '120'],       # GLSL versions
        [0, 3],               # Array dimensions
        [2, 3, 4],            # Matrix dimensions
        ['varying', 'temp'],  # modes
        ['col', 1],           # columns
        ['fs', 'vs'],         # shader stages
    )

    factory = ParamsFactory()

    # This can be filled in to produce the file name for the test.
    # Note that idx, col, row, and arr will need to have a '-' added to the end
    # of the value if it is not empty
    name = '{stage}-{mode}-{arr}mat{matrix_dim}-{idx}{col}{row}wr.shader_test'

    for v, a, d, m, c, s in iter_:
        for t in ['float', 'vec{}'.format(d)]:
            if s == 'vs':
                func = make_vs
            elif s == 'fs':
                if m == 'varying':
                    # Fragment shaders cannot write varyings
                    continue
                func = make_fs

            if a != 0:
                arr = 'array-'

                func(
                    name.format(stage=s,
                                mode=m,
                                matrix_dim=d,
                                arr=arr,
                                idx='',
                                col='col-' if c == 'col' else '',
                                row='row-' if t == 'float' else ''),
                    factory.get(m, a, d, 1, c, t, v))
            else:
                arr = ''

            func(
                name.format(stage=s,
                            mode=m,
                            matrix_dim=d,
                            arr=arr,
                            idx='index-' if a != 0 else '',
                            col='col-' if c == 'col' else '',
                            row='row-' if t == 'float' else ''),
                factory.get(m, a, d, 'index', c, t, v))


if __name__ == '__main__':
    main()
