#   This file is part of nexdatas - Tango Server for NeXus data writer
#
#    Copyright (C) 2012-2017 DESY, Jan Kotanski <jkotan@mail.desy.de>
#
#    nexdatas is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    nexdatas is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with nexdatas.  If not, see <http://www.gnu.org/licenses/>.
#

""" Provides h5cpp file writer """

import math
import os
import sys
import threading
import numpy as np
from pninexus import h5cpp

from . import filewriter
# from .Types import nptype


try:
    _npver = np.version.version.split(".")
    NPMAJOR = int(_npver[0])
    if NPMAJOR > 1:
        npunicode = np.str_
    else:
        npunicode = np.unicode_
except Exception:
    NPMAJOR = 1
    npunicode = np.unicode_


def nptype(dtype):
    """ converts to numpy types

    :param dtype: h5 writer type type
    :type dtype: :obj:`str`
    :returns: nupy type
    :rtype: :obj:`str`
    """
    if str(dtype) in ['string', b'string']:
        return 'str'
    return dtype


if sys.version_info > (3,):
    unicode = str
    long = int
else:
    bytes = str


def _tostr(text):
    """ converts text  to str type

    :param text: text
    :type text: :obj:`bytes` or :obj:`unicode`
    :returns: text in str type
    :rtype: :obj:`str`
    """
    if isinstance(text, str):
        return text
    elif sys.version_info > (3,):
        return str(text, "utf8")
    else:
        return str(text)


def unlimited_selection(sel, shape):
    """ checks if hyperslab is unlimited

    :param sel: hyperslab selection
    :type sel: :class:`filewriter.FTHyperslab`
    :param shape: give shape
    :type shape: :obj:`list`
    :returns: if hyperslab is unlimited list
    :rtype: :obj:`list` <:obj:`bool`>
    """
    res = None
    if isinstance(sel, tuple):
        res = []
        for sl in sel:
            if hasattr(sl, "stop"):
                res.append(True if sl.stop in [unlimited()] else False)
            elif hasattr(sl, "count"):
                res.append(True if sl.count in [unlimited()] else False)
            else:
                res.append(True if sl in [unlimited()] else False)

    elif hasattr(sel, "count"):
        res = []
        for ct in sel.count():
            res.append(True if ct in [unlimited()] else False)
    elif isinstance(sel, slice):
        res = [True if sel.stop in [unlimited()] else False]

    elif sel in [unlimited()]:
        res = [True]
    lsh = len(shape)
    lct = len(res)
    ln = max(lsh, lct)
    if res and any(t is True for t in res):
        offset = [0 for _ in range(ln)]
        block = [1 for _ in range(ln)]
        stride = [1 for _ in range(ln)]
        count = list(shape)
        while lct > len(count):
            count.append(1)
        for si in range(lct):
            if res[si]:
                count[si] = h5cpp.dataspace.UNLIMITED
        # print("Hyperslab1 %s %s %s %s" % (offset, block, count, stride))
        return h5cpp.dataspace.Hyperslab(
            offset=offset, block=block, count=count, stride=stride)
    else:
        return None


def _slice2selection(t, shape):
    """ converts slice(s) to selection

    :param t: slice tuple
    :type t: :obj:`tuple`
    :param shape: field shape
    :type shape: :obj:`list` < :obj:`int` >
    :returns: hyperslab selection
    :rtype: :class:`h5cpp.dataspace.Hyperslab`
    """
    if t is Ellipsis:
        return None
    elif isinstance(t, filewriter.FTHyperslab):
        offset = list(t.offset or [])
        block = list(t.block or [])
        count = list(t.count or [])
        stride = list(t.stride or [])
        for dm, sz in enumerate(shape):
            if len(offset) > dm:
                if offset[dm] is None:
                    offset[dm] = 0
            else:
                offset.append(0)
            if len(block) > dm:
                if block[dm] is None:
                    block[dm] = 1
            else:
                block.append(1)
            if len(count) > dm:
                if count[dm] is None:
                    count[dm] = sz
            else:
                count.append(sz)
            if len(stride) > dm:
                if stride[dm] is None:
                    stride[dm] = 1
            else:
                stride.append(1)
        # print("Hyperslab2 %s %s %s %s" % (offset, block, count, stride))
        return h5cpp.dataspace.Hyperslab(
            offset=offset, block=block, count=count, stride=stride)

    elif isinstance(t, slice):
        start = t.start or 0
        stop = t.stop or shape[0]
        if start < 0:
            start == shape[0] + start
        if stop < 0:
            stop == shape[0] + stop
        if t.step in [None, 1]:
            return h5cpp.dataspace.Hyperslab(
                offset=(start,), block=((stop - start),))
        else:
            return h5cpp.dataspace.Hyperslab(
                offset=(start,),
                count=int(math.ceil((stop - start) / float(t.step))),
                stride=(t.step,))
    elif isinstance(t, (int, long)) and shape and len(shape) > 1:
        offset = [0] * len(shape)
        offset[0] = t
        return h5cpp.dataspace.Hyperslab(
            offset=tuple(offset), block=tuple(shape))
    elif isinstance(t, (int, long)):
        return h5cpp.dataspace.Hyperslab(
            offset=(t,), block=(1,))
    elif isinstance(t, (list, tuple)):
        offset = []
        block = []
        count = []
        stride = []
        it = -1
        for tit, tel in enumerate(t):
            it += 1
            if isinstance(tel, (int, long)):
                if tel < 0:
                    offset.append(shape[it] + tel)
                else:
                    offset.append(tel)
                block.append(1)
                count.append(1)
                stride.append(1)
            elif isinstance(tel, slice):
                start = tel.start if tel.start is not None else 0
                stop = tel.stop if tel.stop is not None else shape[it]
                if start < 0:
                    start == shape[it] + start
                if stop < 0:
                    stop == shape[it] + stop
                if tel.step in [None, 1]:
                    offset.append(start)
                    block.append(stop - start)
                    count.append(1)
                    stride.append(1)
                else:
                    offset.append(start)
                    block.append(1)
                    count.append(
                        int(math.ceil(
                            (stop - start) / float(tel.step))))
                    stride.append(tel.step)
            elif tel is Ellipsis:
                esize = len(shape) - len(t) + 1
                for jt in range(esize):
                    offset.append(0)
                    block.append(shape[it])
                    count.append(1)
                    stride.append(1)
                    if jt < esize - 1:
                        it += 1
        # print("Hyperslab3 %s %s %s %s" % (offset, block, count, stride))
        if len(offset):
            return h5cpp.dataspace.Hyperslab(
                offset=offset, block=block, count=count, stride=stride)


pTh = {
    "long": h5cpp.datatype.Integer,
    "str": h5cpp.datatype.kVariableString,
    "unicode": h5cpp.datatype.kVariableString,
    "bool": h5cpp.datatype.kEBool,
    "int": h5cpp.datatype.kInt64,
    "int64": h5cpp.datatype.kInt64,
    "int32": h5cpp.datatype.kInt32,
    "int16": h5cpp.datatype.kInt16,
    "int8": h5cpp.datatype.kInt8,
    "uint": h5cpp.datatype.kInt64,
    "uint64": h5cpp.datatype.kUInt64,
    "uint32": h5cpp.datatype.kUInt32,
    "uint16": h5cpp.datatype.kUInt16,
    "uint8": h5cpp.datatype.kUInt8,
    "float": h5cpp.datatype.kFloat32,
    "float64": h5cpp.datatype.kFloat64,
    "float32": h5cpp.datatype.kFloat32,
    "string": h5cpp.datatype.kVariableString,
}


hTp = {
    h5cpp.datatype.Integer: "long",
    h5cpp.datatype.kVariableString: "string",
    h5cpp._datatype.Class.STRING: "string",
    h5cpp.datatype.kInt64: "int64",
    h5cpp.datatype.kInt32: "int32",
    h5cpp.datatype.kInt16: "int16",
    h5cpp.datatype.kInt8: "int8",
    h5cpp.datatype.kInt64: "uint",
    h5cpp.datatype.kUInt64: "uint64",
    h5cpp.datatype.kUInt32: "uint32",
    h5cpp.datatype.kUInt16: "uint16",
    h5cpp.datatype.kUInt8: "uint8",
    h5cpp.datatype.Float: "float",
    h5cpp.datatype.kFloat64: "float64",
    h5cpp.datatype.kFloat32: "float32",
}


