/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.cxf.tools.corba.processors.idl;

import javax.wsdl.Definition;
import javax.xml.namespace.QName;

import antlr.collections.AST;

import org.apache.cxf.binding.corba.wsdl.Anonsequence;
import org.apache.cxf.binding.corba.wsdl.CorbaType;
import org.apache.cxf.binding.corba.wsdl.Sequence;
import org.apache.cxf.tools.corba.common.ReferenceConstants;
import org.apache.ws.commons.schema.XmlSchema;
import org.apache.ws.commons.schema.XmlSchemaComplexType;
import org.apache.ws.commons.schema.XmlSchemaElement;
import org.apache.ws.commons.schema.XmlSchemaSequence;
import org.apache.ws.commons.schema.XmlSchemaType;
import org.apache.ws.commons.schema.constants.Constants;

public class SequenceVisitor extends VisitorBase {

    private static final String ELEMENT_NAME = "item";

    private AST identifierNode;

    public SequenceVisitor(Scope scope,
                           Definition defn,
                           XmlSchema schemaRef,
                           WSDLASTVisitor wsdlVisitor,
                           AST identifierNodeRef) {
        super(scope, defn, schemaRef, wsdlVisitor);
        identifierNode = identifierNodeRef;
    }

    public static boolean accept(AST node) {
        return node.getType() == IDLTokenTypes.LITERAL_sequence;
    }

    public void visit(AST seq) {
        // <sequence_type> ::= "sequence" "<" <simple_type_spec> "," <positive_int_const> ">"
        //                   | "sequence" "<" <simple_type_spec> ">"


        AST simpleTypeSpecNode = seq.getFirstChild();
        // REVISIT: TypesUtils.getPrimitiveCorbaTypeNameNode should be renamed
        // to something more suitable and should be made more general.
        AST boundNode = TypesUtils.getCorbaTypeNameNode(simpleTypeSpecNode);
        //get chance to check if bound is symbol name which defined as const,
        //if so, replace the symbol name with defined const
        if (boundNode != null) {
            String constValue = TypesUtils.getConstValueByName(boundNode, typeMap);
            if (constValue != null) {
                boundNode.setText(constValue);
            }
        }

        SimpleTypeSpecVisitor visitor = new SimpleTypeSpecVisitor(new Scope(getScope(), identifierNode),
                                                                  definition,
                                                                  schema,
                                                                  wsdlVisitor,
                                                                  null);
        visitor.visit(simpleTypeSpecNode);

        XmlSchemaType stype = visitor.getSchemaType();
        CorbaType ctype = visitor.getCorbaType();
        Scope fullyQualifiedName = visitor.getFullyQualifiedName();


        long bound = -1;
        if (boundNode != null) {
            bound = Long.parseLong(boundNode.toString());
        }

        final Scope scopedName;
        if (identifierNode == null) {
            // anonymous type
            scopedName = TypesUtils.generateAnonymousScopedName(getScope(), schema);
        } else {
            scopedName = new Scope(getScope(), identifierNode);
        }

        final XmlSchemaType schemaType;

        // According to CORBA Binding for WSDL specification,
        // idl:sequence<octet> maps to xs:base64Binary by default.
        //
        // wsdlVisitor.getSequenceOctetType() returns the XmlSchema type
        // that idl:sequence<octet> should map to, as specified by the
        // -s command line option or the default type xsd:base64Binary.
        //
        if (stype != null) {
            if (!stype.getQName().equals(Constants.XSD_UNSIGNEDBYTE)) {
                schemaType = generateSchemaType(stype, scopedName, bound, fullyQualifiedName);
            } else {
                schemaType = wsdlVisitor.getSequenceOctetType();
            }
        } else {
            schemaType = generateSchemaType(null, scopedName, bound, fullyQualifiedName);
        }

        final CorbaType corbaType;
        if (identifierNode == null) {
            corbaType = generateCorbaAnonsequence(ctype,
                                                  schemaType,
                                                  scopedName,
                                                  bound,
                                                  fullyQualifiedName);
        } else {
            corbaType = generateCorbaSequence(ctype,
                                              schemaType,
                                              scopedName,
                                              bound,
                                              fullyQualifiedName);
        }


        setSchemaType(schemaType);
        setCorbaType(corbaType);
        setFullyQualifiedName(fullyQualifiedName);
    }

