/**
 * Copyright - See the COPYRIGHT that is included with this distribution.
 * pvxs is distributed subject to a Software License Agreement found
 * in file LICENSE that is included with this distribution.
 */

#include <cstring>
#include <epicsAssert.h>

#include "dataimpl.h"
#include "utilpvt.h"

namespace pvxs {

NoField::NoField()
    :std::runtime_error ("No such field")
{}

NoField::~NoField() {}

NoConvert::~NoConvert() {}

LookupError::LookupError(const std::string& msg)
    :std::runtime_error(msg)
{}

LookupError::~LookupError() {}


std::shared_ptr<const impl::FieldDesc>
Value::Helper::type(const Value& v)
{
    if(v) {
        return std::shared_ptr<const impl::FieldDesc>(v.store->top->desc, v.desc);
    } else {
        return nullptr;
    }
}

Value Value::Helper::build(const void* ptr, StoreType type)
{
    TypeCode base{TypeCode::Null};
    switch (type) {
    case StoreType::Bool:     base = TypeCode::Bool; break;
    case StoreType::Integer:  base = TypeCode::Int64; break;
    case StoreType::UInteger: base = TypeCode::UInt64; break;
    case StoreType::Real:     base = TypeCode::Float64; break;
    case StoreType::String:   base = TypeCode::String; break;
    case StoreType::Array: {
        auto& arr = *static_cast<const shared_array<const void>*>(ptr);
        switch(arr.original_type()) {
#define CASE(TYPE) case ArrayType::TYPE: base = TypeCode::TYPE ## A; break
        CASE(Bool);
        CASE(Int8);
        CASE(Int16);
        CASE(Int32);
        CASE(Int64);
        CASE(UInt8);
        CASE(UInt16);
        CASE(UInt32);
        CASE(UInt64);
        CASE(Float32);
        CASE(Float64);
        CASE(String);
#undef CASE
        case ArrayType::Value:
            base = TypeCode::AnyA;
            break;
        case ArrayType::Null:
            throw std::logic_error("Unable to infer ArrayType::Null");
        }
    }
        break;
    case StoreType::Compound: {
        auto src = *reinterpret_cast<const Value*>(ptr);
        if(src) {
            auto dst = TypeDef(src).create();
            dst.assign(src);
            return dst;
        }
    }
        base = TypeCode::Any;
        break;
    case StoreType::Null:
        throw std::logic_error("Unable to infer ArrayType::Null");
    }

    Value ret(TypeDef(base).create());
    ret.copyIn(ptr, type);

    return ret;
}

Value::Value(const std::shared_ptr<const impl::FieldDesc>& desc)
    :desc(nullptr)
{
    if(!desc)
        return;

    auto top = std::make_shared<StructTop>();

    top->desc = desc;
    top->members.resize(desc->size());
    {
        auto& root = top->members[0];
        root.init(desc->code.storedAs());
        root.top = top.get();
    }

    if(desc->code==TypeCode::Struct) {
        for(auto& pair : desc->mlookup) {
            auto cfld = desc.get() + pair.second;
            auto& mem = top->members.at(pair.second);
            mem.top = top.get();
            mem.init(cfld->code.storedAs());
        }
    }

    this->desc = desc.get();
    decltype (store) val(top, top->members.data()); // alias
    this->store = std::move(val);
}

Value::Value(const std::shared_ptr<const impl::FieldDesc>& desc, Value& parent)
    :Value(desc)
{
    store->top->enclosing = parent.store;
}

Value::~Value() {}

Value Value::cloneEmpty() const
{
    Value ret;
    if(desc) {
        decltype (store->top->desc) fld(store->top->desc, desc);
        ret = Value(fld);
    }
    return ret;
}

Value Value::clone() const
{
    Value ret;
    if(desc) {
        decltype (store->top->desc) fld(store->top->desc, desc);
        ret = Value(fld);
        ret.assign(*this);
    }
    return ret;
}

Value& Value::assign(const Value& o)
{
    if(!store || !o.store)
        throw std::logic_error("Can't assign() to/from empty Value");

    if(type().kind()==Kind::Compound) {
        // pass through Struct and others w/ type
        copyIn(&o, StoreType::Compound);
    } else {
        // unpack other field types
        copyIn(o.store.get(), o.store->code);
    }
    return *this;
}

Value Value::allocMember()
{
    // allocate member type for Struct[] or Union[]
    if(!desc || (desc->code!=TypeCode::UnionA && desc->code!=TypeCode::StructA))
        throw std::runtime_error("allocMember() only meaningful for Struct[] or Union[]");

    decltype (store->top->desc) fld(store->top->desc, desc->members.data());
    return Value::Helper::build(fld, *this);
}

void Value::clear()
{
    if(!desc)
        return;

    for(auto i : range(size_t(0u), desc->size())) {
        auto& s = store.get()[i];
        s.valid = false;

        switch(s.code) {
        case StoreType::Array:
            s.as<shared_array<const void>>().clear();
            break;
        case StoreType::Compound:
        {
            auto& v = s.as<Value>();
            v.desc = nullptr;
            v.store.reset();
        }
            break;
        case StoreType::String:
            s.as<std::string>().clear();
            break;
        case StoreType::Null:
            break; // nothing to do
        case StoreType::Bool:
        case StoreType::UInteger:
        case StoreType::Integer:
        case StoreType::Real:
            memset(&s.store, 0, sizeof(s.store)); // just zero
            break;
        }
    }
}

bool Value::isMarked(bool parents, bool children) const
{
    if(!desc)
        return false;

    if(store->valid)
        return true;

    auto top = store->top;

    if(children && desc->size()>1u) {
        // TODO more efficient
        for(auto bit : range(desc->size()))
        {
            auto cstore = store.get() + bit;
            if(cstore->valid)
                return true;
        }
    }

    if(parents) {
        auto pdesc = desc;
        auto pstore = store.get();
        while(pdesc!=top->desc.get()) {
            pstore -= pdesc->parent_index;
            pdesc -= pdesc->parent_index;

            if(pstore->valid)
                return true;
        }
    }

    return false;
}

Value Value::ifMarked(bool parents, bool children) const
{
    Value ret;
    if(isMarked(parents, children))
        ret = *this;
    return ret;
}

void Value::mark(bool v)
{
    if(!desc)
        return;

    store->valid = v;
    if(!v)
        return;

    auto top = store->top;
    std::shared_ptr<FieldStorage> enc;
    while(top && (enc=top->enclosing.lock())) {
        enc->valid = true;
        top = enc->top;
    }
}

void Value::unmark(bool parents, bool children)
{
    if(!desc)
        return;

    store->valid = false;

    auto top = store->top;

    if(children && desc->size()>1u) {
        // TODO more efficient
        for(auto bit : range(desc->size()))
        {
            (store.get() + bit)->valid = false;
        }
    }

    if(parents) {
        auto pdesc = desc;
        auto pstore = store.get();
        while(pdesc!=top->desc.get()) {
            pdesc -= pdesc->parent_index;
            pstore -= pdesc->parent_index;

            pstore->valid = false;
        }
    }
}

TypeCode Value::type() const
{
    return desc ? desc->code : TypeCode::Null;
}

StoreType Value::storageType() const
{
    return store ? store->code : StoreType::Null;
}

const std::string& Value::id() const
{
    if(!desc)
        throw std::runtime_error("Null Value");
    return desc->id;
}

bool Value::idStartsWith(const std::string& prefix) const
{
    auto ID = this->id();
    return ID.size()>=prefix.size() && prefix==ID.substr(0u, prefix.size());
}

bool Value::_equal(const impl::FieldDesc* A, const impl::FieldDesc* B)
{
    if(A==B) {
        return true;

    } else if(!A ^ !B) {
        return false;

    } else if(!A) { // !A && !B
        return true;

    } else if(A->size()!=B->size()) {
        return false;
    }

    for(auto i : range(A->size())) {
        if(A[i].code!=B[i].code)
            return false;

        if(A[i].code==TypeCode::StructA || A[i].code==TypeCode::UnionA) {
            if(!_equal(&A[i].members[0], &B[i].members[0]))
                return false;

        } else if(A[i].code==TypeCode::Struct || A[i].code==TypeCode::Union) {
            auto it = A[i].mlookup.begin();
            auto end= A[i].mlookup.end();
            auto it2= B[i].mlookup.begin();

            for(;it!=end; ++it, ++it2) {
                if(it->first!=it2->first) {
                    return false; // different field name

                } else if(it->second!=it2->second) {
                    return false; // different field order

                } else if(A[i].code==TypeCode::Union) {
                    if(!_equal(&A[i].members[it->second], &B[i].members[it2->second]))
                        return false;

                } // else if A[i] is Struct, outer loop will reach members
            }
        }
    }

    return true;
}

const std::string &Value::nameOf(const Value& descendant) const
{
    if(!store || !descendant.store)
        throw NoField();

    size_t doffset;
    if(desc->code==TypeCode::Struct) {
        doffset = descendant.desc - desc;
        if(doffset==0 || doffset > desc->mlookup.size())
            throw std::logic_error("not a descendant");

    } else if(desc->code==TypeCode::Union) {
        doffset = descendant.desc - desc->members.data();

    } else {
        throw std::logic_error("nameOf() only implemented for Struct and Union");
    }

    // inefficient, but we don't keep a reverse mapping
    for(auto& it : desc->mlookup) {
        if(it.second == doffset)
            return it.first;
    }

    throw std::logic_error("missing descendant");
}

namespace {
// C-style cast between scalar storage types, and print to string (base 10)
template<typename Src>
bool copyOutScalar(const Src& src, void *ptr, StoreType type)
{
    switch(type) {
    case StoreType::Real:     *reinterpret_cast<double*>(ptr) = double(src); return true;
    case StoreType::Integer:  *reinterpret_cast<int64_t*>(ptr) = int64_t(src); return true;
    case StoreType::UInteger: *reinterpret_cast<uint64_t*>(ptr) = uint64_t(src); return true;
    case StoreType::Bool:     *reinterpret_cast<bool*>(ptr) = bool(src); return true;
    case StoreType::String:   *reinterpret_cast<std::string*>(ptr) = SB()<<src; return true;
    case StoreType::Null:
    case StoreType::Compound:
    case StoreType::Array:
        break;
    }
    return false;
}
}

void Value::copyOut(void *ptr, StoreType type) const
{
    if(!desc)
        throw NoField();

    switch(store->code) {
    case StoreType::Real:     if(copyOutScalar(store->as<double>(), ptr, type)) return; else break;
    case StoreType::Integer:  if(copyOutScalar(store->as<int64_t>(), ptr, type)) return; else break;
    case StoreType::UInteger: if(copyOutScalar(store->as<uint64_t>(), ptr, type)) return; else break;
    case StoreType::Bool: {
        auto& src = store->as<bool>();

        switch(type) {
        case StoreType::Bool:     *reinterpret_cast<bool*>(ptr) = src; return;
        case StoreType::Integer:
        case StoreType::UInteger: *reinterpret_cast<uint64_t*>(ptr) = src; return;
        case StoreType::Real:     *reinterpret_cast<double*>(ptr) = src; return;
        case StoreType::String:   *reinterpret_cast<std::string*>(ptr) = src ? "true" : "false"; return;
        default:
            break;
        }
        break;
    }
    case StoreType::String: {
        auto& src = store->as<std::string>();

        switch(type) {
        case StoreType::String: *reinterpret_cast<std::string*>(ptr) = src; return;
        case StoreType::Integer: {
            *reinterpret_cast<int64_t*>(ptr) = parseTo<int64_t>(src);
            return;
        }
        case StoreType::UInteger: {
            *reinterpret_cast<uint64_t*>(ptr) = parseTo<uint64_t>(src);
            return;
        }
        case StoreType::Real: {
            *reinterpret_cast<double*>(ptr) = parseTo<double>(src);
            return;
        }
        case StoreType::Bool: {
            if(src=="true") { *reinterpret_cast<bool*>(ptr) = true; return; }
            else if(src=="false") { *reinterpret_cast<bool*>(ptr) = false; return; }
        }
        default:
            break;
        }
        break;
    }
    case StoreType::Array: {
        auto& src = store->as<shared_array<const void>>();
        switch (type) {
        case StoreType::Array: *reinterpret_cast<shared_array<const void>*>(ptr) = src; return;
            // TODO: print array
            //       extract [0] as scalar?
        default:
            break;
        }
        break;
    }
    case StoreType::Compound: {
        auto& src = store->as<Value>();
        if(type==StoreType::Compound) {
            // extract Value
            *reinterpret_cast<Value*>(ptr) = src;
            return;

        } else if(src) {
            // automagic deref and delegate assign
            src.copyOut(ptr, type);
            return;

        }

        break;
    }
    case StoreType::Null:
        break;
    }

    throw NoConvert(SB()<<"Can't extract "<<this->type()<<" as "<<type);
}

bool Value::tryCopyOut(void *ptr, StoreType type) const
{
    try {
        copyOut(ptr, type);
        return true;
    }catch(NoField&){
        return false;
    }catch(NoConvert&){
        return false;
    }
}

namespace {

// C-style cast between scalar storage types, and print to string (base 10)
template<typename Dest>
bool copyInScalar(Dest& dest, const void *ptr, StoreType type)
{
    switch(type) {
    case StoreType::Real:     dest = Dest(*reinterpret_cast<const double*>(ptr)); return true;
    case StoreType::Integer:  dest = Dest(*reinterpret_cast<const int64_t*>(ptr)); return true;
    case StoreType::UInteger: dest = Dest(*reinterpret_cast<const uint64_t*>(ptr)); return true;
    case StoreType::Bool:     dest = Dest(*reinterpret_cast<const bool*>(ptr)); return true;
    case StoreType::String:   dest = parseTo<Dest>(*reinterpret_cast<const std::string*>(ptr)); return true;
    case StoreType::Null:
    case StoreType::Compound:
    case StoreType::Array:
        break;
    }
    return false;
}
}

void Value::copyIn(const void *ptr, StoreType type)
{
    // control flow should either throw NoField or NoConvert, or update 'store' and
    // reach the mark() at the end.

    if(!desc)
        throw NoField();

    switch(store->code) {
    case StoreType::Real: {
        if(!copyInScalar(store->as<double>(), ptr, type)) throw NoConvert(SB()<<"Unable to assign "<<desc->code<<" with "<<type);
        // truncate as if assigned to narrower type
        if(desc->code==TypeCode::Float32)
            store->as<double>() = float(store->as<double>());
        break;
    }
    case StoreType::Integer: {
        if(!copyInScalar(store->as<int64_t>(), ptr, type)) throw NoConvert(SB()<<"Unable to assign "<<desc->code<<" with "<<type);
        // truncate as if assigned to narrower type
        int64_t orig = store->as<int64_t>();
        switch(desc->code.code) {
        case TypeCode::Int8: orig = int8_t(orig); break;
        case TypeCode::Int16: orig = int16_t(orig); break;
        case TypeCode::Int32: orig = int32_t(orig); break;
        case TypeCode::Int64: orig = int64_t(orig); break;
        default: break;
        }
        store->as<int64_t>() = orig;
        break;
    }
    case StoreType::UInteger: {
        if(!copyInScalar(store->as<uint64_t>(), ptr, type)) throw NoConvert(SB()<<"Unable to assign "<<desc->code<<" with "<<type);
        // truncate as if assigned to narrower type
        int64_t orig = store->as<int64_t>();
        switch(desc->code.code) {
        case TypeCode::UInt8: orig = uint8_t(orig); break;
        case TypeCode::UInt16: orig = uint16_t(orig); break;
        case TypeCode::UInt32: orig = uint32_t(orig); break;
        case TypeCode::UInt64: orig = uint64_t(orig); break;
        default: break;
        }
        store->as<int64_t>() = orig;
        break;
    }
    case StoreType::Bool: {
        auto& dest = store->as<bool>();
        switch(type) {
        case StoreType::Bool:     dest = *reinterpret_cast<const bool*>(ptr); break;
        case StoreType::Integer:
        case StoreType::UInteger: dest = 0!=*reinterpret_cast<const uint64_t*>(ptr); break;
        //case StoreType::Real:  // TODO pick condition.  strict non-zero?  fabs()<0.5 ?
        case StoreType::String:
            if("true"==*reinterpret_cast<const std::string*>(ptr)) { dest = true; break; }
            else if("false"==*reinterpret_cast<const std::string*>(ptr)) { dest = false; break; }
            // fall through
        default:
            throw NoConvert(SB()<<"Unable to assign "<<desc->code<<" with "<<type);
        }
        break;
    }
    case StoreType::String: {
        auto& dest = store->as<std::string>();

        switch(type) {
        case StoreType::String:   dest = *reinterpret_cast<const std::string*>(ptr); break;
        case StoreType::Integer:  dest = SB()<<*reinterpret_cast<const int64_t*>(ptr); break;
        case StoreType::UInteger: dest = SB()<<*reinterpret_cast<const uint64_t*>(ptr); break;
        case StoreType::Real:     dest = SB()<<*reinterpret_cast<const double*>(ptr); break;
        case StoreType::Bool:     dest = (*reinterpret_cast<const bool*>(ptr)) ? "true" : "false"; break;
        default:
            throw NoConvert(SB()<<"Unable to assign "<<desc->code<<" with "<<type);
        }
        break;
    }
    case StoreType::Array: {
        auto& dest = store->as<shared_array<const void>>();
        switch (type) {
        case StoreType::Array: {
            auto& src = *reinterpret_cast<const shared_array<const void>*>(ptr);
            if(src.original_type()==ArrayType::Null || src.empty()) {
                // assignment from untyped or empty
                dest.clear();

            } else if(src.original_type()==ArrayType::Value && desc->code.kind()==Kind::Compound) {
                // assign array of Struct/Union/Any
                auto tsrc  = src.castTo<const Value>();

                if(desc->code!=TypeCode::AnyA) {
                    // enforce member type for Struct[] and Union[]
                    for(auto& val : tsrc) {
                        if(val.desc && val.desc!=desc->members.data()) {
                            throw NoConvert(SB()<<"Unable to assign "<<desc->code<<" with "<<type);
                        }
                    }
                }
                dest = src;

            } else if(src.original_type()!=ArrayType::Value && uint8_t(desc->code.code)==uint8_t(src.original_type())) {
                // assign array of scalar w/o convert
                dest = src;

            } else {
                // TODO: alloc and convert
                throw NoConvert(SB()<<"Unable to assign "<<desc->code<<" with "<<type);
            }
            break;
        }
        default:
            throw NoConvert(SB()<<"Unable to assign "<<desc->code<<" with "<<type);
        }
        break;
    }
    case StoreType::Compound:
        if(type==StoreType::Null) {
            store->as<Value>() = Value(); // unselect Union or Any
            break;

        } else if(desc->code==TypeCode::Any) {
            // assigning variant union.
            auto& val = store->as<Value>();
            if(type==StoreType::Compound) {
                val = *reinterpret_cast<const Value*>(ptr);
                break;

            } else {
                val = Value::Helper::build(ptr, type);
                break;
            }

        } else if(desc->code==TypeCode::Union) {
            auto& val = store->as<Value>();
            if(type==StoreType::Compound) {
                // assign union from Value.  (eg. during Value::clone())
                // select and assign
                auto& src = *reinterpret_cast<const Value*>(ptr);
                for(auto i : range(desc->miter.size())) {
                    auto idx(desc->miter[i].second);

                    if(src.desc!=&desc->members[idx])
                        continue;

                    std::shared_ptr<const FieldDesc> udesc(store->top->desc, &desc->members[idx]);
                    Value temp(udesc, *this);
                    temp.assign(src);
                    val = std::move(temp);
                    break;
                }
                if(!val)
                    throw NoConvert("Unsupported assignment to unselected union");
                break;

            } else if(!val) {
                // caller is attempting to assign a value to an unselected discriminating union.
                // attempt convenient, but inefficient auto-selection
                for(auto i : range(desc->miter.size())) {
                    auto idx(desc->miter[i].second);
                    std::shared_ptr<const FieldDesc> udesc(store->top->desc, &desc->members[idx]);
                    Value temp(udesc, *this);
                    try{
                        temp.copyIn(ptr, type);
                    }catch(NoConvert&){
                        continue;
                    }
                    val = std::move(temp);
                }
                if(!val)
                    throw NoConvert("Unsupported assignment to unselected union");
                break;

            } else {
                // union member already selected, auto-deref
                val.copyIn(ptr, type);
                break;
            }
        }
        throw NoConvert(SB()<<"Unable to assign "<<desc->code<<" with "<<type);
    case StoreType::Null:
        if(type==StoreType::Compound) {
            auto& src = *reinterpret_cast<const Value*>(ptr);
            if(src.type()==TypeCode::Struct) {
                // copy struct to struct
                // all marked source field may be mapped to destination fields

                for(const auto& sfld : src.imarked()) {
                    if(sfld.type()==TypeCode::Struct) {
                        // entire sub-struct marked.

                        if(auto dfld = (*this)[src.nameOf(sfld)]) {
                            dfld.mark();
                        }

                    } else {
                        auto& name(src.nameOf(sfld));
                        if(auto dfld = (*this)[name]) {
                            try {
                                dfld.copyIn(&sfld.store->store, sfld.store->code);
                            }catch(NoConvert& e){
                                throw NoConvert(SB()<<"field \""<<name<<"\" : "<<e.what());
                            }
                        } else {
                            throw NoField();
                        }
                    }
                }
                if(src.isMarked())
                    mark();

                return;
            }
        }
        throw NoConvert(SB()<<"Unable to assign "<<desc->code<<" with "<<type);
    }

    mark();
}

bool Value::tryCopyIn(const void *ptr, StoreType type)
{
    try {
        copyIn(ptr, type);
        return true;
    }catch(NoField&){
        return false;
    }catch(NoConvert&){
        return false;
    }
}

void Value::traverse(const std::string &expr, bool modify, bool dothrow)
{
    size_t pos=0;
    bool maybedot = false;

    while(desc && pos<expr.size()) {
        if(expr[pos]=='<') {
            // attempt traverse to parent
            if(desc!=store->top->desc.get())
            {
                auto pdesc = desc - desc->parent_index;
                std::shared_ptr<FieldStorage> pstore(store, store.get() - desc->parent_index);
                store = std::move(pstore);
                desc = pdesc;
                pos++;
                continue;
            } else {
                // at top
                store.reset();
                desc = nullptr;
                if(dothrow)
                    throw LookupError(SB()<<"Can't traverse to parent of root with '"<<expr<<"'");
                break;
            }

        }

        if(desc->code.code==TypeCode::Struct) {
            // attempt traverse to member.
            // expect: [0-9a-zA-Z_.]+[\[-$]

            // skip a leading dot
            if(maybedot) {
                if(expr[pos]!='.') {
                    store.reset();
                    desc = nullptr;
                    if(dothrow)
                        throw LookupError(SB()<<"expected '.' at "<<pos<<" in '"<<expr<<"'");
                    break;
                }
                maybedot = false;
                pos++;
            }

            size_t sep = expr.find_first_of("<[-", pos);

            decltype (desc->mlookup)::const_iterator it;

            const auto& name = expr.substr(pos, sep-pos);

            if(sep>0 && (it=desc->mlookup.find(name))!=desc->mlookup.end()) {
                // found it
                auto next = desc+it->second;
                decltype(store) value(store, store.get()+it->second);
                store = std::move(value);
                desc = next;
                pos = sep;

            } else {
                // no such member
                store.reset();
                desc = nullptr;
                if(dothrow)
                    throw LookupError(SB()<<"no such member '"<<name<<"' in '"<<expr<<"'");
            }

        } else if(desc->code.code==TypeCode::Union || desc->code.code==TypeCode::Any) {
            // attempt to traverse to (and maybe select) member
            // expect: ->[0-9a-zA-Z_]+[.\[-$]

            maybedot = false;

            if(expr.size()-pos >= 2 && expr[pos]=='-' && expr[pos+1]=='>') {
                pos += 2; // skip past "->"

                if(desc->code.code==TypeCode::Any) {
                    // select member of Any (may be Null)
                    *this = store->as<Value>();

                } else {
                    // select member of Union
                    size_t sep = expr.find_first_of("<[-.", pos);

                    decltype (desc->mlookup)::const_iterator it;
                    auto& fld = store->as<Value>();

                    if(sep>0 && (it=desc->mlookup.find(expr.substr(pos, sep-pos)))!=desc->mlookup.end()) {
                        // found it.

                        if(modify || fld.desc==&desc->members[it->second]) {
                            // will select, or already selected
                            if(fld.desc!=&desc->members[it->second]) {
                                // select
                                std::shared_ptr<const FieldDesc> mtype(store->top->desc, &desc->members[it->second]);
                                fld = Value(mtype, *this);
                            }
                            pos = sep;
                            *this = fld;
                            maybedot = true;

                        } else {
                            // traversing const Value, can't select Union
                            store.reset();
                            desc = nullptr;
                            if(dothrow)
                                throw LookupError(SB()<<"traversing const Value, can't select Union in '"<<expr<<"'");
                        }

                    } else if(fld.desc) {
                        // deref selected
                        *this = fld;

                    } else {
                        store.reset();
                        desc = nullptr;
                        if(dothrow)
                            throw LookupError(SB()<<"can't deref. empty Union '"<<expr<<"'");
                    }
                }
            } else {
                // expected "->"
                store.reset();
                desc = nullptr;
                if(dothrow)
                    throw LookupError(SB()<<"expected -> in '"<<expr<<"'");
            }

        } else if(desc->code.isarray() && desc->code.kind()==Kind::Compound) {
            // attempt to traverse into array of Struct, Union, or Any
            // expect: \[[0-9]+\]

            maybedot = false;

            size_t sep = expr.find_first_of(']', pos);

            if(expr[pos]=='['
                    && sep!=std::string::npos && sep-pos>=2)
            {
                auto index = parseTo<uint64_t>(expr.substr(pos+1, sep-1-pos));
                auto& varr = store->as<shared_array<const void>>();
                shared_array<const Value> arr;
                if((varr.original_type()==ArrayType::Value)
                        && index < (arr = varr.castTo<const Value>()).size())
                {
                    *this = arr[index];
                    pos = sep+1;
                    maybedot = true;

                } else {
                    // wrong element type or out of range
                    store.reset();
                    desc = nullptr;
                    if(dothrow)
                        throw std::runtime_error(SB()<<"wrong element type or out of range in '"<<expr<<"'");
                }

            } else {
                // syntax error
                store.reset();
                desc = nullptr;
                if(dothrow)
                    throw std::runtime_error(SB()<<"indexing syntax error in '"<<expr<<"'");
            }

        } else {
            // syntax error or wrong field type (can't index scalar array)
            store.reset();
            desc = nullptr;
            if(dothrow)
                throw std::runtime_error(SB()<<"indexing syntax error or wrong field type (can't index scalar array) in '"<<expr<<"'");
        }
    }

    if(!desc && dothrow)
        throw NoField();
}

Value Value::operator[](const std::string& name)
{
    Value ret(*this);
    ret.traverse(name, true, false);
    return ret;
}

const Value Value::operator[](const std::string& name) const
{
    Value ret(*this);
    ret.traverse(name, false, false);
    return ret;
}

Value Value::lookup(const std::string& name)
{
    Value ret(*this);
    ret.traverse(name, true, true);
    return ret;
}

const Value Value::lookup(const std::string& name) const
{
    Value ret(*this);
    ret.traverse(name, false, true);
    return ret;
}

size_t Value::nmembers() const
{
    switch(desc ? desc->code.code : TypeCode::Null) {
    case TypeCode::Struct:
    case TypeCode::StructA:
    case TypeCode::Union:
    case TypeCode::UnionA:
        return desc->miter.size();
    default:
        return 0u;
    }
}

template<>
Value::Iterable<Value::_IAll>::iterator
Value::Iterable<Value::_IAll>::end() const noexcept
{
    iterator ret{val, 0u};

    if(val && val.type()==TypeCode::Struct) {
        ret.pos = val.desc->mlookup.size();

    } else if(val && val.type()==TypeCode::Union) {
        ret.pos = val.desc->miter.size();
    }
    return ret;
}


template<>
Value
Value::_Iterator<Value::_IAll>::operator*() const noexcept
{
    Value ret;

    if(val.type()==TypeCode::Struct) {
        decltype (ret.store) store(val.store, val.store.get() + 1u + pos);
        ret.store = std::move(store);
        ret.desc = val.desc + 1u + pos;

    } else if(val && val.type()==TypeCode::Union) {
        auto pos_desc = &val.desc->members[val.desc->miter[pos].second];

        if(val.store->as<Value>().desc==pos_desc) {
            // pointing to selected Union field
            ret = val.store->as<Value>();

        } else {
            std::shared_ptr<const FieldDesc> base(val.store, pos_desc);
            ret = Value(base);
        }
    }
    return ret;
}

template<>
Value::Iterable<Value::_IChildren>::iterator
Value::Iterable<Value::_IChildren>::end() const noexcept
{
    iterator ret{val, 0u};

    if(val && (val.type()==TypeCode::Struct || val.type()==TypeCode::Union)) {
        ret.pos = val.desc->miter.size();
    }
    return ret;
}

template<>
Value
Value::_Iterator<Value::_IChildren>::operator*() const noexcept
{
    auto offset = val.desc->miter[pos].second;
    Value ret;

    if(val.type()==TypeCode::Struct) {
        decltype (ret.store) store(val.store, val.store.get() + offset);
        ret.store = std::move(store);
        ret.desc = val.desc + offset;

    } else if(val && val.type()==TypeCode::Union) {
        auto pos_desc = &val.desc->members[val.desc->miter[pos].second];

        if(val.store->as<Value>().desc==pos_desc) {
            // pointing to selected Union field
            ret = val.store->as<Value>();

        } else {
            std::shared_ptr<const FieldDesc> base(val.store, pos_desc);
            ret = Value(base);
        }
    }
    return ret;
}

static
void _next_marked(const Value& ref, size_t& pos, size_t& nextcheck)
{
    if(pos < nextcheck)
        return;

    if(ref.type()==TypeCode::Struct) {
        auto base_desc = Value::Helper::desc(ref);

        while(pos < base_desc->mlookup.size()) {
            auto desc = base_desc + 1u + pos;
            auto S = Value::Helper::store_ptr(ref) + 1u + pos;
            if(S->valid) {
                nextcheck = pos + desc->size();
                return;
            }

            ++pos;
        }
        nextcheck = pos;

    } else if(ref.type()==TypeCode::Union) {
        auto desc = Value::Helper::desc(ref);

        if(pos >= desc->miter.size())
            return; // end of iteration

        const auto& val = Value::Helper::store_ptr(ref)->as<Value>();
        size_t sel_idx = Value::Helper::desc(val) - desc->members.data();
        size_t pos_idx = desc->miter[pos].second;

        if(!val || pos_idx > sel_idx) {
            // no field selected, or pos is after selection
            // end of iteration

            pos = desc->miter.size();

        } else if(pos_idx < sel_idx) {
            // before selected
            // jump forward to selection

            for(auto i : range(pos, desc->miter.size())) {
                if(desc->miter[i].second == sel_idx) {
                    pos = i;
                    return;
                }
            }
            assert(false); // corrupt iterator?
        }
    }
}

template<>
Value::Iterable<Value::_IMarked>::iterator
Value::Iterable<Value::_IMarked>::begin() const noexcept
{
    iterator ret{val, 0u};
    _next_marked(ret.val, ret.pos, ret.nextcheck);
    return ret;
}

template<>
Value::Iterable<Value::_IMarked>::iterator
Value::Iterable<Value::_IMarked>::end() const noexcept
{
    iterator ret{val, 0u};

    if(val && val.type()==TypeCode::Struct) {
        ret.pos = val.desc->mlookup.size();

    } else if(val && val.type()==TypeCode::Union) {
        ret.pos = val.desc->miter.size();
    }
    ret.nextcheck = ret.pos;
    return ret;
}

template<>
Value
Value::_Iterator<Value::_IMarked>::operator*() const noexcept
{
    Value ret;

    if(val.type()==TypeCode::Struct) {
        decltype (ret.store) store(val.store, val.store.get() + 1u + pos);
        ret.store = std::move(store);
        ret.desc = val.desc + 1u + pos;

    } else if(val && val.type()==TypeCode::Union) {
        auto pos_desc = &val.desc->members[val.desc->miter[pos].second];

        if(val.store->as<Value>().desc==pos_desc) {
            // pointing to selected Union field
            ret = val.store->as<Value>();

        } else {
            std::shared_ptr<const FieldDesc> base(val.store, pos_desc);
            ret = Value(base);
        }
    }
    return ret;
}

template<>
Value::_Iterator<Value::_IMarked>&
Value::_Iterator<Value::_IMarked>::operator++() noexcept
{
    pos++;
    _next_marked(val, pos, nextcheck);
    return *this;
}

namespace impl {

void FieldStorage::init(StoreType code)
{
    this->code = code;
    switch(code) {
    case StoreType::Null:
        return;
    case StoreType::Bool:
        as<bool>() = false;
        return;
    case StoreType::Integer:
    case StoreType::UInteger:
    case StoreType::Real:
        // just zero 8 bytes
        as<uint64_t>() = 0u;
        return;
    case StoreType::String:
        new(&store) std::string();
        return;
    case StoreType::Compound:
        new(&store) std::shared_ptr<FieldStorage>();
        return;
    case StoreType::Array:
        new(&store) shared_array<void>();
        return;
    }
    throw std::logic_error("FieldStore::init()");
}

void FieldStorage::deinit()
{
    switch(code) {
    case StoreType::Null:
    case StoreType::Integer:
    case StoreType::UInteger:
    case StoreType::Real:
    case StoreType::Bool:
             break;
    case StoreType::Array:
        as<shared_array<void>>().~shared_array();
        break;
    case StoreType::String:
        as<std::string>().~basic_string();
        break;
    case StoreType::Compound:
        as<Value>().~Value();
        break;
    default:
        throw std::logic_error("FieldStore::deinit()");
    }
    code = StoreType::Null;
}

FieldStorage::~FieldStorage()
{
    deinit();
}

size_t FieldStorage::index() const
{
    const size_t ret = this - top->members.data();
    return ret;
}

}} // namespace pvxs::impl