def unlimited(parent=None):
    """ return dataspace UNLIMITED variable for the current writer module

    :param parent: parent object
    :type parent: :class:`FTObject`
    :returns:  dataspace UNLIMITED variable
    :rtype: :class:`h5cpp.dataspace.UNLIMITED`
    """
    return h5cpp.dataspace.UNLIMITED


def open_file(filename, readonly=False, libver=None, swmr=False):
    """ open the new file

    :param filename: file name
    :type filename: :obj:`str`
    :param readonly: readonly flag
    :type readonly: :obj:`bool`
    :param libver: library version: 'lastest' or 'earliest'
    :type libver: :obj:`str`
    :returns: file object
    :rtype: :class:`H5CppFile`
    """

    fapl = h5cpp.property.FileAccessList()
    # if hasattr(fapl, "set_close_degree"):
    #     fapl.set_close_degree(h5cpp._property.CloseDegree.STRONG)
    if readonly:
        flag = h5cpp.file.AccessFlags.READONLY
    else:
        flag = h5cpp.file.AccessFlags.READWRITE
    if swmr:
        if readonly:
            if hasattr(h5cpp.file.AccessFlags, "SWMRREAD"):
                flag = flag | h5cpp.file.AccessFlags.SWMRREAD
        else:
            if hasattr(h5cpp.file.AccessFlags, "SWMRWRITE"):
                flag = flag | h5cpp.file.AccessFlags.SWMRWRITE

    if libver is None or libver == 'lastest':
        fapl.library_version_bounds(
            h5cpp.property.LibVersion.LATEST,
            h5cpp.property.LibVersion.LATEST)
    return H5CppFile(h5cpp.file.open(filename, flag, fapl), filename)


def is_image_file_supported():
    """ provides if loading of image files are supported

    :retruns: if loading of image files are supported
    :rtype: :obj:`bool`
    """
    return hasattr(h5cpp.file, "from_buffer") and \
        hasattr(h5cpp.file, "ImageFlags")


def is_vds_supported():
    """ provides if vds are supported

    :retruns: if vds are supported
    :rtype: :obj:`bool`
    """
    return hasattr(h5cpp.property, "VirtualDataMaps") and \
        hasattr(h5cpp.property, "VirtualDataMaps")


def is_unlimited_vds_supported():
    """ provides if unlimited vds are supported

    :retruns: if unlimited vds are supported
    :rtype: :obj:`bool`
    """
    return is_vds_supported()


def load_file(membuffer, filename=None, readonly=False, **pars):
    """ load a file from memory byte buffer

    :param membuffer: memory buffer
    :type membuffer: :obj:`bytes` or :obj:`io.BytesIO`
    :param filename: file name
    :type filename: :obj:`str`
    :param readonly: readonly flag
    :type readonly: :obj:`bool`
    :param pars: parameters
    :type pars: :obj:`dict` < :obj:`str`, :obj:`str`>
    :returns: file object
    :rtype: :class:`H5PYFile`
    """
    if not is_image_file_supported():
        raise Exception(
            "Loading a file from a memory buffer not supported")
    if type(membuffer).__name__ == "ndarray":
        npdata = np.array(membuffer[:], dtype="uint8")
    else:
        if hasattr(membuffer, "getbuffer"):
            membuffer = membuffer.getbuffer()
        elif hasattr(membuffer, "getvalue"):
            membuffer = membuffer.getvalue()
        try:
            npdata = np.frombuffer(membuffer[:], dtype=np.uint8)
        except Exception:
            npdata = np.fromstring(membuffer[:], dtype=np.uint8)
    if readonly:
        flag = h5cpp.file.ImageFlags.READONLY
    else:
        flag = h5cpp.file.ImageFlags.READWRITE
    return H5CppFile(h5cpp.file.from_buffer(npdata, flag), filename)


def create_file(filename, overwrite=False, libver=None, swmr=None):
    """ create a new file

    :param filename: file name
    :type filename: :obj:`str`
    :param overwrite: overwrite flag
    :type overwrite: :obj:`bool`
    :param libver: library version: 'lastest' or 'earliest'
    :type libver: :obj:`str`
    :returns: file object
    :rtype: :class:`H5CppFile`
    """
    fcpl = h5cpp.property.FileCreationList()
    fapl = h5cpp.property.FileAccessList()
    # if hasattr(fapl, "set_close_degree"):
    #     fapl.set_close_degree(h5cpp._property.CloseDegree.STRONG)
    flag = h5cpp.file.AccessFlags.TRUNCATE if overwrite \
        else h5cpp.file.AccessFlags.EXCLUSIVE
    if libver is None or libver == 'lastest' or swmr:
        fapl.library_version_bounds(
            h5cpp.property.LibVersion.LATEST,
            h5cpp.property.LibVersion.LATEST)
    fl = h5cpp.file.create(filename, flag, fcpl, fapl)
    rt = fl.root()
    attrs = rt.attributes
    attrs.create("file_time", pTh["unicode"]).write(
        unicode(H5CppFile.currenttime()))
    hdf5ver = u""
    if hasattr(h5cpp, "current_library_version"):
        hdf5ver = h5cpp.current_library_version()
    attrs.create("HDF5_Version", pTh["unicode"]).write(hdf5ver)
    attrs.create("NX_class", pTh["unicode"]).write(u"NXroot")
    # attrs.create("NeXus_version", pTh["unicode"]).write(u"4.3.0")
    attrs.create("file_name", pTh["unicode"]).write(unicode(filename))
    attrs.create("file_update_time", pTh["unicode"]).write(
        unicode(H5CppFile.currenttime()))
    rt.close()
    return H5CppFile(fl, filename)


def link(target, parent, name):
    """ create link

    :param target: nexus path name
    :type target: :obj:`str`
    :param parent: parent object
    :type parent: :class:`FTObject`
    :param name: link name
    :type name: :obj:`str`
    :returns: link object
    :rtype: :class:`H5CppLink`
    """
    if ":/" in target:
        filename, path = target.split(":/")
    else:
        filename, path = None, target

    localfname = H5CppLink.getfilename(parent)
    if filename and \
       os.path.abspath(filename) != os.path.abspath(localfname):
        h5cpp.node.link(target_file=filename,
                        target=h5cpp.Path(path),
                        link_base=parent.h5object,
                        link_path=h5cpp.Path(name))
    else:
        h5cpp.node.link(target=h5cpp.Path(path),
                        link_base=parent.h5object,
                        link_path=h5cpp.Path(name))

    lks = parent.h5object.links
    lk = [e for e in lks if str(e.path.name) == name][0]
    el = H5CppLink(lk, parent)
    return el


def get_links(parent):
    """ get links

    :param parent: parent object
    :type parent: :class:`FTObject`
    :returns: list of link objects
    :returns: link object
    :rtype: :obj: `list` <:class:`H5CppLink`>
    """
    lks = parent.h5object.links
    links = [H5CppLink(e, parent) for e in lks]
    return links


def data_filter(filterid=None, name=None, options=None, availability=None,
                shuffle=None, rate=None):
    """ create data filter

    :param filterid: hdf5 filter id
    :type filterid: :obj:`int`
    :param name: filter name
    :type name: :obj:`str`
    :param options: filter cd values
    :type options: :obj:`tuple` <:obj:`int`>
    :param availability: filter availability i.e. 'optional' or 'mandatory'
    :type availability: :obj:`str`
    :param shuffle: filter shuffle
    :type shuffle: :obj:`bool`
    :param rate: filter shuffle
    :type rate: :obj:`bool`
    :returns: data filter object
    :rtype: :class:`H5CppDataFilter`
    """
    dtf = H5CppDataFilter()
    if filterid:
        dtf.filterid = filterid
    if name:
        dtf.name = name
    if shuffle:
        dtf.shuffle = shuffle
    if rate:
        dtf.rate = rate
    if options:
        dtf.options = options
    if availability:
        dtf.availability = availability
    return dtf


def deflate_filter(rate=None, shuffle=None, availability=None):
    """ create data filter

    :param rate: filter shuffle
    :type rate: :obj:`bool`
    :param shuffle: filter shuffle
    :type shuffle: :obj:`bool`
    :returns: deflate filter object
    :rtype: :class:`H5CppDataFilter`
    """
    dtf = H5CppDataFilter()
    dtf.filterid = 1
    dtf.name = "deflate"
    if shuffle:
        dtf.shuffle = shuffle
    dtf.rate = rate or 2
    if availability:
        dtf.availability = availability
    return dtf