    private XmlSchemaType generateSchemaType(XmlSchemaType stype, Scope scopedName,
                                             long bound, Scope fullyQualifiedName) {
        XmlSchemaComplexType ct = new XmlSchemaComplexType(schema, true);
        ct.setName(mapper.mapToQName(scopedName));
        XmlSchemaSequence sequence = new XmlSchemaSequence();
        XmlSchemaElement el = new XmlSchemaElement(schema, false);
        el.setName(ELEMENT_NAME);
        el.setMinOccurs(0);
        if (bound != -1) {
            el.setMaxOccurs(bound);
        } else {
            el.setMaxOccurs(Long.MAX_VALUE);
        }
        if (stype != null) {
            el.setSchemaTypeName(stype.getQName());
            if (stype.getQName().equals(ReferenceConstants.WSADDRESSING_TYPE)) {
                el.setNillable(true);
            }
        } else {
            SequenceDeferredAction elementAction =
                new SequenceDeferredAction(el);
            wsdlVisitor.getDeferredActions().add(fullyQualifiedName, elementAction);
        }
        sequence.getItems().add(el);
        ct.setParticle(sequence);
        return ct;
    }

    private CorbaType generateCorbaSequence(CorbaType ctype,
                                                XmlSchemaType schemaType,
                                                Scope scopedName,
                                                long bound,
                                                Scope fullyQualifiedName) {
        //create the corba sequence
        Sequence corbaSeq = new Sequence();
        if (bound == -1) {
            bound = 0;
        }
        corbaSeq.setBound(bound);
        corbaSeq.setQName(new QName(typeMap.getTargetNamespace(), scopedName.toString()));
        corbaSeq.setType(schemaType.getQName());
        //REVISIT, if we add qualification then change the below.
        corbaSeq.setElemname(new QName("", ELEMENT_NAME));
        if (ctype != null) {
            corbaSeq.setElemtype(ctype.getQName());
        } else {
            SequenceDeferredAction seqAction =
                new SequenceDeferredAction(corbaSeq);
            wsdlVisitor.getDeferredActions().add(fullyQualifiedName, seqAction);
        }
        corbaSeq.setRepositoryID(scopedName.toIDLRepositoryID());

        return corbaSeq;
    }

    private Anonsequence generateCorbaAnonsequence(CorbaType ctype,
                                                   XmlSchemaType schemaType,
                                                   Scope scopedName,
                                                   long bound,
                                                   Scope fullyQualifiedName) {
        // create corba anonsequence
        Anonsequence result = new Anonsequence();
        if (bound == -1) {
            bound = 0;
        }
        result.setBound(bound);
        result.setQName(new QName(typeMap.getTargetNamespace(), scopedName.toString()));
        //REVISIT, if we add qualification then change the below.
        result.setElemname(new QName("", ELEMENT_NAME));
        if (schemaType == null || ctype == null) {
            SequenceDeferredAction anonSeqAction =
                new SequenceDeferredAction(result);
            wsdlVisitor.getDeferredActions().add(fullyQualifiedName, anonSeqAction);
        } else {
            result.setType(schemaType.getQName());
            result.setElemtype(ctype.getQName());
        }

        // Need to create an action if the type was forward declared.
        if (schemaType == null) {
            SequenceDeferredAction anonSeqAction =
                new SequenceDeferredAction(schemas, schema);
            wsdlVisitor.getDeferredActions().add(fullyQualifiedName, anonSeqAction);
        }

        // add corbaType
        typeMap.getStructOrExceptionOrUnion().add(result);

        return result;
    }

}
