import os
import platform
import re
import sys
import unittest
import warnings

from collections import namedtuple
from contextlib import contextmanager
from ctypes import *
from os import path
from textwrap import dedent
from unittest.mock import patch, Mock

import tecplot as tp
from tecplot import session
from tecplot.constant import *
from tecplot.exception import *


from test import mocked_connected, mocked_sdk_version, patch_tecutil, skip_if_sdk_version_before, skip_if_connected
from ..sample_data import sample_data_file, loaded_sample_data


class TestSolutionTimeClustering(unittest.TestCase):
    @skip_if_sdk_version_before(2021, 2)
    def setUp(self):
        tp.new_layout()
        self.datafile = sample_data_file('10x10x10')
        tp.data.load_tecplot(self.datafile)
        self.ds = tp.active_frame().dataset

    def tearDown(self):
        tp.new_layout()
        os.remove(self.datafile)

    def test_str(self):
        timeclustering = self.ds.solution_time_clustering
        timeclustering.time_scaling = TimeScaling.Linear
        timeclustering.absolute_tolerance = 0.0025
        timeclustering.tolerance_factor = 1.0e-6
        expected = textwrap.dedent('''\
            SolutionTimeClustering:
              time_scaling: TimeScaling.Linear
              absolute_tolerance: {}
              tolerance_factor: {}'''.format(0.0025, 1.0e-6))
        self.assertEqual(expected, str(timeclustering))

    def test_time_scaling(self):
        timeclustering = self.ds.solution_time_clustering
        self.assertEqual(timeclustering.time_scaling, TimeScaling.Linear)
        timeclustering.time_scaling = TimeScaling.Logarithmic
        self.assertEqual(timeclustering.time_scaling, TimeScaling.Logarithmic)
        with self.assertRaises(ValueError):
            timeclustering.time_scaling = 0.5
        with patch_tecutil('SolutionTimeSetTimeScaling', return_value=False):
            with self.assertRaises(TecplotSystemError):
                timeclustering.time_scaling = TimeScaling.Logarithmic

    def test_absolute_tolerance(self):
        timeclustering = self.ds.solution_time_clustering
        for val in [0, 0.0025, 0.5, 1, 100, 1000]:
            timeclustering.absolute_tolerance = val
            self.assertAlmostEqual(timeclustering.absolute_tolerance, val)
        with self.assertRaises(ValueError):
            timeclustering.absolute_tolerance = 'badvalue'
        with self.assertRaises((TecplotLogicError, TecplotSystemError)):
            timeclustering.absolute_tolerance = -1
        with patch_tecutil('SolutionTimeSetAbsoluteTolerance', return_value=False):
            with self.assertRaises(TecplotSystemError):
                timeclustering.absolute_tolerance = 0

    def test_tolerance_factor(self):
        timeclustering = self.ds.solution_time_clustering
        for val in [0, 1e-6, 1e-4, 0.0025, 0.5, 1]:
            timeclustering.tolerance_factor = val
            self.assertAlmostEqual(timeclustering.tolerance_factor, val)
        with self.assertRaises(ValueError):
            timeclustering.tolerance_factor = 'badvalue'
        with self.assertRaises((TecplotLogicError, TecplotSystemError)):
            timeclustering.tolerance_factor = -0.1
        with self.assertRaises((TecplotLogicError, TecplotSystemError)):
            timeclustering.tolerance_factor = 1.1
        with patch_tecutil('SolutionTimeSetToleranceFactor', return_value=False):
            with self.assertRaises(TecplotSystemError):
                timeclustering.tolerance_factor = 0

    def test_all_zeros(self):
        timeclustering = self.ds.solution_time_clustering
        for scaling in TimeScaling:
            timeclustering.time_scaling = scaling
            timeclustering.absolute_tolerance = 0.0025
            timeclustering.tolerance_factor = 1e-6
            self.assertAlmostEqual(timeclustering.absolute_tolerance, 0.0025)
            self.assertAlmostEqual(timeclustering.tolerance_factor, 1e-6)
            timeclustering.absolute_tolerance = 0
            timeclustering.tolerance_factor = 0
            self.assertAlmostEqual(timeclustering.absolute_tolerance, 0)
            self.assertAlmostEqual(timeclustering.tolerance_factor, 0)