def target_field_view(filename, fieldpath, shape,
                      dtype=None, maxshape=None):
    """ create target field view for VDS

    :param filename: file name
    :type filename: :obj:`str`
    :param fieldpath: nexus field path
    :type fieldpath: :obj:`str`
    :param shape: shape
    :type shape: :obj:`list` < :obj:`int` >
    :param dtype: attribute type
    :type dtype: :obj:`str`
    :param maxshape: shape
    :type maxshape: :obj:`list` < :obj:`int` >
    :returns: target field view object
    :rtype: :class:`H5CppTargetFieldView`
    """
    return H5CppTargetFieldView(
        filename, fieldpath, shape, dtype, maxshape)


def virtual_field_layout(shape, dtype, maxshape=None, parent=None):
    """ creates a virtual field layout for a VDS file

    :param shape: shape
    :type shape: :obj:`list` < :obj:`int` >
    :param dtype: attribute type
    :type dtype: :obj:`str`
    :param maxshape: shape
    :type maxshape: :obj:`list` < :obj:`int` >
    :returns: virtual layout
    :rtype: :class:`H5CppVirtualFieldLayout`
    """
    if not is_vds_supported():
        raise Exception("VDS not supported")
    return H5CppVirtualFieldLayout(
        h5cpp.property.VirtualDataMaps(),
        shape, dtype, maxshape, parent)


class H5CppFile(filewriter.FTFile):

    """ file tree file
    """

    def __init__(self, h5object, filename):
        """ constructor

        :param h5object: h5 object
        :type h5object: :obj:`any`
        :param filename:  file name
        :type filename: :obj:`str`
        """
        filewriter.FTFile.__init__(self, h5object, filename)
        #: (:obj:`str`) object nexus path
        self.path = None
        #: (:obj:`str`) nexus file name
        self.filename = filename
        if hasattr(h5object, "path"):
            self.path = h5object.path

    def root(self):
        """ root object

        :returns: parent object
        :rtype: :class:`H5CppGroup`
        """
        return H5CppGroup(self._h5object.root(), self)

    def flush(self):
        """ flash the data
        """
        self._h5object.flush()

    def close(self):
        """ close file
        """
        filewriter.FTFile.close(self)
        if self._h5object.is_valid:
            self._h5object.close()

    @property
    def is_valid(self):
        """ check if file is valid

        :returns: valid flag
        :rtype: :obj:`bool`
        """
        return self._h5object.is_valid

    @property
    def readonly(self):
        """ check if file is readonly

        :returns: readonly flag
        :rtype: :obj:`bool`
        """
        try:
            flag = self._h5object.intent == h5cpp.file.AccessFlags.READONLY
            if not flag and hasattr(h5cpp.file.AccessFlags, "SWMRREAD"):
                return self._h5object.intent == \
                    h5cpp.file.AccessFlags.READONLY \
                    | h5cpp.file.AccessFlags.SWMRREAD
            else:
                return flag
        except Exception:
            return None

    def reopen(self, readonly=False, swmr=False, libver=None):
        """ reopen file

        :param readonly: readonly flag
        :type readonly: :obj:`bool`
        :param swmr: swmr flag
        :type swmr: :obj:`bool`
        :param libver:  library version, default: 'latest'
        :type libver: :obj:`str`
        """

        fapl = h5cpp.property.FileAccessList()
        # if hasattr(fapl, "set_close_degree"):
        #     fapl.set_close_degree(h5cpp._property.CloseDegree.STRONG)
        if libver is None or libver == 'lastest' or swmr:
            fapl.library_version_bounds(
                h5cpp.property.LibVersion.LATEST,
                h5cpp.property.LibVersion.LATEST)

        if swmr:
            if not hasattr(h5cpp.file.AccessFlags, "SWMRWRITE"):
                raise Exception("SWMR not supported")
            if not readonly:
                flag = h5cpp.file.AccessFlags.READWRITE \
                       | h5cpp.file.AccessFlags.SWMRWRITE
            else:
                flag = h5cpp.file.AccessFlags.READONLY \
                       | h5cpp.file.AccessFlags.SWMRREAD

        elif readonly:
            flag = h5cpp.file.AccessFlags.READONLY
        else:
            flag = h5cpp.file.AccessFlags.READWRITE
        if self.is_valid:
            self.close()
        self._h5object = h5cpp.file.open(self.name, flag, fapl)
        filewriter.FTFile.reopen(self)


