    import {
        Zondy
    } from '../common/Base';
    import {
        readHref
    } from './util/XLink';
    import XML from './util/XML';
    import {
        readDecimalString,
        readString,
        readNonNegativeInteger,
        readDecimal,
        readBooleanString,
        readNonNegativeIntegerString
    } from './util/xsd';
    import {
        makeArrayPusher,
        makeObjectPropertyPusher,
        makeObjectPropertySetter,
        makeStructureNS,
        pushParseAndPop
    } from './util/base/xml';

    /**
     * @private
     * @const NAMESPACE_URIS 命名空间标识符
     * @type {Array<null|string>}
     */
    const NAMESPACE_URIS = [
        null,
        'http://www.opengis.net/wms'
    ];

    /**
     * @private
     * @const PARSERS
     * @type {Object<string, Object<string>>}
     */
    const PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'Service': makeObjectPropertySetter(readService),
            'Capability': makeObjectPropertySetter(readCapability)
        });

    /**
     * @private
     * @const CAPABILITY_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const CAPABILITY_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'Request': makeObjectPropertySetter(readRequest),
            'Exception': makeObjectPropertySetter(readException),
            'Layer': makeObjectPropertySetter(readCapabilityLayer)
        });

    /**
     * @author 基础平台/产品2部 龚跃健
     * @class Zondy.Format.WMSCapabilities
     * @classdesc  用于读取WMS capabilities数据的类
     * @extends  Zondy.Format.XML
     * @example
     * var parser = new Zondy.Format.WMSCapabilities();
     * var result = parser.read(doc);
     */
    class WMSCapabilities extends XML {
        constructor() {
            super();

            /**
             * @private
             * @member Zondy.Format.WMSCapabilities.prototype.version
             * @type {string|undefined}
             * @description 版本号
             * @default undefined
             */
            this.version = undefined;
        }

        /**
         * @private
         * @function Zondy.Format.WMSCapabilities.prototype.readFromDocument
         * @description 从文档中读取信息，继承于父类XML
         * @return {wmsCapabilityObject|null}
         */
        readFromDocument(doc) {
            for (let n = doc.firstChild; n; n = n.nextSibling) {
                if (n.nodeType == Node.ELEMENT_NODE) {
                    return this.readFromNode(n);
                }
            }
            return null;
        }

        /**
         * @private
         * @function Zondy.Format.WMSCapabilities.prototype.readFromNode
         * @description 从文档节点中读取信息，继承于父类XML
         * @return {wmsCapabilityObject|null}
         */
        readFromNode(node) {
            this.version = node.getAttribute('version').trim();
            const wmsCapabilityObject = pushParseAndPop({
                'version': this.version
            }, PARSERS, node, []);
            return wmsCapabilityObject ? wmsCapabilityObject : null;
        }
    }

    /**
     * @private
     * @const SERVICE_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const SERVICE_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'Name': makeObjectPropertySetter(readString),
            'Title': makeObjectPropertySetter(readString),
            'Abstract': makeObjectPropertySetter(readString),
            'KeywordList': makeObjectPropertySetter(readKeywordList),
            'OnlineResource': makeObjectPropertySetter(readHref),
            'ContactInformation': makeObjectPropertySetter(readContactInformation),
            'Fees': makeObjectPropertySetter(readString),
            'AccessConstraints': makeObjectPropertySetter(readString),
            'LayerLimit': makeObjectPropertySetter(readNonNegativeInteger),
            'MaxWidth': makeObjectPropertySetter(readNonNegativeInteger),
            'MaxHeight': makeObjectPropertySetter(readNonNegativeInteger)
        });

    /**
     * @private
     * @const CONTACT_INFORMATION_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const CONTACT_INFORMATION_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'ContactPersonPrimary': makeObjectPropertySetter(readContactPersonPrimary),
            'ContactPosition': makeObjectPropertySetter(readString),
            'ContactAddress': makeObjectPropertySetter(readContactAddress),
            'ContactVoiceTelephone': makeObjectPropertySetter(readString),
            'ContactFacsimileTelephone': makeObjectPropertySetter(readString),
            'ContactElectronicMailAddress': makeObjectPropertySetter(readString)
        });

    /**
     * @private
     * @const CONTACT_PERSON_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const CONTACT_PERSON_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'ContactPerson': makeObjectPropertySetter(readString),
            'ContactOrganization': makeObjectPropertySetter(readString)
        });

    /**
     * @private
     * @const CONTACT_ADDRESS_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const CONTACT_ADDRESS_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'AddressType': makeObjectPropertySetter(readString),
            'Address': makeObjectPropertySetter(readString),
            'City': makeObjectPropertySetter(readString),
            'StateOrProvince': makeObjectPropertySetter(readString),
            'PostCode': makeObjectPropertySetter(readString),
            'Country': makeObjectPropertySetter(readString)
        });

    /**
     * @private
     * @const EXCEPTION_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const EXCEPTION_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'Format': makeArrayPusher(readString)
        });

    /**
     * @private
     * @const LAYER_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const LAYER_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'Name': makeObjectPropertySetter(readString),
            'Title': makeObjectPropertySetter(readString),
            'Abstract': makeObjectPropertySetter(readString),
            'KeywordList': makeObjectPropertySetter(readKeywordList),
            'SRS': makeObjectPropertyPusher(readString),
            'EX_GeographicBoundingBox': makeObjectPropertySetter(readEXGeographicBoundingBox),
            'BoundingBox': makeObjectPropertyPusher(readBoundingBox),
            'Dimension': makeObjectPropertyPusher(readDimension),
            'Attribution': makeObjectPropertySetter(readAttribution),
            'AuthorityURL': makeObjectPropertyPusher(readAuthorityURL),
            'Identifier': makeObjectPropertyPusher(readString),
            'MetadataURL': makeObjectPropertyPusher(readMetadataURL),
            'DataURL': makeObjectPropertyPusher(readFormatOnlineresource),
            'FeatureListURL': makeObjectPropertyPusher(readFormatOnlineresource),
            'Style': makeObjectPropertyPusher(readStyle),
            'MinScaleDenominator': makeObjectPropertySetter(readDecimal),
            'MaxScaleDenominator': makeObjectPropertySetter(readDecimal),
            'Layer': makeObjectPropertyPusher(readLayer)
        });

    /**
     * @private
     * @const ATTRIBUTION_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const ATTRIBUTION_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'Title': makeObjectPropertySetter(readString),
            'OnlineResource': makeObjectPropertySetter(readHref),
            'LogoURL': makeObjectPropertySetter(readSizedFormatOnlineresource)
        });

    /**
     * @private
     * @const EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS =
        makeStructureNS(NAMESPACE_URIS, {
            'westBoundLongitude': makeObjectPropertySetter(readDecimal),
            'eastBoundLongitude': makeObjectPropertySetter(readDecimal),
            'southBoundLatitude': makeObjectPropertySetter(readDecimal),
            'northBoundLatitude': makeObjectPropertySetter(readDecimal)
        });

    /**
     * @private
     * @const REQUEST_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const REQUEST_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'GetCapabilities': makeObjectPropertySetter(readOperationType),
            'GetMap': makeObjectPropertySetter(readOperationType),
            'GetFeatureInfo': makeObjectPropertySetter(readOperationType)
        });

    /**
     * @private
     * @const OPERATIONTYPE_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const OPERATIONTYPE_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'Format': makeObjectPropertyPusher(readString),
            'DCPType': makeObjectPropertyPusher(readDCPType)
        });

    /**
     * @private
     * @const DCPTYPE_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const DCPTYPE_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'HTTP': makeObjectPropertySetter(readHTTP)
        });

    /**
     * @private
     * @const HTTP_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const HTTP_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'Get': makeObjectPropertySetter(readFormatOnlineresource),
            'Post': makeObjectPropertySetter(readFormatOnlineresource)
        });

    /**
     * @private
     * @const STYLE_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const STYLE_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'Name': makeObjectPropertySetter(readString),
            'Title': makeObjectPropertySetter(readString),
            'Abstract': makeObjectPropertySetter(readString),
            'LegendURL': makeObjectPropertyPusher(readSizedFormatOnlineresource),
            'StyleSheetURL': makeObjectPropertySetter(readFormatOnlineresource),
            'StyleURL': makeObjectPropertySetter(readFormatOnlineresource)
        });

    /**
     * @private
     * @const FORMAT_ONLINERESOURCE_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const FORMAT_ONLINERESOURCE_PARSERS =
        makeStructureNS(NAMESPACE_URIS, {
            'Format': makeObjectPropertySetter(readString),
            'OnlineResource': makeObjectPropertySetter(readHref)
        });

    /**
     * @private
     * @const KEYWORDLIST_PARSERS
     * @type {Object<string, Object<string>>}
     */
    const KEYWORDLIST_PARSERS = makeStructureNS(
        NAMESPACE_URIS, {
            'Keyword': makeArrayPusher(readString)
        });

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readAttribution
     * @description 读取属性信息
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} 属性对象.
     */
    function readAttribution(node, objectStack) {
        return pushParseAndPop({}, ATTRIBUTION_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readBoundingBox
     * @description 读取地图坐标系信息，包括地理坐标系名，范围，分辨率
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object} 地图范围对象.
     */
    function readBoundingBox(node, objectStack) {
        const extent = [
            readDecimalString(node.getAttribute('minx')),
            readDecimalString(node.getAttribute('miny')),
            readDecimalString(node.getAttribute('maxx')),
            readDecimalString(node.getAttribute('maxy'))
        ];

        const resolutions = [
            readDecimalString(node.getAttribute('resx')),
            readDecimalString(node.getAttribute('resy'))
        ];

        return {
            'srs': node.getAttribute('SRS'),
            'extent': extent,
            'res': resolutions
        };
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readEXGeographicBoundingBox
     * @description 读取地理坐标范围
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object} 地理坐标范围对象.
     */
    function readEXGeographicBoundingBox(node, objectStack) {
        const geographicBoundingBox = pushParseAndPop({},
            EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS,
            node, objectStack);
        if (!geographicBoundingBox) {
            return undefined;
        }
        const westBoundLongitude = geographicBoundingBox['westBoundLongitude'];
        const southBoundLatitude = geographicBoundingBox['southBoundLatitude'];
        const eastBoundLongitude = geographicBoundingBox['eastBoundLongitude'];
        const northBoundLatitude = geographicBoundingBox['northBoundLatitude'];
        if (westBoundLongitude === undefined || southBoundLatitude === undefined ||
            eastBoundLongitude === undefined || northBoundLatitude === undefined) {
            return undefined;
        }
        return [
            westBoundLongitude, southBoundLatitude,
            eastBoundLongitude, northBoundLatitude
        ];
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readCapability
     * @description 读取Capability
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Capability对象.
     */
    function readCapability(node, objectStack) {
        return pushParseAndPop({}, CAPABILITY_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readService
     * @description 读取Service
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Service对象.
     */
    function readService(node, objectStack) {
        return pushParseAndPop({}, SERVICE_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readContactInformation
     * @description 读取ContactInformation
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Contact information 对象.
     */
    function readContactInformation(node, objectStack) {
        return pushParseAndPop({}, CONTACT_INFORMATION_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readContactPersonPrimary
     * @description 读取ContactPersonPrimary
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Contact person 对象.
     */
    function readContactPersonPrimary(node, objectStack) {
        return pushParseAndPop({}, CONTACT_PERSON_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readContactAddress
     * @description 读取ContactAddress
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Contact address对象.
     */
    function readContactAddress(node, objectStack) {
        return pushParseAndPop({}, CONTACT_ADDRESS_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readException
     * @description 读取Exception
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Array<string>|undefined} Format array.
     */
    function readException(node, objectStack) {
        return pushParseAndPop([], EXCEPTION_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readCapabilityLayer
     * @description 读取CapabilityLayer
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Layer对象.
     */
    function readCapabilityLayer(node, objectStack) {
        return pushParseAndPop({}, LAYER_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readLayer
     * @description 读取Layer
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Layer对象.
     */
    function readLayer(node, objectStack) {
        const parentLayerObject = (objectStack[objectStack.length - 1]);

        const layerObject = pushParseAndPop({}, LAYER_PARSERS, node, objectStack);

        if (!layerObject) {
            return undefined;
        }
        let queryable = readBooleanString(node.getAttribute('queryable'));
        if (queryable === undefined) {
            queryable = parentLayerObject['queryable'];
        }
        layerObject['queryable'] = queryable !== undefined ? queryable : false;

        let cascaded = readNonNegativeIntegerString(
            node.getAttribute('cascaded'));
        if (cascaded === undefined) {
            cascaded = parentLayerObject['cascaded'];
        }
        layerObject['cascaded'] = cascaded;

        let opaque = readBooleanString(node.getAttribute('opaque'));
        if (opaque === undefined) {
            opaque = parentLayerObject['opaque'];
        }
        layerObject['opaque'] = opaque !== undefined ? opaque : false;

        let noSubsets = readBooleanString(node.getAttribute('noSubsets'));
        if (noSubsets === undefined) {
            noSubsets = parentLayerObject['noSubsets'];
        }
        layerObject['noSubsets'] = noSubsets !== undefined ? noSubsets : false;

        let fixedWidth = readDecimalString(node.getAttribute('fixedWidth'));
        if (!fixedWidth) {
            fixedWidth = parentLayerObject['fixedWidth'];
        }
        layerObject['fixedWidth'] = fixedWidth;

        let fixedHeight = readDecimalString(node.getAttribute('fixedHeight'));
        if (!fixedHeight) {
            fixedHeight = parentLayerObject['fixedHeight'];
        }
        layerObject['fixedHeight'] = fixedHeight;

        // See 7.2.4.8
        const addKeys = ['Style', 'SRS', 'AuthorityURL'];
        addKeys.forEach(function (key) {
            if (key in parentLayerObject) {
                const childValue = layerObject[key] || [];
                layerObject[key] = childValue.concat(parentLayerObject[key]);
            }
        });

        const replaceKeys = ['EX_GeographicBoundingBox', 'BoundingBox', 'Dimension',
            'Attribution', 'MinScaleDenominator', 'MaxScaleDenominator'
        ];
        replaceKeys.forEach(function (key) {
            if (!(key in layerObject)) {
                const parentValue = parentLayerObject[key];
                layerObject[key] = parentValue;
            }
        });

        return layerObject;
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readDimension
     * @description 读取Dimension
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object} Dimension对象.
     */
    function readDimension(node, objectStack) {
        const dimensionObject = {
            'name': node.getAttribute('name'),
            'units': node.getAttribute('units'),
            'unitSymbol': node.getAttribute('unitSymbol'),
            'default': node.getAttribute('default'),
            'multipleValues': readBooleanString(node.getAttribute('multipleValues')),
            'nearestValue': readBooleanString(node.getAttribute('nearestValue')),
            'current': readBooleanString(node.getAttribute('current')),
            'values': readString(node)
        };
        return dimensionObject;
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readFormatOnlineresource
     * @description 读取FormatOnlineresource
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Online resource对象.
     */
    function readFormatOnlineresource(node, objectStack) {
        return pushParseAndPop({}, FORMAT_ONLINERESOURCE_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readRequest
     * @description 读取Request
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Request对象.
     */
    function readRequest(node, objectStack) {
        return pushParseAndPop({}, REQUEST_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readDCPType
     * @description 读取DCPType
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} DCP type对象.
     */
    function readDCPType(node, objectStack) {
        return pushParseAndPop({}, DCPTYPE_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readHTTP
     * @description 读取HTTP
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} HTTP对象.
     */
    function readHTTP(node, objectStack) {
        return pushParseAndPop({}, HTTP_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readOperationType
     * @description 读取OperationType
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Operation type对象.
     */
    function readOperationType(node, objectStack) {
        return pushParseAndPop({}, OPERATIONTYPE_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readSizedFormatOnlineresource
     * @description 读取SizedFormatOnlineresource
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Online resource对象.
     */
    function readSizedFormatOnlineresource(node, objectStack) {
        const formatOnlineresource = readFormatOnlineresource(node, objectStack);
        if (formatOnlineresource) {
            const size = [
                readNonNegativeIntegerString(node.getAttribute('width')),
                readNonNegativeIntegerString(node.getAttribute('height'))
            ];
            formatOnlineresource['size'] = size;
            return formatOnlineresource;
        }
        return undefined;
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readAuthorityURL
     * @description 读取AuthorityURL
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Authority URL对象.
     */
    function readAuthorityURL(node, objectStack) {
        const authorityObject = readFormatOnlineresource(node, objectStack);
        if (authorityObject) {
            authorityObject['name'] = node.getAttribute('name');
            return authorityObject;
        }
        return undefined;
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readMetadataURL
     * @description 读取MetadataURL
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Metadata URL对象.
     */
    function readMetadataURL(node, objectStack) {
        const metadataObject = readFormatOnlineresource(node, objectStack);
        if (metadataObject) {
            metadataObject['type'] = node.getAttribute('type');
            return metadataObject;
        }
        return undefined;
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readStyle
     * @description 读取Style
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Object|undefined} Style对象.
     */
    function readStyle(node, objectStack) {
        return pushParseAndPop({}, STYLE_PARSERS, node, objectStack);
    }

    /**
     * @private
     * @function Zondy.Format.WMSCapabilities.prototype.readKeywordList
     * @description 读取KeywordList
     * @param {Element} node 节点.
     * @param {Array<*>} objectStack 对象堆栈.
     * @return {Array<string>|undefined} Keyword list.
     */
    function readKeywordList(node, objectStack) {
        return pushParseAndPop([], KEYWORDLIST_PARSERS, node, objectStack);
    }

    export {
        WMSCapabilities
    };
    Zondy.Format.WMSCapabilities = WMSCapabilities;