package com.ctc.wstx.dtd;

import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamException;

import com.ctc.wstx.cfg.ErrorConsts;
import com.ctc.wstx.io.WstxInputData;
import com.ctc.wstx.sr.InputProblemReporter;
import com.ctc.wstx.util.ElementId;
import com.ctc.wstx.util.ElementIdMap;
import com.ctc.wstx.util.PrefixedName;

/**
 * Specific attribute class for attributes that contain (unique)
 * identifiers.
 */
public final class DTDIdAttr
    extends DTDAttribute
{
    /*
    ///////////////////////////////////////////////////
    // Life-cycle
    ///////////////////////////////////////////////////
     */

    /**
     * Main constructor. Note that id attributes can never have
     * default values.
     *<p>
     * note: although ID attributes are not to have default value,
     * this is 'only' a validity constraint, and in dtd-aware-but-
     * not-validating mode it is apparently 'legal' to add default
     * values.
     */
    public DTDIdAttr(PrefixedName name, DefaultAttrValue defValue, int specIndex,
                     boolean nsAware, boolean xml11)
    {
        super(name, defValue, specIndex, nsAware, xml11);
    }

    @Override
    public DTDAttribute cloneWith(int specIndex) {
        return new DTDIdAttr(mName, mDefValue, specIndex, mCfgNsAware, mCfgXml11);
    }

    /*
    ///////////////////////////////////////////////////
    // Public API
    ///////////////////////////////////////////////////
     */

    @Override
    public int getValueType() {
        return TYPE_ID;
    }

    @Override
    public boolean typeIsId() {
        return true;
    }

    /*
    ///////////////////////////////////////////////////
    // Public API, validation
    ///////////////////////////////////////////////////
     */

    /**
     * Method called by the validator
     * to let the attribute do necessary normalization and/or validation
     * for the value.
     */
    @SuppressWarnings("cast")
    @Override
    public String validate(DTDValidatorBase v, char[] cbuf, int start, int end, boolean normalize)
        throws XMLStreamException
    {
        // Let's trim leading white space first...
        while (start < end && WstxInputData.isSpaceChar(cbuf[start])) {
            ++start;
        }

        // No id?
        if (start >= end) {
            return reportValidationProblem(v, "Empty ID value");
        }
        --end; // so that it now points to the last char
        while (end > start && WstxInputData.isSpaceChar(cbuf[end])) {
            --end;
        }

        // Ok, need to check char validity, and also calc hash code:
        char c = cbuf[start];
        if (!WstxInputData.isNameStartChar(c, mCfgNsAware, mCfgXml11)) {
            return reportInvalidChar(v, c, "not valid as the first ID character");
        }
        int hash = (int) c;
        for (int i = start+1; i <= end; ++i) {
            c = cbuf[i];
            if (!WstxInputData.isNameChar(c, mCfgNsAware, mCfgXml11)) {
                return reportInvalidChar(v, c, "not valid as an ID character");
            }
            hash = (hash * 31) + (int) c;
        }

        // Either way, we do need to validate characters, and calculate hash
        ElementIdMap m = v.getIdMap();
        PrefixedName elemName = v.getElemName();
        Location loc = v.getLocation();
        ElementId id = m.addDefined(cbuf, start, (end - start + 1), hash,
                                    loc, elemName, mName);

        // We can detect dups by checking if Location is the one we passed:
        if (id.getLocation() != loc) {
            return reportValidationProblem(v, "Duplicate id '"+id.getId()+"', first declared at "
                                           +id.getLocation());
        }

        if (normalize) {
            return id.getId();
        }
        return null;
    }

    /**
     * Method called by the validator
     * to ask attribute to verify that the default it has (if any) is
     * valid for such type.
     */
    @Override
    public void validateDefault(InputProblemReporter rep, boolean normalize)
    {
        // Should never get called
        throw new IllegalStateException(ErrorConsts.ERR_INTERNAL);
    }
}