class H5CppGroup(filewriter.FTGroup):

    """ file tree group
    """

    def __init__(self, h5object, tparent=None):
        """ constructor

        :param h5object: h5 object
        :type h5object: :obj:`any`
        :param tparent: tree parent
        :type tparent: :obj:`FTObject`
        """
        self._ancache = None
        self._avcache_lock = threading.Lock()
        self._avcache = {}

        filewriter.FTGroup.__init__(self, h5object, tparent)
        #: (:obj:`str`) object nexus path
        self.path = u""
        #: (:obj:`str`) object name
        self.name = None
        if hasattr(h5object, "link"):
            self.name = h5object.link.path.name
            if tparent and tparent.path:
                if hasattr(tparent, "root"):
                    if self.name == ".":
                        self.path = u"/"
                    else:
                        self.path = u"/" + self.name
                else:
                    if tparent.path.endswith("/"):
                        self.path = tparent.path
                    else:
                        self.path = tparent.path + u"/"
                    self.path += self.name
            if ":" not in self.name:
                # print("GROUP", type(self), self.name)
                if u"NX_class" in self.attributes._names():
                    clss = filewriter.first(
                        h5object.attributes["NX_class"]).read()
                else:
                    clss = ""
                if clss:
                    if isinstance(clss, (list, np.ndarray)):
                        clss = clss[0]
                if clss and clss != 'NXroot':
                    self.path += u":" + str(clss)

    def set_attr_value(self, name, value):
        """ set device parameters

        :param name: attribute name
        :type name: :obj:`str`
        :param value: attribute value
        :type value: :obj:`any`
        """
        with self._avcache_lock:
            self._avcache[name] = value
            if self._ancache is None:
                names = self.attributes.names()
                self._ancache = set(names)
            self._ancache.add(name)

    def get_attr_value(self, name):
        """ get scan info parameters

        :param name: attribute name
        :type name: :obj:`str`
        :returns value: attribute value
        :rtype value: :obj:`any`
        """
        with self._avcache_lock:
            # print(self._avcache)
            vl = self._avcache.get(name, None)
        return vl

    def get_attr_names(self):
        """ get scan info parameters

        :returns value: attribute value
        :rtype value: :obj:`any`
        """
        with self._avcache_lock:
            if self._ancache is not None:
                names = self._ancache
            else:
                names = self.attributes.names()
                self._ancache = set(names)
        return names

    def remove_attr_names(self, name):
        """ remove the attribute name

        :param name: attribute name
        :type name: :obj:`str`
        """
        with self._avcache_lock:
            if self._ancache is not None and name in self._ancache:
                self._ancache.pop(name)

    def add_attr_name(self, name):
        """ add the attribute name

        :param name: attribute name
        :type name: :obj:`str`
        """
        with self._avcache_lock:
            if self._ancache is not None and name not in self._ancache:
                self._ancache.add(name)

    def open(self, name):
        """ open a file tree element

        :param name: element name
        :type name: :obj:`str`
        :returns: file tree object
        :rtype: :class:`FTObject`
        """
        try:
            if self._h5object.has_group(h5cpp.Path(name)):
                # print("OPEN", name, type(self._tparent))
                return H5CppGroup(
                    self._h5object.get_group(h5cpp.Path(name)), self)
            elif self._h5object.has_dataset(h5cpp.Path(name)):
                return H5CppField(
                    self._h5object.get_dataset(h5cpp.Path(name)), self)
            elif self._h5object.attributes.exists(name):
                return H5CppAttribute(self._h5object.attributes[name], self)
            else:
                return H5CppLink(
                    [lk for lk in self._h5object.links
                     if lk.path.name == name][0], self)

        except Exception as e:
            print(str(e))
            return H5CppLink(
                [lk for lk in self._h5object.links
                 if lk.path.name == name][0], self)

    def open_link(self, name):
        """ open a file tree element as link

        :param name: element name
        :type name: :obj:`str`
        :returns: file tree object
        :rtype: :class:`FTObject`
        """
        return H5CppLink(
            [lk for lk in self._h5object.links
             if lk.path.name == name][0], self)

    def create_group(self, n, nxclass=None):
        """ open a file tree element

        :param n: group name
        :type n: :obj:`str`
        :param nxclass: group type
        :type nxclass: :obj:`str`
        :returns: file tree group
        :rtype: :class:`H5CppGroup`
        """
        gr = h5cpp.node.Group(self._h5object, n)
        if nxclass is not None:
            gr.attributes.create(
                "NX_class", pTh["unicode"]).write(unicode(nxclass))
        return H5CppGroup(gr, self)

    def create_virtual_field(self, name, layout, fillvalue=0):
        """ creates a virtual filed tres element

        :param name: field name
        :type name: :obj:`str`
        :param layout: virual field layout
        :type layout: :class:`H5CppFieldLayout`
        :param fillvalue:  fill value
        :type fillvalue: :obj:`int` or :class:`np.ndarray`
        """
        if not is_vds_supported():
            raise Exception("VDS not supported")
        dcpl = h5cpp.property.DatasetCreationList()
        if fillvalue is not None:
            if hasattr(dcpl, "set_fill_value"):
                if isinstance(fillvalue, np.ndarray):
                    dcpl.set_fill_value(
                        fillvalue[0], pTh[str(fillvalue.dtype)])
                else:
                    dcpl.set_fill_value(
                        fillvalue, pTh[_tostr(layout.dtype)])
            else:
                raise Exception("VDS fill_value not supported")

        shape = layout.shape or [1]
        dataspace = h5cpp.dataspace.Simple(
            tuple(shape),
            tuple([h5cpp.dataspace.UNLIMITED] * len(shape)))
        vf = h5cpp.node.VirtualDataset(
            self._h5object, h5cpp.Path(name),
            pTh[_tostr(layout.dtype)], dataspace,
            layout._h5object, dcpl=dcpl)
        return H5CppField(vf, self)

    def create_field(self, name, type_code,
                     shape=None, chunk=None, dfilter=None):
        """ open a file tree element

        :param n: group name
        :type n: :obj:`str`
        :param type_code: nexus field type
        :type type_code: :obj:`str`
        :param shape: shape
        :type shape: :obj:`list` < :obj:`int` >
        :param chunk: chunk
        :type chunk: :obj:`list` < :obj:`int` >
        :param dfilter: filter deflater
        :type dfilter: :class:`H5CppDataFilter`
        :returns: file tree field
        :rtype: :class:`H5CppField`
        """
        dcpl = h5cpp.property.DatasetCreationList()
        if type_code in ["str", "unicode", "string"] and \
           shape is None and chunk is None:
            dataspace = h5cpp.dataspace.Scalar()
            return H5CppField(h5cpp.node.Dataset(
                self._h5object, h5cpp.Path(name),
                pTh[_tostr(type_code)], dataspace,
                dcpl=dcpl), self)
        else:
            shape = shape or [1]
            dataspace = h5cpp.dataspace.Simple(
                tuple(shape), tuple([h5cpp.dataspace.UNLIMITED] * len(shape)))
            if dfilter:
                if not isinstance(dfilter, list):
                    dfilter = [dfilter]
                for dfl in dfilter:
                    if dfl.shuffle:
                        sfilter = h5cpp.filter.Shuffle()
                        sfilter(dcpl)
                    h5object = None
                    if dfl.rate:
                        h5object = h5cpp.filter.Deflate(dfl.rate)
                    elif dfl.filterid > 0:
                        h5object = h5cpp.filter.ExternalFilter(
                            dfl.filterid, list(dfl.options), dfl.name)
                    elif dfl.name == "shuffle":
                        h5object = h5cpp.filter.Shuffle()
                    elif dfl.name == "deflate":
                        if dfl.options:
                            dfl.rate = dfl.options[0]
                        h5object = h5cpp.filter.Deflate(dfl.rate)
                    elif dfl.name == "nbit":
                        h5object = h5cpp.filter.NBit()
                    elif dfl.name == "fletcher32":
                        h5object = h5cpp.filter.Fletcher32()
                    elif dfl.name == "szip":
                        h5object = h5cpp.filter.SZip()
                        if dfl.options:
                            h5object.option_mask = dfl.options[0]
                        if len(dfl.options) > 1:
                            h5object.pixel_per_block = dfl.options[1]
                    elif dfl.name == "scaleoffset":
                        h5object = h5cpp.filter.ScaleOffset()
                        if dfl.options:
                            h5object.scale_type = dfl.options[0]
                        if len(dfl.options) > 1:
                            h5object.scale_factor = dfl.options[1]

                    if h5object:
                        if dfl.availability:
                            h5object(dcpl)
                        elif dfl.availability == "optional":
                            h5object(dcpl, h5cpp.filter.Availability.OPTIONALY)
                        else:
                            h5object(dcpl, h5cpp.filter.Availability.MANDATORY)
            if chunk is None and shape is not None:
                chunk = [(dm if dm != 0 else 1) for dm in shape]
            dcpl.layout = h5cpp.property.DatasetLayout.CHUNKED
            dcpl.chunk = tuple(chunk)
            return H5CppField(h5cpp.node.Dataset(
                self._h5object, h5cpp.Path(name),
                pTh[_tostr(type_code)], dataspace,
                dcpl=dcpl), self)

    @property
    def size(self):
        """ group size

        :returns: group size
        :rtype: :obj:`int`
        """
        return self._h5object.links.size

    @property
    def attributes(self):
        """ return the attribute manager

        :returns: attribute manager
        :rtype: :class:`H5CppAttributeManager`
        """
        return H5CppAttributeManager(self._h5object.attributes, self)

    def close(self):
        """ close group
        """
        filewriter.FTGroup.close(self)

        if self._h5object.is_valid:
            self._h5object.close()

    def reopen(self):
        """ reopen group
        """
        if hasattr(self._tparent.h5object, "root"):
            self._h5object = self._tparent.h5object.root()
        else:
            try:
                self._h5object = self._tparent.h5object.get_group(
                    h5cpp.Path(self.name))
            except Exception as e:
                print(str(e))
                self._h5object = [lk for lk in self._tparent.h5object.links
                                  if lk.path.name == self.name][0]
        filewriter.FTGroup.reopen(self)

    def exists(self, name):
        """ if child exists

        :param name: child name
        :type name: :obj:`str`
        :returns: existing flag
        :rtype: :obj:`bool`
        """
        return name in [
            lk.path.name for lk in self._h5object.links]

    def names(self):
        """ read the child names

        :returns: h5 object
        :rtype: :obj:`list` <`str`>
        """
        return [
            lk.path.name for lk in self._h5object.links]

    class H5CppGroupIter(object):

        def __init__(self, group):
            """ constructor

            :param group: group object
            :type manager: :obj:`H5CppGroup`
            """

            self.__group = group
            self.__names = group.names()

        def __next__(self):
            """ the next attribute

            :returns: attribute object
            :rtype: :class:`FTAtribute`
            """
            if self.__names:
                return self.__group.open(self.__names.pop(0))
            else:
                raise StopIteration()

        next = __next__

        def __iter__(self):
            """ attribute iterator

            :returns: attribute iterator
            :rtype: :class:`H5CppAttrIter`
            """
            return self

    def __iter__(self):
        """ attribute iterator

        :returns: attribute iterator
        :rtype: :class:`H5CppAttrIter`
        """
        return self.H5CppGroupIter(self)

    @property
    def is_valid(self):
        """ check if field is valid

        :returns: valid flag
        :rtype: :obj:`bool`
        """
        return self._h5object.is_valid