class TestDataset(unittest.TestCase):
    def setUp(self):
        self.filenames = {
            '10x10x10' : sample_data_file('10x10x10'),
            '2x2x3_overlap' : sample_data_file('2x2x3_overlap')}
        warnings.simplefilter('always')

    def tearDown(self):
        tp.new_layout()
        for fname in self.filenames.values():
            os.remove(fname)

    def test_init(self):
        page = tp.layout.Page(1)
        frame = tp.layout.Frame(2,page)
        dataset = tp.data.Dataset(3,frame)
        self.assertEqual(dataset.uid,3)
        self.assertEqual(dataset.frame.uid,2)
        self.assertEqual(dataset.frame.page.uid,1)

    def test_create_on_demand(self):
        tp.new_layout()
        fr = tp.active_frame()
        self.assertFalse(fr.has_dataset)
        ds = tp.active_frame().dataset
        self.assertTrue(fr.has_dataset)

    def test_repr(self):
        page = tp.layout.Page(1)
        frame = tp.layout.Frame(2,page)
        dataset = tp.data.Dataset(3,frame)
        self.assertEqual(repr(dataset), 'Dataset(uid=3, frame=Frame(uid=2, page=Page(uid=1)))')

    def test_str(self):
        fmt = 'Dataset: {}\n  Zones: {}\n  Variables: {}'
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['10x10x10'])
        self.assertEqual(str(ds), fmt.format("'Internally created data set'",
            "'Rectangular zone'", "'X', 'Y', 'Z'"))
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        self.assertEqual(str(ds), fmt.format("'Internally created data set'",
            "'Rectangular zone', 'Rectangular zone 1', 'Rectangular zone 2'",
            "'X', 'Y', 'Z', 'P'"))

    def test_contains(self):
        tp.new_layout()
        ds0 = tp.data.load_tecplot(self.filenames['10x10x10'])
        tp.active_page().add_frame()
        ds1 = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        self.assertIn(ds0.variable(0), ds0)
        self.assertNotIn(ds1.variable(0), ds0)
        self.assertIn(ds0.zone(0), ds0)
        self.assertNotIn(ds1.zone(0), ds0)
        class UnknownObj(object):
            def __init__(self, ds):
                self.dataset = ds
        unkobj = UnknownObj(ds0)
        self.assertNotIn(unkobj, ds0)

    def test_eq(self):
        page = tp.layout.Page(1)
        frame = tp.layout.Frame(2,page)
        dataset3 = tp.data.Dataset(3,frame)
        dataset4 = tp.data.Dataset(4,frame)
        dataset3_copy = tp.data.Dataset(3,frame)
        self.assertEqual(dataset3, dataset3_copy)
        self.assertNotEqual(dataset3, dataset4)

    def test_aux_data(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['10x10x10'])
        self.assertIsInstance(ds.aux_data, session.AuxData)
        self.assertEqual(ds.aux_data.object_type, AuxDataObjectType.Dataset)

    def test_title(self):
        tp.new_layout()
        tp.data.load_tecplot(self.filenames['10x10x10'])
        ds = tp.active_frame().dataset

        with patch_tecutil('DataSetSetTitleByUniqueID', return_value=False):
            with self.assertRaises(TecplotSystemError):
                ds.title = 'test'

        self.assertEqual(ds.title, 'Internally created data set')
        ds.title = 'test'
        self.assertEqual(ds.title, 'test')
        ds.title = 'Test Τεστ'
        self.assertEqual(ds.title, 'Test Τεστ')
        ds.title = ''
        self.assertEqual(ds.title, '')

        ds.frame.uid -= 1
        with self.assertRaises((TecplotLogicError, TecplotSystemError)):
            ds.title = None

        with patch_tecutil('DataSetGetInfoByUniqueID',
                           return_value=(False,None,None,None)):
            with self.assertRaises(TecplotSystemError):
                _ = ds.title

    def test_num_zones(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['10x10x10'])
        self.assertEqual(ds.num_zones, 1)
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        self.assertEqual(ds.num_zones, 3)

    def test_zone(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        z0 = ds.zone(0)
        z1 = ds.zone(1)
        self.assertIsInstance(z0, tp.data.OrderedZone)
        self.assertIsInstance(z1, tp.data.OrderedZone)
        self.assertEqual(z0, ds.zone(z0))
        self.assertEqual(z1, ds.zone(z1))
        self.assertEqual(z0, ds.zone('Rectangular zone 1'))
        self.assertEqual(z1, ds.zone('Rectangular zone 2'))
        self.assertEqual(z0, ds.zone(re.compile('Rec.*1')))
        self.assertEqual(z1, ds.zone(re.compile('Rec.*2')))
        self.assertIsNone(ds.zone(re.compile('NoZone')))
        self.assertEqual(z1, ds.zone(-1))
        self.assertEqual(z0, ds.zone(-2))
        with self.assertRaises(TecplotIndexError):
            ds.zone(2)
        with self.assertRaises(TecplotIndexError):
            ds.zone(-3)

    def test_zones(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        self.assertEqual(len(list(ds.zones())), 2)
        self.assertEqual(len(list(ds.zones('Rec*'))), 2)
        self.assertEqual(len(list(ds.zones('rec*'))), 2)
        self.assertEqual(len(list(ds.zones(re.compile(r'Rec.*')))), 2)
        self.assertEqual(len(list(ds.zones(re.compile(r'rec.*')))), 0)
        self.assertEqual(len(list(ds.zones('*1'))), 1)
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            self.assertEqual(len(list(ds.zones('Z*'))), 0)
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'], zones=[1])
        self.assertEqual(ds.num_zones, 2)
        self.assertEqual(len(list(ds.zones())), 1)
        self.assertEqual(len(list(ds.zones('*2'))), 1)

        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])

    def test_zone_indices(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        self.assertEqual(len(list(ds._zone_indices)), 2)
        with patch_tecutil('ZoneGetEnabledByDataSetID', return_value=(False,None)):
            with self.assertRaises(TecplotSystemError):
                _ = ds._zone_indices

    def test_zone_uids(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        self.assertEqual(len(list(ds._zone_uids)), 2)
        with patch_tecutil('ZoneGetUniqueIDsByDataSetID', return_value=(False,None,None)):
            self.assertEqual(len(list(ds._zone_uids)), 2)
            with patch_tecutil('ZoneGetUniqueIDByDataSetID', side_effect=TecplotSystemError):
                with self.assertRaises(TecplotSystemError):
                    _ = ds._zone_uids

    @skip_if_connected
    def test_zone_uids_connected(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        try:
            _client = tp.data.dataset._tecutil_connector.client
            class MockClient:
                connected = True
                def ZoneGetUniqueIDsByDataSetID(self, *a, **kw):
                    return True, 2, [0, 1]
            tp.data.dataset._tecutil_connector.client = MockClient()
            self.assertEqual(len(list(ds._zone_uids)), 2)
        finally:
            tp.data.dataset._tecutil_connector.client = _client

    def test_zone_types(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        self.assertEqual(len(list(ds._zone_types)), 2)
        with patch_tecutil('ZoneGetTypesByDataSetID', return_value=(False,None,None)):
            self.assertEqual(len(list(ds._zone_types)), 2)
            with patch_tecutil('ZoneGetTypeByDataSetID', side_effect=TecplotSystemError):
                self.assertEqual(len(list(ds._zone_types)), 2)
                with patch_tecutil('ZoneGetType', side_effect=TecplotSystemError):
                    with self.assertRaises(TecplotSystemError):
                        _ = ds._zone_types

    @skip_if_connected
    def test_zone_types_connected(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        try:
            _client = tp.data.dataset._tecutil_connector.client
            class MockClient:
                connected = True
                def ZoneGetTypesByDataSetID(self, *a, **kw):
                    return True, 2, [0, 1]
            tp.data.dataset._tecutil_connector.client = MockClient()
            self.assertEqual(len(list(ds._zone_types)), 2)
        finally:
            tp.data.dataset._tecutil_connector.client = _client

    def test_zone_names(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        self.assertEqual(len(list(ds.zone_names)), 2)
        with patch_tecutil('ZoneGetNamesByDataSetID', return_value=(False,None)):
            self.assertEqual(len(list(ds.zone_names)), 2)
            with patch_tecutil('ZoneGetEnabledNamesByDataSetID', return_value=(False,None)):
                self.assertEqual(len(list(ds.zone_names)), 2)
                with patch.object(tp.data.dataset.Dataset, 'zones',
                                  side_effect=TecplotSystemError):
                    with self.assertRaises(TecplotSystemError):
                        _ = ds.zone_names

    def test_num_variables(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['10x10x10'])
        self.assertEqual(ds.num_variables, 3)
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        self.assertEqual(ds.num_variables, 4)

    def test_variable(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        v0 = ds.variable(0)
        v1 = ds.variable(1)
        self.assertIsInstance(v0, tp.data.Variable)
        self.assertIsInstance(v1, tp.data.Variable)
        self.assertEqual(v0, ds.variable('X'))
        self.assertEqual(v1, ds.variable('Y'))
        self.assertEqual(v0, ds.variable(re.compile('X')))
        self.assertEqual(v1, ds.variable(re.compile('Y')))
        self.assertIsNone(ds.variable(re.compile('No Variable')))
        self.assertEqual(ds.variable('P'), ds.variable(-1))
        self.assertEqual(ds.variable('Z'), ds.variable(-2))
        with self.assertRaises(TecplotIndexError):
            ds.variable(4)
        with self.assertRaises(TecplotIndexError):
            ds.variable(-5)

    def test_variables(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        self.assertEqual(len(list(ds.variables())), 4)
        self.assertEqual(len(list(ds.variables('X'))), 1)
        self.assertEqual(len(list(ds.variables('x'))), 1)
        self.assertEqual(len(list(ds.variables(re.compile('X')))), 1)
        self.assertEqual(len(list(ds.variables(re.compile('x')))), 0)
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'], variables=[1,2])
        self.assertEqual(ds.num_variables, 3)
        self.assertEqual(len(list(ds.variables())), 2)
        self.assertEqual(len(list(ds.variables('Y'))), 1)

    def test_copy_zones(self):
        tp.new_layout()
        tp.data.load_tecplot(self.filenames['10x10x10'])
        ds = tp.active_frame().dataset
        z = ds.copy_zones(ds.zones())[0]
        self.assertEqual(ds.num_zones, 2)
        self.assertEqual(ds.zone(0)._shape, z._shape)
        self.assertNotEqual(ds.zone(0).uid, z.uid)
        self.assertEqual(ds.zone(1).uid, z.uid)

        # verify all zones are copied when no zones are specified
        num_zones = ds.num_zones
        z = ds.copy_zones()
        self.assertEqual(num_zones*2, ds.num_zones)

        z = ds.copy_zones([0], i_range=(None, None, None))
        self.assertEqual(z[0].dimensions, (10, 10, 10))

        z = ds.copy_zones([0], i_range=(0, 1, 0), k_range=(1, 9, 5))
        self.assertEqual(z[0].dimensions, (2,10,3))

        with self.assertRaises(TecplotLogicError):
            z = ds.copy_zones([0], i_range=(0, 1, -1))

        # verify that range parameters are ignored when copying non-Ordered
        # zones
        ds = tp.active_page().add_frame().create_dataset('D', ['x','y','z'])
        z = ds.add_fe_zone(ZoneType.FETriangle, 'Z1', 5, 2)
        zcopy = ds.copy_zones(ds.zones(0), i_range=(0,1,1), j_range=(0,1,1),
                              k_range=(0,1,1))
        self.assertEqual(zcopy[0].num_points, 5)
        self.assertEqual(zcopy[0].num_elements, 2)

    def test_mirror_zones(self):
        ds = tp.data.load_tecplot(sample_data_file('2x2x3_overlap'),
            read_data_option=ReadDataOption.ReplaceInActiveFrame)
        fr = tp.active_frame()
        mirrored_zones = list(ds.mirror_zones('X'))
        self.assertEqual(len(mirrored_zones), 2)
        self.assertEqual(ds.num_zones, 4)
        zn = ds.zone(0)
        mirror = ds.zone(2)
        self.assertAlmostEqual(zn.values(0)[0], -mirror.values(0)[0])
        self.assertAlmostEqual(zn.values(0)[1], -mirror.values(0)[1])
        self.assertAlmostEqual(zn.values(0)[2], -mirror.values(0)[2])
        self.assertAlmostEqual(zn.values(1)[0], mirror.values(1)[0])
        self.assertAlmostEqual(zn.values(1)[1], mirror.values(1)[1])
        self.assertAlmostEqual(zn.values(1)[2], mirror.values(1)[2])
        self.assertAlmostEqual(zn.values(2)[0], mirror.values(2)[0])
        self.assertAlmostEqual(zn.values(2)[1], mirror.values(2)[1])
        self.assertAlmostEqual(zn.values(2)[2], mirror.values(2)[2])

        # verify variables are not branched (they are shared)
        zn.values(3)[0] = 4321
        self.assertAlmostEqual(mirror.values(3)[0], 4321)

        mirrored_zones = list(ds.mirror_zones([ds.variable(3)], ds.zone(0), 1))
        self.assertEqual(len(mirrored_zones), 2)
        self.assertEqual(ds.num_zones, 6)
        zn = ds.zone(0)
        mirror = mirrored_zones[0]
        self.assertAlmostEqual(zn.values(0)[2], mirror.values(0)[2])
        self.assertAlmostEqual(zn.values(1)[2], mirror.values(1)[2])
        self.assertAlmostEqual(zn.values(2)[2], mirror.values(2)[2])
        self.assertAlmostEqual(zn.values(3)[2], -mirror.values(3)[2])

        mirrored_zones = list(ds.mirror_zones([ds.variable(3)], [ds.zone(0), 1]))
        self.assertEqual(len(mirrored_zones), 2)
        self.assertEqual(ds.num_zones, 8)
        zn = ds.zone(0)
        mirror = mirrored_zones[0]
        self.assertAlmostEqual(zn.values(0)[2], mirror.values(0)[2])
        self.assertAlmostEqual(zn.values(1)[2], mirror.values(1)[2])
        self.assertAlmostEqual(zn.values(2)[2], mirror.values(2)[2])
        self.assertAlmostEqual(zn.values(3)[2], -mirror.values(3)[2])

    def test_delete_zones_and_variables(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        self.assertIn('P', [v.name for v in ds.variables()])
        self.assertIn('Rectangular zone 2', [z.name for z in ds.zones()])
        ds.delete_variables(ds.variables('P'))
        ds.delete_zones(ds.zones('*2'))
        self.assertNotIn('P', [v.name for v in ds.variables()])
        self.assertNotIn('Rectangular zone 2', [z.name for z in ds.zones()])

        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        self.assertIn('P', [v.name for v in ds.variables()])
        self.assertIn('Rectangular zone 1', [z.name for z in ds.zones()])
        ds.delete_variables(ds.variable('P').index)
        ds.delete_zones(ds.zone('Rectangular zone 1').index)
        self.assertNotIn('P', [v.name for v in ds.variables()])
        self.assertNotIn('Rectangular zone 1', [z.name for z in ds.zones()])

        self.assertTrue(tp.tecutil._tecutil.DataSetJournalIsValid())

    def test_delete_all_zones(self):
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'], read_data_option=ReadDataOption.ReplaceInActiveFrame)
        self.assertEqual(ds.num_zones, 2)

        with self.assertRaises((TecplotLogicError, TecplotSystemError)):
            ds.delete_zones(ds.zones())

    def test_delete_all_variables(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        self.assertEqual(ds.num_variables, 4)
        with self.assertRaises((TecplotLogicError, TecplotSystemError)):
            ds.delete_variables(ds.variables())

    def test_VariablesNamedTuple(self):
        tp.new_layout()
        ds = tp.active_frame().create_dataset('D', ['x', 'y'])
        self.assertEqual(ds.VariablesNamedTuple._fields, tuple('x y'.split()))

        tp.new_layout()
        ds = tp.active_frame().create_dataset('D', ['x', 'x'])
        self.assertEqual(ds.VariablesNamedTuple._fields, tuple('x0 x1'.split()))

        tp.new_layout()
        ds = tp.active_frame().create_dataset('D', ['x']*20)
        t = tuple(['x{:02d}'.format(i) for i in  range(20)])
        self.assertEqual(ds.VariablesNamedTuple._fields, t)

        tp.new_layout()
        ds = tp.active_frame().create_dataset('D', ['X', 'Y=f(X)'])
        t = tuple(['X', 'Y_f_X_'])
        self.assertEqual(ds.VariablesNamedTuple._fields, t)

        tp.new_layout()
        ds = tp.active_frame().create_dataset('D', ['_', '_'])
        t = tuple(['v0', 'v1'])
        self.assertEqual(ds.VariablesNamedTuple._fields, t)

        tp.new_layout()
        ds = tp.active_frame().create_dataset('D', ['def', 'if'])
        t = tuple(['def_', 'if_'])
        self.assertEqual(ds.VariablesNamedTuple._fields, t)

        tp.new_layout()
        ds = tp.active_frame().create_dataset('D', ['1', '2', '3'])
        t = tuple(['v1', 'v2', 'v3'])
        self.assertEqual(ds.VariablesNamedTuple._fields, t)

    def test_add_variable(self):
        tp.new_layout()
        ds = tp.active_frame().create_dataset('D')
        x = ds.add_variable('x')
        self.assertEqual(ds.num_variables, 1)
        self.assertEqual(ds.variable(0).name, 'x')
        self.assertEqual(ds.variable(0), x)
        ds.add_ordered_zone('Z1', 3)
        ds.add_ordered_zone('Z2', 3)
        y = ds.add_variable('y', dtypes=FieldDataType.Double)
        z = ds.add_variable('z', dtypes=[FieldDataType.Double,
                                         FieldDataType.Int32])
        self.assertEqual(ds.num_variables, 3)
        p = ds.add_variable('p', locations=ValueLocation.Nodal)
        q = ds.add_variable('q', locations=[ValueLocation.CellCentered,
                                        ValueLocation.Nodal])
        self.assertEqual(len(q.values('Z1')), 2)
        self.assertEqual(len(q.values('Z2')), 3)

        with patch_tecutil('DataSetAddVarX', return_value=False):
            with self.assertRaises(TecplotSystemError):
                ds.add_variable('r')

    @skip_if_sdk_version_before(2021, 2)
    def test_add_zone(self):
        if __debug__:
            tp.new_layout()
            ds = tp.active_frame().create_dataset('D')
            with self.assertRaises(TecplotLogicError):
                ds.add_zone(ZoneType.Ordered, 'Z', 3)

        tp.new_layout()
        ds = tp.active_frame().create_dataset('D', ['x','y','z','p'])
        z1 = ds.add_zone(ZoneType.Ordered, 'Z1', (3,3,3))
        z2 = ds.add_zone(ZoneType.Ordered, 'Z2', (3,3,3),
            dtypes=FieldDataType.Double, locations=ValueLocation.Nodal,
            solution_time=3.14, strand_id=10, index=0)
        self.assertEqual(ds.num_zones, 1)
        self.assertEqual(z1.uid, z2.uid)
        self.assertEqual(z1.index, 0)
        self.assertEqual(z2.index, 0)
        self.assertEqual(z2.strand, 10)
        self.assertAlmostEqual(z2.solution_time, 3.14)
        z3 = ds.add_zone(ZoneType.Ordered, 'Z3', (3,3,3),
            dtypes=[FieldDataType.Double]*3 + [FieldDataType.Int32],
            locations=[ValueLocation.CellCentered]*4,
            solution_time=6.28, strand_id=5, index=1,
            face_neighbor_mode=FaceNeighborMode.LocalOneToOne)
        self.assertEqual(ds.num_zones, 2)
        self.assertEqual(z3.index, 1)
        self.assertEqual(z3.strand, 5)
        self.assertAlmostEqual(z3.solution_time, 6.28)

        z3_1 = ds.add_zone(ZoneType.Ordered, 'Z3.1', (3,3,3), parent_zone=z3)
        self.assertEqual(z3_1.index, 2)

        with patch_tecutil('DataSetAddZoneX', return_value=False):
            with self.assertRaises(TecplotSystemError):
                ds.add_zone(ZoneType.Ordered, 'Z', 3)

        z4 = ds.add_fe_zone(ZoneType.FELineSeg, 'Z4', 3, 2)
        self.assertEqual(z4.num_points, 3)

        z5 = ds.add_poly_zone(ZoneType.FEPolygon, 'Z5', 3, 2, 1)
        self.assertEqual(z5.num_points, 3)

    def test_create_dataset(self):
        tp.new_layout()
        fr = tp.active_frame()
        self.assertFalse(fr.has_dataset)
        ds = fr.dataset
        self.assertTrue(fr.has_dataset)
        self.assertEqual(ds, fr.dataset)

    @skip_if_sdk_version_before(2021, 2)
    def test_solution_time_clustering(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        tclustering = ds.solution_time_clustering
        self.assertIsInstance(tclustering, tp.data.SolutionTimeClustering)

    @skip_if_sdk_version_before(2020, 2)
    def test_solution_times(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])

        self.assertEqual(ds.num_solution_times, 0)
        self.assertEqual(ds.solution_times, [])

        ds.zone(0).solution_time = 1

        self.assertEqual(ds.num_solution_times, 0)
        self.assertEqual(ds.solution_times, [])

        for z in ds.zones():
            z.strand = 1

        self.assertEqual(ds.num_solution_times, 2)
        self.assertEqual(ds.solution_times, [0,1])

        ds.zone(1).solution_time += 1
        self.assertEqual(ds.num_solution_times, 1)
        self.assertEqual(ds.solution_times, [1])

        ds.zone(1).solution_time = 2
        self.assertEqual(ds.num_solution_times, 2)
        self.assertEqual(ds.solution_times, [1,2])

    def test_solution_time_failures(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])
        for t, z in enumerate(ds.zones()):
            z.strand = 1
            z.solution_time = t

        with patch_tecutil('SolutionTimeGetNumTimeStepsByDataSetID',
                           return_value=(False, 0)):
            with self.assertRaises(TecplotSystemError):
                n = ds.num_solution_times

        with patch_tecutil('SolutionTimeGetSolutionTimesByDataSetID',
                           return_value=(False, 0, 0)):
            with patch_tecutil('ArrayDealloc'):
                with self.assertRaises(TecplotSystemError):
                    n = ds.solution_times

        with mocked_connected():
            with patch_tecutil('SolutionTimeGetSolutionTimesByDataSetID',
                               return_value=(True, 1, [1])):
                with patch_tecutil('ArrayDealloc') as dealloc:
                    n = ds.solution_times
                    self.assertEqual(dealloc.call_count, 0)

    def test_copy(self):
        ds = tp.active_page().add_frame().create_dataset('D', ['x','y'])
        z = ds.add_ordered_zone('Z1', (3,))
        zcopy = z.copy()
        self.assertEqual(ds.num_zones, 2)
        self.assertNotEqual(z, zcopy)
        self.assertEqual(z.dataset, zcopy.dataset)
        self.assertEqual(z.zone_type, zcopy.zone_type)
        self.assertEqual(z.strand, zcopy.strand)
        self.assertEqual(z.solution_time, zcopy.solution_time)
        self.assertEqual(z.name, zcopy.name)
        self.assertEqual(z.values(0).shared_zones, [])
        self.assertEqual(z.values(1).shared_zones, [])

        zcopy2 = ds.copy_zones(z.index, share_variables=True)
        zcopy3 = ds.copy_zones(z, share_variables=False)
        self.assertEqual(zcopy2[0].values(0).shared_zones, [z,zcopy2[0]])
        self.assertEqual(zcopy2[0].values(1).shared_zones, [z,zcopy2[0]])
        self.assertEqual(zcopy3[0].values(0).shared_zones, [])
        self.assertEqual(zcopy3[0].values(1).shared_zones, [])

        zcopy4 = ds.copy_zones(z,share_variables=[ds.variable(1)])
        self.assertEqual(zcopy4[0].values(0).shared_zones, [])
        self.assertEqual(zcopy4[0].values(1).shared_zones, [z,zcopy2[0],zcopy4[0]])

        zcopy5 = ds.copy_zones(z,share_variables=[0])
        self.assertEqual(zcopy5[0].values(0).shared_zones, [z,zcopy2[0],zcopy5[0]])
        self.assertEqual(zcopy5[0].values(1).shared_zones, [])

        zcopy6 = ds.copy_zones([z,zcopy],share_variables=[])
        self.assertEqual(len(zcopy6), 2)
        self.assertEqual(zcopy6[0].values(0).shared_zones, [])
        self.assertEqual(zcopy6[0].values(1).shared_zones, [])
        self.assertEqual(zcopy6[1].values(0).shared_zones, [])
        self.assertEqual(zcopy6[1].values(1).shared_zones, [])

    def test_variable_sharing(self):
        tp.new_layout()
        ds = tp.active_page().add_frame().create_dataset('D', ['x','y'])
        z = ds.add_ordered_zone('Z1', (3,))
        zcopy1 = z.copy()
        self.assertEqual(z.values(0).shared_zones, [])
        self.assertEqual(z.values(1).shared_zones, [])
        ds.share_variables(z, zcopy1, ds.variable(0))
        ds.share_variables(0,[1],[1])
        self.assertEqual(z.values(0).shared_zones, [z,zcopy1])
        self.assertEqual(z.values(1).shared_zones, [z,zcopy1])
        ds.branch_variables(z,0, copy_data=False)
        self.assertEqual(z.values(0).shared_zones, [])
        self.assertEqual(z.values(0).passive, True)
        self.assertEqual(z.values(1).shared_zones, [z,zcopy1])
        ds.branch_variables([0],[ds.variable(1)])
        self.assertEqual(z.values(0).shared_zones, [])
        self.assertEqual(z.values(1).shared_zones, [])

    def test_branch_variable_failure(self):
        tp.new_layout()
        ds = tp.active_page().add_frame().create_dataset('D', ['x','y'])
        z = ds.add_ordered_zone('Z1', (3,))
        zcopy1 = z.copy()
        with patch_tecutil('DataValueBranchShared', return_value=False):
            with self.assertRaises(TecplotSystemError):
                ds.branch_variables(z, 0, copy_data=False)

    def test_variable_sharing_failure(self):
        tp.new_layout()
        ds = tp.active_page().add_frame().create_dataset('D', ['x','y'])
        z = ds.add_ordered_zone('Z1', (3,))
        zcopy1 = z.copy()
        self.assertEqual(z.values(0).shared_zones, [])
        self.assertEqual(z.values(1).shared_zones, [])
        with patch_tecutil('DataValueIsSharingOk', return_value=False):
            with self.assertRaises(TecplotSystemError):
                ds.share_variables(z, zcopy1, ds.variable(0))

    def test_connectivity_sharing(self):
        tp.new_layout()
        ds = tp.active_page().add_frame().create_dataset('D', ['x','y'])
        z_fe = ds.add_fe_zone(tp.constant.ZoneType.FELineSeg, 'Z1', 3, 2)
        z_poly = ds.add_poly_zone(tp.constant.ZoneType.FEPolygon, 'Z1', 3, 1, 1)

        with self.assertRaises(TecplotSystemError):
            ds.share_connectivity(z_fe,[z_poly])
        z_fe2 = z_fe.copy()
        z_poly2 = z_poly.copy()
        self.assertEqual(z_fe2.shared_connectivity, [z_fe,z_fe2])
        self.assertEqual(z_poly2.shared_connectivity, [z_poly,z_poly2])

        ds.branch_connectivity(z_fe)
        ds.branch_connectivity([3])
        self.assertEqual(z_fe2.shared_connectivity, [])
        self.assertEqual(z_poly.shared_connectivity, [])
        ds.share_connectivity(0, [2])
        ds.share_connectivity(z_poly,z_poly2)
        self.assertEqual(z_fe2.shared_connectivity, [z_fe,z_fe2])
        self.assertEqual(z_poly2.shared_connectivity, [z_poly,z_poly2])

        with patch_tecutil('DataConnectBranchShared', return_value=False):
            with self.assertRaises(TecplotSystemError):
                ds.branch_connectivity(z_fe)


    def test_no_matching_pattern(self):
        tp.new_layout()
        ds = tp.data.load_tecplot(self.filenames['2x2x3_overlap'])

        with warnings.catch_warnings(record=True) as w:
            self.assertIsNone(ds.zone('No Zone'))
            self.assertEqual(len(w), 0)
            self.assertIsNone(ds.zone('No Zone?'))
            if __debug__:
                self.assertEqual(len(w), 1)
                self.assertEqual(w[-1].category, TecplotPatternMatchWarning)
            else:
                self.assertEqual(len(w), 0)

        with warnings.catch_warnings(record=True) as w:
            self.assertEqual(list(ds.zones('No Zone')), [])
            self.assertEqual(len(w), 0)
            self.assertEqual(list(ds.zones('No Zone?')), [])
            if __debug__:
                self.assertEqual(len(w), 1)
                self.assertEqual(w[-1].category, TecplotPatternMatchWarning)
            else:
                self.assertEqual(len(w), 0)

        with warnings.catch_warnings(record=True) as w:
            self.assertIsNone(ds.variable('No Variable'))
            self.assertEqual(len(w), 0)
            self.assertIsNone(ds.variable('No Va[r]iable'))
            if __debug__:
                self.assertEqual(len(w), 1)
                self.assertEqual(w[-1].category, TecplotPatternMatchWarning)
            else:
                self.assertEqual(len(w), 0)

        with warnings.catch_warnings(record=True) as w:
            self.assertEqual(list(ds.variables('No Variable')), [])
            self.assertEqual(len(w), 0)
            self.assertEqual(list(ds.variables('No Va[r]iable')), [])
            if __debug__:
                self.assertEqual(len(w), 1)
                self.assertEqual(w[-1].category, TecplotPatternMatchWarning)
            else:
                self.assertEqual(len(w), 0)


class TestDatasetExamples(unittest.TestCase):
    def test_doc_VariablesNamedTuple(self):
        tp.new_layout()
        with loaded_sample_data('3x3x3_p'):
            dataset = tp.active_frame().dataset
            result = tp.data.query.probe_at_position(0,0.1,0.3)
            data = dataset.VariablesNamedTuple(*result.data)
            msg = '(x, p) = ({:.2f}, {:.2f})'.format(data.X, data.P)
            self.assertEqual(msg, '(x, p) = (0.00, -0.02)')


if __name__ == '__main__':
    import test
    test.main()
