/*
 * Style parent's are a bit different. We accept the following formats:
 *
 * @[[*]package:][style/]<entry>
 * ?[[*]package:]style/<entry>
 * <[*]package>:[style/]<entry>
 * [[*]package:style/]<entry>
 */
Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) {
    if (str.empty()) {
        return {};
    }

    StringPiece16 name = str;

    bool hasLeadingIdentifiers = false;
    bool privateRef = false;

    // Skip over these identifiers. A style's parent is a normal reference.
    if (name.data()[0] == u'@' || name.data()[0] == u'?') {
        hasLeadingIdentifiers = true;
        name = name.substr(1, name.size() - 1);
    }

    if (name.data()[0] == u'*') {
        privateRef = true;
        name = name.substr(1, name.size() - 1);
    }

    ResourceNameRef ref;
    ref.type = ResourceType::kStyle;

    StringPiece16 typeStr;
    extractResourceName(name, &ref.package, &typeStr, &ref.entry);
    if (!typeStr.empty()) {
        // If we have a type, make sure it is a Style.
        const ResourceType* parsedType = parseResourceType(typeStr);
        if (!parsedType || *parsedType != ResourceType::kStyle) {
            std::stringstream err;
            err << "invalid resource type '" << typeStr << "' for parent of style";
            *outError = err.str();
            return {};
        }
    }

    if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
        std::stringstream err;
        err << "invalid parent reference '" << str << "'";
        *outError = err.str();
        return {};
    }

    Reference result(ref);
    result.privateReference = privateRef;
    return result;
}
Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri) {
    if (stringStartsWith<char16_t>(namespaceUri, kSchemaPrefix)) {
        StringPiece16 schemaPrefix = kSchemaPrefix;
        StringPiece16 package = namespaceUri;
        return package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size())
                .toString();
    } else if (namespaceUri == kSchemaAuto) {
        return std::u16string();
    }
    return {};
}
std::u16string Pseudolocalizer::text(const StringPiece16& text) {
    std::u16string out;
    size_t depth = mLastDepth;
    size_t lastpos, pos;
    const size_t length = text.size();
    const char16_t* str = text.data();
    bool escaped = false;
    for (lastpos = pos = 0; pos < length; pos++) {
        char16_t c = str[pos];
        if (escaped) {
            escaped = false;
            continue;
        }
        if (c == '\'') {
            escaped = true;
            continue;
        }

        if (c == k_arg_start) {
            depth++;
        } else if (c == k_arg_end && depth) {
            depth--;
        }

        if (mLastDepth != depth || pos == length - 1) {
            bool pseudo = ((mLastDepth % 2) == 0);
            size_t nextpos = pos;
            if (!pseudo || depth == mLastDepth) {
                nextpos++;
            }
            size_t size = nextpos - lastpos;
            if (size) {
                std::u16string chunk = text.substr(lastpos, size).toString();
                if (pseudo) {
                    chunk = mImpl->text(chunk);
                } else if (str[lastpos] == k_arg_start && str[nextpos - 1] == k_arg_end) {
                    chunk = mImpl->placeholder(chunk);
                }
                out.append(chunk);
            }
            if (pseudo && depth < mLastDepth) { // End of message
                out.append(mImpl->end());
            } else if (!pseudo && depth > mLastDepth) { // Start of message
                out.append(mImpl->start());
            }
            lastpos = nextpos;
            mLastDepth = depth;
        }
    }
    return out;
}
bool BindingXmlPullParser::readExpressions() {
    mOverride = true;
    std::vector<XmlPullParser::Attribute> expressions;
    std::string idValue;

    const auto endAttrIter = mParser->endAttributes();
    for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) {
        if (attr->namespaceUri == kAndroidNamespaceUri && attr->name == u"id") {
            idValue = util::utf16ToUtf8(attr->value);
        } else {
            StringPiece16 value = util::trimWhitespace(attr->value);
            if (util::stringStartsWith<char16_t>(value, u"@{") &&
                    util::stringEndsWith<char16_t>(value, u"}")) {
                // This is attribute's value is an expression of the form
                // @{expression}. We need to capture the expression inside.
                expressions.push_back(XmlPullParser::Attribute{
                        attr->namespaceUri,
                        attr->name,
                        value.substr(2, value.size() - 3).toString()
                });
            } else {
                // This is a normal attribute, use as is.
                mAttributes.emplace_back(*attr);
            }
        }
    }

    // Check if we have any expressions.
    if (!expressions.empty()) {
        // We have expressions, so let's assign the target a tag number
        // and add it to our targets list.
        int32_t targetId = mNextTagId++;
        mTargets.push_back(Target{
                util::utf16ToUtf8(mParser->getElementName()),
                idValue,
                targetId,
                std::move(expressions)
        });

        std::stringstream numGen;
        numGen << kBindingTagPrefix << targetId;
        mAttributes.push_back(XmlPullParser::Attribute{
                std::u16string(kAndroidNamespaceUri),
                std::u16string(u"tag"),
                util::utf8ToUtf16(numGen.str())
        });
    }
    return true;
}
bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) {
    if (str.empty()) {
        return false;
    }

    size_t offset = 0;
    bool priv = false;
    if (str.data()[0] == u'*') {
        priv = true;
        offset = 1;
    }

    StringPiece16 package;
    StringPiece16 type;
    StringPiece16 entry;
    if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) {
        return false;
    }

    const ResourceType* parsedType = parseResourceType(type);
    if (!parsedType) {
        return false;
    }

    if (entry.empty()) {
        return false;
    }

    if (outRef) {
        outRef->package = package;
        outRef->type = *parsedType;
        outRef->entry = entry;
    }

    if (outPrivate) {
        *outPrivate = priv;
    }
    return true;
}