class H5CppField(filewriter.FTField):

    """ file tree file
    """

    def __init__(self, h5object, tparent=None):
        """ constructor

        :param h5object: h5 object
        :type h5object: :obj:`any`
        :param tparent: tree parent
        :type tparent: :obj:`FTObject`
        """
        self._ancache = None
        self._avcache_lock = threading.Lock()
        self._avcache = {}
        filewriter.FTField.__init__(self, h5object, tparent)
        #: (:obj:`str`) object nexus path
        self.path = ''
        #: (:obj:`str`) object name
        self.name = None
        if hasattr(h5object, "link"):
            self.name = h5object.link.path.name
            if tparent and tparent.path:
                if tparent.path == "/":
                    self.path = "/" + self.name
                else:
                    self.path = tparent.path + "/" + self.name
        #: (:obj:`bool`) bool flag
        # self.boolflag = False

    def set_attr_value(self, name, value):
        """ set device parameters

        :param name: attribute name
        :type name: :obj:`str`
        :param value: attribute value
        :type value: :obj:`any`
        """
        with self._avcache_lock:
            self._avcache[name] = value
            if self._ancache is None:
                names = self.attributes.names()
                self._ancache = set(names)
            self._ancache.add(name)

    def get_attr_names(self):
        """ get scan info parameters

        :returns value: attribute value
        :rtype value: :obj:`any`
        """
        with self._avcache_lock:
            if self._ancache is not None:
                names = self._ancache
            else:
                names = self.attributes.names()
                self._ancache = set(names)
        return names

    def remove_attr_names(self, name):
        """ remove the attribute

        :param name: attribute name
        :type name: :obj:`str`
        """
        with self._avcache_lock:
            if self._ancache is not None and name in self._ancache:
                self._ancache.pop()

    def add_attr_name(self, name):
        """ add the attribute name

        :param name: attribute name
        :type name: :obj:`str`
        """
        with self._avcache_lock:
            if self._ancache is not None and name not in self._ancache:
                self._ancache.add(name)

    def get_attr_value(self, name):
        """ get scan info parameters

        :param name: attribute name
        :type name: :obj:`str`
        :returns value: attribute value
        :rtype value: :obj:`any`
        """
        with self._avcache_lock:
            vl = self._avcache.get(name, None)
        return vl

    @property
    def attributes(self):
        """ return the attribute manager

        :returns: attribute manager
        :rtype: :class:`H5CppAttributeManager`
        """
        return H5CppAttributeManager(self._h5object.attributes, self)

    def close(self):
        """ close field
        """
        filewriter.FTField.close(self)
        if self._h5object.is_valid:
            self._h5object.close()

    def reopen(self):
        """ reopen field
        """
        try:
            self._h5object = self._tparent.h5object.get_dataset(
                h5cpp.Path(self.name))
        except Exception:
            self._h5object = [lk for lk in self._tparent.h5object.links
                              if lk.path.name == self.name][0]

        filewriter.FTField.reopen(self)

    def refresh(self):
        """ refresh the field

        :returns: refreshed
        :rtype: :obj:`bool`
        """
        self._h5object.refresh()
        return True

    def grow(self, dim=0, ext=1):
        """ grow the field

        :param dim: growing dimension
        :type dim: :obj:`int`
        :param dim: size of the grow
        :type dim: :obj:`int`
        """
        if self._h5object.dataspace.type != h5cpp.dataspace.Type.SCALAR:
            self._h5object.extent(dim, ext)

    def read(self):
        """ read the field value

        :returns: h5 object
        :rtype: :obj:`any`
        """
        if self.dtype in ['string', b'string']:
            # workaround for bug: h5cpp #355
            if self.size == 0:
                if self.shape:
                    v = np.empty(shape=self.shape,
                                 dtype=nptype(self.dtype))
                else:
                    v = []
            else:
                v = self._h5object.read()
            try:
                v = v.decode('UTF-8')
            except Exception:
                pass
        else:
            v = self._h5object.read()
        return v

    def write(self, o):
        """ write the field value

        :param o: h5 object
        :type o: :obj:`any`
        """
        self._h5object.write(o)

    def __setitem__(self, t, o):
        """ set value

        :param t: slice tuple
        :type t: :obj:`tuple`
        :param o: h5 object
        :type o: :obj:`any`
        """
        if self.shape == (1,) and t == 0:
            return self._h5object.write(o)
        selection = _slice2selection(t, self.shape)
        if selection is None:
            self._h5object.write(o)
        else:
            self._h5object.write(o, selection=selection)

    def __getitem__(self, t):
        """ get value

        :param t: slice tuple
        :type t: :obj:`tuple`
        :returns: h5 object
        :rtype: :obj:`any`
        """
        if self.shape == (1,) and t == 0:
            if self.dtype in ['string', b'string']:
                # workaround for bug: h5cpp #355
                if self.size == 0:
                    if self.shape:
                        v = np.empty(shape=self.shape, dtype=self.dtype)
                    else:
                        v = []
                else:
                    v = self._h5object.read()
            else:
                v = self._h5object.read()

        selection = _slice2selection(t, self.shape)
        if selection is None:
            if self.dtype in ['string', b'string']:
                # workaround for bug: h5cpp #355
                if self.size == 0:
                    if self.shape:
                        v = np.empty(shape=self.shape, dtype=self.dtype)
                    else:
                        v = []
                else:
                    v = self._h5object.read()
                try:
                    v = v.decode('UTF-8')
                except Exception:
                    pass
            else:
                v = self._h5object.read()
            return v
        v = self._h5object.read(selection=selection)
        # if hasattr(v, "shape") and hasattr(v, "reshape"):
        #     shape = [sh for sh in v.shape if sh != 1]
        #     if shape != list(v.shape):
        #         v.reshape(shape)
        if hasattr(v, "shape"):
            shape = v.shape
            if len(shape) == 3 and shape[2] == 1:
                #: problem with old numpy
                # v.reshape(shape[:2])
                v = v[:, :, 0]
                shape = v.shape
            if len(shape) == 3 and shape[1] == 1:
                # v.reshape([shape[0], shape[2]])
                v = v[:, 0, :]
                shape = v.shape
            if len(shape) == 3 and shape[0] == 1:
                # v.reshape([shape[1], shape[2]])
                v = v[0, :, :]
                shape = v.shape
            if len(shape) == 2 and shape[1] == 1:
                # v.reshape([shape[0]]]
                v = v[0, :]
                shape = v.shape
            if len(shape) == 2 and shape[0] == 1:
                # v.reshape([shape[1]])
                v = v[:, 0]
                shape = v.shape
            if len(shape) == 1 and shape[0] == 1:
                v = v[0]
        if self.dtype in ['string', b'string']:
            try:
                v = v.decode('UTF-8')
            except Exception:
                pass
        return v

    @property
    def is_valid(self):
        """ check if field is valid

        :returns: valid flag
        :rtype: :obj:`bool`
        """
        return self._h5object.is_valid

    @property
    def dtype(self):
        """ field data type

        :returns: field data type
        :rtype: :obj:`str`
        """
        # if self.boolflag:
        #     return "bool"
        datatype = self._h5object.datatype
        dtype = datatype.type
        dsize = datatype.size
        if str(dtype) == "FLOAT":
            if dsize == 8:
                return "float64"
            elif dsize == 4:
                return "float32"
            elif dsize == 16:
                return "float128"
            else:
                return "float"
        elif str(dtype) == "INTEGER":

            if dsize == 8:
                if datatype.is_signed():
                    return "int64"
                else:
                    return "uint64"
            elif dsize == 4:
                if datatype.is_signed():
                    return "int32"
                else:
                    return "uint32"
            elif dsize == 2:
                if datatype.is_signed():
                    return "int16"
                else:
                    return "uint16"
            elif dsize == 1:
                if datatype.is_signed():
                    return "int8"
                else:
                    return "uint8"
            elif dsize == 16:
                if datatype.is_signed():
                    return "int128"
                else:
                    return "uint128"
            else:
                return "int"
        elif str(dtype) == "ENUM":
            if h5cpp._datatype.is_bool(
                    h5cpp.datatype.Enum(datatype)):
                return "bool"
            else:
                return "int"

        return hTp[dtype]
#

    @property
    def shape(self):
        """ field shape

        :returns: field shape
        :rtype: :obj:`list` < :obj:`int` >
        """
        if hasattr(self._h5object.dataspace, "current_dimensions"):
            return self._h5object.dataspace.current_dimensions
        if self._h5object.dataspace.type == h5cpp.dataspace.Type.SCALAR:
            return ()
        else:
            return (1,)

    @property
    def size(self):
        """ field size

        :returns: field size
        :rtype: :obj:`int`
        """
        return self._h5object.dataspace.size


class H5CppLink(filewriter.FTLink):

    """ file tree link
    """

    def __init__(self, h5object, tparent=None):
        """ constructor

        :param h5object: h5 object
        :type h5object: :obj:`any`
        :param tparent: tree parent
        :type tparent: :obj:`FTObject`
        """
        self._avcache_lock = threading.Lock()
        self._avcache = {}
        self._ancache = None
        filewriter.FTLink.__init__(self, h5object, tparent)
        #: (:obj:`str`) object nexus path
        self.path = ''
        #: (:obj:`str`) object name
        self.name = None
        if tparent and tparent.path:
            self.path = tparent.path
        if not self.path.endswith("/"):
            self.path += "/"
        self.name = h5object.path.name
        self.path += self.name

    def set_attr_value(self, name, value):
        """ set device parameters

        :param name: attribute name
        :type name: :obj:`str`
        :param value: attribute value
        :type value: :obj:`any`
        """
        with self._avcache_lock:
            self._avcache[name] = value
            if self._ancache is None:
                names = self.attributes.names()
                self._ancache = set(names)
            self._ancache.add(name)

    def get_attr_names(self):
        """ get scan info parameters

        :returns value: attribute value
        :rtype value: :obj:`any`
        """
        with self._avcache_lock:
            if self._ancache is not None:
                names = self._ancache
            else:
                names = self.attributes.names()
                self._ancache = set(names)
        return names

    def get_attr_value(self, name):
        """ get scan info parameters

        :param name: attribute name
        :type name: :obj:`str`
        :returns value: attribute value
        :rtype value: :obj:`any`
        """
        with self._avcache_lock:
            vl = self._avcache.get(name, None)
        return vl

    def add_attr_name(self, name):
        """ add the attribute name

        :param name: attribute name
        :type name: :obj:`str`
        """
        with self._avcache_lock:
            if self._ancache is not None and name not in self._ancache:
                self._ancache.add(name)

    def remove_attr_names(self, name):
        """ remove the attribute

        :param name: attribute name
        :type name: :obj:`str`
        """
        with self._avcache_lock:
            if self._ancache is not None and name in self._ancache:
                self._ancache.pop()
        if hasattr(self._tparent, "remove_attr_name"):
            self._tparent.remove_attr_name(name)

    @property
    def is_valid(self):
        """ check if link is valid

        :returns: valid flag
        :rtype: :obj:`bool`
        """
        try:
            return self._h5object.node.is_valid
        except Exception:
            return False

    def refresh(self):
        """ refresh the field

        :returns: refreshed
        :rtype: :obj:`bool`
        """
        self._h5object.refresh()
        return True

    @classmethod
    def getfilename(cls, obj):
        """ provides a filename from h5 node

        :param obj: h5 node
        :type obj: :class:`FTObject`
        :returns: file name
        :rtype: :obj:`str`
        """
        filename = ""
        while not filename:
            par = obj.parent
            if par is None:
                break
            if hasattr(par, "filename"):
                filename = par.filename
                break
            else:
                obj = par
        return filename

    @property
    def target_path(self):
        """ target path

        :returns: target path
        :rtype: :obj:`str`
        """
        fpath = self._h5object.target().file_path
        opath = self._h5object.target().object_path
        if not fpath:
            fpath = self.getfilename(self)
        return "%s:/%s" % (fpath, opath)

    def reopen(self):
        """ reopen field
        """
        lks = self._tparent.h5object.links
        try:
            lk = [e for e in lks
                  if e.path.name == self.name][0]
            self._h5object = lk
        except Exception:
            self._h5object = None
        filewriter.FTLink.reopen(self)

    def close(self):
        """ close group
        """
        filewriter.FTLink.close(self)
        self._h5object = None


class H5CppDataFilter(filewriter.FTDataFilter):

    """ file tree deflate
    """


class H5CppVirtualFieldLayout(filewriter.FTVirtualFieldLayout):

    """ virtual field layout """

    def __init__(self, h5object, shape, dtype=None, maxshape=None,
                 tparent=None):
        """ constructor

        :param h5object: h5 object
        :type h5object: :obj:`any`
        :param shape: shape
        :type shape: :obj:`list` < :obj:`int` >
        :param dtype: attribute type
        :type dtype: :obj:`str`
        :param maxshape: shape
        :type maxshape: :obj:`list` < :obj:`int` >
        """
        filewriter.FTVirtualFieldLayout.__init__(self, h5object, tparent)
        #: (:obj:`list` < :obj:`int` >) shape
        self.shape = shape
        # : (:obj:`str`): data type
        self.dtype = dtype
        #: (:obj:`list` < :obj:`int` >) maximal shape
        self.maxshape = maxshape
        #: (:obj:`list` <:obj:`dict` >) vmap list
        self.__vmaps = []

    @classmethod
    def cure_keys(cls, key):
        """ cure keys

        :param key: field key
        :type key: :class:`FTHyperslab` or :obj:`tuple` or :obj:`list`
        :  or :obj:`int`
        :returns: field key
        :rtype: :class:`FTHyperslab` or :obj:`tuple`
        """
        tkey = []
        if isinstance(key, list):
            try:
                sk = list(set([len(ky) for ky in key]))
            except Exception:
                sk = []
            if len(sk) == 1 and sk[0] == 4:
                offset, block, count, stride = map(list, zip(*key))
                return filewriter.FTHyperslab(offset, block, count, stride)
            for ky in key:
                if isinstance(ky, list) and len(ky) > 0 and len(ky) < 4:
                    tkey.append(slice(*ky))
                else:
                    if ky is None:
                        ky = slice(None)
                    tkey.append(ky)

            return tuple(tkey)
        return key

    @classmethod
    def cure_shape(cls, vmaps, shape):
        """ cure shape from virtual map elements

        :param vmaps: list of virtual map description
        :type vmaps: :obj:`list`<:obj:`dict`>
        :param shape: field shape
        :type shape: :obj:`list` < :obj:`int` >
        :returns: field shape
        :rtype: :obj:`list` < :obj:`int` >
        """
        # print("vmaps", vmaps, shape)
        if shape is None:
            return shape
        sizes = [(sh if sh > 1 else 0) for sh in shape]
        for vmap in vmaps:
            if "key" in vmap:
                key = vmap["key"]
                try:
                    sk = list(set([len(ky) for ky in key]))
                except Exception:
                    sk = []
                if len(sk) == 1 and sk[0] == 4:
                    offset, block, count, stride = map(list, zip(*key))
                    for si, sh in enumerate(shape):
                        if sh < 2:
                            if block[si] != unlimited() \
                                    and count[si] != unlimited() \
                                    and count[si] and block[si] and stride[si]:
                                sizes[si] = max(
                                    sizes[si],
                                    offset[si] + stride[si] * [count[si] - 1]
                                    + block[si])
                            else:
                                sizes[si] = max(
                                    sizes[si], 1)
                else:
                    eshape = vmap["shape"] if "shape" in vmap else []

                    for si, sh in enumerate(shape):
                        if sh < 2:
                            if isinstance(key, list) and len(key) > si \
                                    and isinstance(key[si], list) \
                                    and len(key[si]) > 0 and len(key[si]) < 4:
                                sky = slice(*key[si])
                                if sky.stop != unlimited():
                                    start = sky.start or 0
                                    stop = sky.stop or 0
                                    step = sky.step or 1
                                    size = (step * ((stop - start - 1) // step)
                                            + start) + 1
                                    sizes[si] = max(sizes[si], size)
                                else:
                                    if si:
                                        sizes[si] = max(
                                            sizes[si],
                                            eshape[si]
                                            if len(eshape) > si else 1, 1)
                                    else:
                                        sizes[si] += max(
                                            eshape[si]
                                            if len(eshape) > si else 1, 1)
                            else:
                                if si:
                                    sizes[si] = max(
                                        sizes[si],
                                        eshape[si]
                                        if len(eshape) > si else 1, 1)
                                else:
                                    sizes[si] += max(
                                        eshape[si]
                                        if len(eshape) > si else 1, 1)
            else:
                eshape = vmap["shape"] if "shape" in vmap else []
                for si, sh in enumerate(shape):
                    if sh < 2:
                        if si:
                            sizes[si] = max(
                                sizes[si],
                                eshape[si] if len(eshape) > si else 1, 1)
                        else:
                            sizes[si] += max(
                                eshape[si] if len(eshape) > si else 1, 1)
        return sizes

    def process_target_field_views(self, parent=None):
        """ process target fields views to virtual field layout

        :param parent: tree parent
        :type parent: :obj:`FTObject`
        """
        if self.__vmaps is not None:
            # print("SHAPE1", shape)
            self.shape = self.cure_shape(self.__vmaps, self.shape)
        # print("SHAPE12", shape)
        counter = 0
        for vmap in self.__vmaps:
            edtype = vmap["dtype"] \
                if "dtype" in vmap else self.dtype
            key = vmap["key"] if "key" in vmap else counter
            key = self.cure_keys(key)
            if "shape" in vmap:
                eshape = vmap["shape"]
            elif isinstance(key, int):
                eshape = list(self.shape)
                eshape[0] = 1
            else:
                eshape = [0] * len(self.shape)

            fieldpath = vmap["fieldpath"] \
                if "fieldpath" in vmap else "/data"
            filename = vmap["filename"] if "filename" in vmap else None
            if "target" in vmap:
                target = vmap["target"]
                if target.startswith("h5file:/"):
                    target = target[8:]
                if "::" in target:
                    filename, fieldpath = target.split("::")
                elif ":/" in target:
                    filename, fieldpath = target.split(":/")
                else:
                    fieldpath = target
            if self._tparent is None and parent is not None:
                self._tparent = parent
            obj = self._tparent
            while filename is None:
                par = obj.parent
                if par is None:
                    break
                if hasattr(par, "root") and hasattr(par, "name"):
                    filename = par.name
                    break
                else:
                    obj = par
            sourceshape = vmap["sourceshape"] \
                if "sourceshape" in vmap else None
            sourcekey = vmap["sourcekey"] \
                if "sourcekey" in vmap else None
            sourcekey = self.cure_keys(sourcekey)
            if not any(eshape):
                eshape = self.find_shape(key, eshape, change_unlimited=False)
            ef = target_field_view(
                filename, fieldpath, eshape, edtype)
            if eshape:
                counter += eshape[0]
            else:
                counter += 1
            # print("KEY", key, sourcekey, sourceshape, eshape)
            self.add(key, ef, sourcekey, sourceshape)

    @classmethod
    def find_shape(cls, key, eshape=None, change_unlimited=True):
        """ find a layout shape from elemnt keys and shape

        :param key: field key
        :type key: :class:`FTHyperslab` o :obj:`tuple`
        :param eshape: element shape
        :type eshape: ::obj:`list`
        :param change_unlimited: unlimited flag
        :type change_unlimited: ::obj:`bool`
        :returns: layout shape
        :rtype: :obj:`list` < :obj:`int` >
        """

        if isinstance(key, filewriter.FTHyperslab):
            if not change_unlimited:
                count = [(ct if ct != unlimited() else 1) for ct in key.count]
                block = [(ct if ct != unlimited() else 1) for ct in key.block]
            else:
                count = key.count
                block = key.block
            eshape = [bl * count[hi] for hi, bl in enumerate(block)]
        if isinstance(key, tuple):
            eshape = []
            for ky in key:
                if not change_unlimited and ky.stop == unlimited():
                    eshape.append(1)
                elif isinstance(ky, slice) and ky.stop > 0:
                    start = ky.start if ky.start is not None else 0
                    step = ky.step if ky.step is not None else 1
                    eshape.append((ky.stop - start) // step)
                else:
                    eshape.append(1)
        return eshape

    def __len__(self):
        """ provides virtual map list length
        :rtype: :obj:`int`
        :returns:  virtual map list length
        """
        # print("LEN", len(self.__vmaps))
        return (len(self.__vmaps))

    def append_vmap(self, vmap, strategy=None):
        """ appends virtual map description into vmap list

        :param vmap: virtual map description
        :type vmap: :obj:`dict`
        :param strategy: datasource strategy i.e. INIT or FINAL
        :type strategy: :obj:`str`
        """
        # print("APEEND", vmap)
        self.__vmaps.append(vmap)

    def __setitem__(self, key, source):
        """ add target field to layout

        :param key: slide
        :type key: :obj:`tuple`
        :param source: target field view
        :type source: :class:`H5PYTargetFieldView`
        """
        self.add(key, source)

    def add(self, key, source, sourcekey=None, shape=None):
        """ add target field to layout

        :param key: slide
        :type key: :obj:`tuple`
        :param source: target field view
        :type source: :class:`H5PYTargetFieldView`
        :param sourcekey: slide or selection
        :type sourcekey: :obj:`tuple`
        :param shape: target shape in the layout
        :type shape: :obj:`tuple`
        """
        if shape is None:
            shape = list(source.shape or [])
            if hasattr(key, "__len__"):
                size = len(key)
                while len(shape) < size:
                    shape.insert(0, 1)
        lds = h5cpp.dataspace.Simple(tuple(shape))
        selection = None
        if key is not None and key != filewriter.FTHyperslab():
            selection = _slice2selection(key, shape)
            lview = h5cpp.dataspace.View(lds, selection)
        else:
            lview = h5cpp.dataspace.View(lds)
        sds = h5cpp.dataspace.Simple(tuple(shape))
        if sourcekey is not None and sourcekey != filewriter.FTHyperslab():
            srcsel = _slice2selection(sourcekey, source.shape)
            eview = h5cpp.dataspace.View(sds, srcsel)
        elif selection is not None:
            usel = unlimited_selection(selection, shape)
            if usel is not None:
                eview = h5cpp.dataspace.View(sds, usel)
            else:
                eview = h5cpp.dataspace.View(sds)
        else:
            eview = h5cpp.dataspace.View(sds)
        fname = source.filename
        path = h5cpp.Path(source.fieldpath)
        self._h5object.add(h5cpp.property.VirtualDataMap(
            lview, str(fname), path, eview))


class H5CppTargetFieldView(filewriter.FTTargetFieldView):

    """ target field for VDS """

    def __init__(self, filename, fieldpath, shape, dtype=None, maxshape=None):
        """ constructor

        :param filename: file name
        :type filename: :obj:`str`
        :param fieldpath: nexus field path
        :type fieldpath: :obj:`str`
        :param shape: shape
        :type shape: :obj:`list` < :obj:`int` >
        :param dtype: attribute type
        :type dtype: :obj:`str`
        :param maxshape: shape
        :type maxshape: :obj:`list` < :obj:`int` >
        """
        filewriter.FTTargetFieldView.__init__(self, None)
        #: (:obj:`str`) directory and file name
        self.filename = filename
        #: (:obj:`str`) nexus field path
        self.fieldpath = fieldpath
        #: (:obj:`list` < :obj:`int` >) shape
        self.shape = shape
        # : (:obj:`str`): data type
        self.dtype = dtype
        #: (:obj:`list` < :obj:`int` >) maximal shape
        self.maxshape = maxshape


class H5CppDeflate(H5CppDataFilter):

    """ deflate filter """


class H5CppAttributeManager(filewriter.FTAttributeManager):

    """ file tree attribute
    """

    def __init__(self, h5object, tparent=None):
        """ constructor

        :param h5object: h5 object
        :type h5object: :obj:`any`
        :param tparent: tree parent
        :type tparent: :obj:`FTObject`
        """
        filewriter.FTAttributeManager.__init__(self, h5object, tparent)
        #: (:obj:`str`) object nexus path
        self.path = ''
        #: (:obj:`str`) object name
        self.name = None

    def remove(self, name):
        """ remove the attribute

        :param name: attribute name
        :type name: :obj:`str`
        """
        # print("remove", name)
        try:
            self._h5object.remove(name)
        except Exception:
            pass
        if hasattr(self._tparent, "remove_attr_name"):
            self._tparent.remove_attr_name(name)

    def set_attr_value(self, name, value):
        """ set device parameters

        :param name: attribute name
        :type name: :obj:`str`
        :param value: attribute value
        :type value: :obj:`any`
        """
        if hasattr(self._tparent, "set_attr_value"):
            return self._tparent.set_attr_value(name, value)

    def names(self):
        """ key values

        :returns: attribute names
        :rtype: :obj:`list` <:obj:`str`>
        """
        return [att.name for att in self._h5object]

    def _names(self):
        """ key values

        :returns: attribute names
        :rtype: :obj:`list` <:obj:`str`>
        """
        # print("NAMESS")
        if hasattr(self._tparent, "get_attr_names"):
            return self._tparent.get_attr_names()
        return self.names()

    def create(self, name, dtype, shape=None, overwrite=False):
        """ create a new attribute

        :param name: attribute name
        :type name: :obj:`str`
        :param dtype: attribute type
        :type dtype: :obj:`str`
        :param shape: attribute shape
        :type shape: :obj:`list` < :obj:`int` >
        :param overwrite: overwrite flag
        :type overwrite: :obj:`bool`
        :returns: attribute object
        :rtype: :class:`H5CppAtribute`
        """

        at = None
        names = self._names()
        if name in names:
            if overwrite:
                try:
                    pass
                    if str(self[name].dtype) == _tostr(dtype) \
                       and self[name].shape == shape:
                        at = self._h5object[name]
                except Exception as e:
                    print(str(e))
                if at is None:
                    self.remove(name)
            else:
                raise Exception("Attribute %s exists" % name)
        shape = shape or []
        if shape:
            if at is None:
                at = self._h5object.create(name, pTh[_tostr(dtype)], shape)
                if hasattr(self._tparent, "add_attr_name"):
                    self._tparent.add_attr_name(name)
            if dtype in ['string', b'string']:
                emp = np.empty(shape, dtype="unicode")
                emp[:] = ''
                at.write(emp)
            else:
                at.write(np.zeros(shape, dtype=dtype))
        else:
            if at is None:
                at = self._h5object.create(name, pTh[_tostr(dtype)])
                if hasattr(self._tparent, "add_attr_name"):
                    self._tparent.add_attr_name(name)
            # if dtype in ['string', b'string']:
            #     at.write(np.array(u"", dtype="unicode"))
            # else:
            #     at.write(np.array(0, dtype=dtype))

        at = H5CppAttribute(at, self.parent)
        # if dtype == "bool":
        #     at.boolflag = True
        return at

    def __len__(self):
        """ number of attributes

        :returns: number of attributes
        :rtype: :obj:`int`
        """
        return self._h5object.__len__()

    def __getitem__(self, name):
        """ get value

        :param name: attribute name
        :type name: :obj:`str`
        :returns: attribute object
        :rtype: :class:`FTAtribute`
        """
        return H5CppAttribute(
            self._h5object.__getitem__(name), self.parent)

    def close(self):
        """ close attribure manager
        """
        filewriter.FTAttributeManager.close(self)

    def reopen(self):
        """ reopen field
        """
        self._h5object = self._tparent.h5object.attributes
        filewriter.FTAttributeManager.reopen(self)

    @property
    def is_valid(self):
        """ check if link is valid

        :returns: valid flag
        :rtype: :obj:`bool`
        """
        return self._tparent.h5object.is_valid


class H5CppAttribute(filewriter.FTAttribute):

    """ file tree attribute
    """

    def __init__(self, h5object, tparent=None):
        """ constructor

        :param h5object: h5 object
        :type h5object: :obj:`any`
        :param tparent: tree parent
        :type tparent: :obj:`FTObject`
        """
        filewriter.FTAttribute.__init__(self, h5object, tparent)
        #: (:obj:`str`) object name
        self.name = h5object.name
        #: (:obj:`str`) object nexus path
        self.path = tparent.path
        self.path += "@%s" % self.name

        #: (:obj:`bool`) bool flag
        # self.boolflag = False

    def close(self):
        """ close attribute
        """
        filewriter.FTAttribute.close(self)
        if self._h5object.is_valid:
            self._h5object.close()

    def read(self):
        """ read attribute value

        :returns: python object
        :rtype: :obj:`any`
        """
        vl = self._h5object.read()
        if self.dtype in ['string', b'string']:
            try:
                vl = vl.decode('UTF-8')
            except Exception:
                pass
        return vl

    def write(self, o):
        """ write attribute value

        :param o: python object
        :type o: :obj:`any`
        """
        self._h5object.write(o)

    def __setitem__(self, t, o):
        """ write attribute value

        :param t: slice tuple
        :type t: :obj:`tuple`
        :param o: python object
        :type o: :obj:`any`
        """
        if t is Ellipsis or t == slice(None, None, None) or \
           t == (slice(None, None, None), slice(None, None, None)) or \
           (hasattr(o, "__len__") and t == slice(0, len(o), None)):
            if self.dtype in ['string', b'string']:
                if isinstance(o, str):
                    self.write(unicode(o))
                else:
                    dtype = npunicode
                    self.write(np.array(o, dtype=dtype))
            else:
                self.write(np.array(o, dtype=self.dtype))
        elif isinstance(t, slice):
            var = self._h5object.read()
            if self.dtype not in ['string', b'string']:
                var[t] = np.array(o, dtype=nptype(self.dtype))
            else:
                dtype = npunicode
                var[t] = np.array(o, dtype=dtype)
                var = var.astype(dtype)
            try:
                self.write(var)
            except Exception:
                dtype = npunicode
                tvar = np.array(var, dtype=dtype)
                self.write(tvar)

        elif isinstance(t, tuple):
            var = self._h5object.read()
            if self.dtype not in ['string', b'string']:
                var[t] = np.array(o, dtype=nptype(self.dtype))
            else:
                dtype = npunicode
                if hasattr(var, "flatten"):
                    vv = var.flatten().tolist() + \
                        np.array(o, dtype=dtype).flatten().tolist()
                    nt = np.array(vv, dtype=dtype)
                    var = np.array(var, dtype=nt.dtype)
                    var[t] = np.array(o, dtype=dtype)
                elif hasattr(var, "tolist"):
                    var = var.tolist()
                    var[t] = np.array(o, dtype=self.dtype).tolist()
                else:
                    var[t] = np.array(o, dtype=self.dtype).tolist()
                var = var.astype(dtype)
            self.write(var)
        else:
            if isinstance(o, str) or isinstance(o, unicode):
                self.write(unicode(o))
            else:
                self.write(np.array(o, dtype=self.dtype))

    def __getitem__(self, t):
        """ read attribute value

        :param t: slice tuple
        :type t: :obj:`tuple`
        :returns: python object
        :rtype: :obj:`any`
        """
        v = self._h5object.__getitem__(t)
        # if hasattr(v, "shape") and hasattr(v, "reshape"):
        #     shape = [sh for sh in v.shape if sh != 1]
        #     if shape != list(v.shape):
        #         v.reshape(shape)
        if hasattr(v, "shape"):
            shape = v.shape
            if len(shape) == 3 and shape[2] == 1:
                #: problem with old numpy
                # v.reshape(shape[:2])
                v = v[:, :, 0]
                shape = v.shape
            if len(shape) == 3 and shape[1] == 1:
                # v.reshape([shape[0], shape[2]])
                v = v[:, 0, :]
                shape = v.shape
            if len(shape) == 3 and shape[0] == 1:
                # v.reshape([shape[1], shape[2]])
                v = v[0, :, :]
                shape = v.shape
            if len(shape) == 2 and shape[1] == 1:
                # v.reshape([shape[0]])
                v = v[0, :]
                shape = v.shape
            if len(shape) == 2 and shape[0] == 1:
                # v.reshape([shape[1]])
                v = v[:, 0]
                shape = v.shape
            if len(shape) == 1 and shape[0] == 1:
                v = v[0]
        if self.dtype in ['string', b'string']:
            try:
                v = v.decode('UTF-8')
            except Exception:
                pass
        return v

    @property
    def is_valid(self):
        """ check if attribute is valid

        :returns: valid flag
        :rtype: :obj:`bool`
        """
        return self._h5object.is_valid

    @property
    def dtype(self):
        """ field data type

        :returns: field data type
        :rtype: :obj:`str`
        """
        # if self.boolflag:
        #     return "bool"
        datatype = self._h5object.datatype
        dtype = datatype.type
        dsize = datatype.size
        if str(dtype) == "FLOAT":
            if dsize == 8:
                return "float64"
            elif dsize == 4:
                return "float32"
            elif dsize == 16:
                return "float128"
            else:
                return "float"
        elif str(dtype) == "INTEGER":
            if dsize == 8:
                if datatype.is_signed():
                    return "int64"
                else:
                    return "uint64"
            elif dsize == 4:
                if datatype.is_signed():
                    return "int32"
                else:
                    return "uint32"
            elif dsize == 2:
                if datatype.is_signed():
                    return "int16"
                else:
                    return "uint16"
            elif dsize == 1:
                if datatype.is_signed():
                    return "int8"
                else:
                    return "uint8"
            elif dsize == 16:
                if datatype.is_signed():
                    return "int128"
                else:
                    return "uint128"
            else:
                return "int"
        elif str(dtype) == "ENUM":
            if h5cpp._datatype.is_bool(
                    h5cpp.datatype.Enum(datatype)):
                return "bool"
            else:
                return "int"
        return hTp[dtype]

    @property
    def shape(self):
        """ attribute shape

        :returns: attribute shape
        :rtype: :obj:`list` < :obj:`int` >
        """
        if hasattr(self._h5object.dataspace, "current_dimensions"):
            return self._h5object.dataspace.current_dimensions
        if self._h5object.dataspace.type == h5cpp.dataspace.Type.SCALAR:
            return ()
        else:
            return (1,)

    def reopen(self):
        """ reopen attribute
        """
        self._h5object = self._tparent.h5object.attributes[self.name]
        filewriter.FTAttribute.reopen(self)

    def set_attr_value(self, name, value):
        """ set device parameters

        :param name: attribute name
        :type name: :obj:`str`
        :param value: attribute value
        :type value: :obj:`any`
        """
        if hasattr(self._tparent, "set_attr_value"):
            return self._tparent.set_attr_value(name, value)

    def get_attr_value(self, name):
        """ get scan info parameters

        :param name: attribute name
        :type name: :obj:`str`
        :returns value: attribute value
        :rtype value: :obj:`any`
        """
        if hasattr(self._tparent, "get_attr_value"):
            return self._tparent.get_attr_value(name)
