/**
 * 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.binding.corba.types;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.namespace.QName;

import org.apache.cxf.binding.corba.wsdl.CaseType;
import org.apache.cxf.binding.corba.wsdl.Enum;
import org.apache.cxf.binding.corba.wsdl.Enumerator;
import org.apache.cxf.binding.corba.wsdl.Union;
import org.apache.cxf.binding.corba.wsdl.Unionbranch;
import org.omg.CORBA.TCKind;
import org.omg.CORBA.TypeCode;

public class CorbaUnionHandler extends CorbaObjectHandler {

    private CorbaObjectHandler discriminator;
    private CorbaObjectHandler value;
    private List<CorbaObjectHandler> cases = new ArrayList<>();
    private int defaultIndex;
    private List<String> labels = new ArrayList<>();

    public CorbaUnionHandler(QName unionName, QName unionIdlType, TypeCode unionTC, Object unionType) {
        super(unionName, unionIdlType, unionTC, unionType);

        // Build a list of labels.  This will be used to generate a discriminator value for the
        // default case (since we are not provided with one from the Stax stream of the Celtix object)
        Union union = (Union)unionType;
        List<Unionbranch> branches = union.getUnionbranch();
        int index = 0;
        for (Iterator<Unionbranch> branchesIter = branches.iterator(); branchesIter.hasNext();) {
            Unionbranch branch = branchesIter.next();
            List<CaseType> branchCases = branch.getCase();
            if (branchCases.isEmpty()) {
                defaultIndex = index;
            } else {
                for (Iterator<CaseType> casesIter = branchCases.iterator(); casesIter.hasNext();) {
                    CaseType ct = casesIter.next();
                    labels.add(ct.getLabel());
                }
            }

            index++;
        }
    }

    public CorbaObjectHandler getDiscriminator() {
        return discriminator;
    }

    public String getDisciminatorValueData() {
        String result = null;
        // The discriminator is handled by either the enum handler or the primitive handler.
        if (discriminator.getTypeCodeKind().value() == TCKind._tk_enum) {
            CorbaEnumHandler enumHandler = (CorbaEnumHandler)discriminator;
            result = enumHandler.getValue();
        } else {
            CorbaPrimitiveHandler primitiveHandler = (CorbaPrimitiveHandler)discriminator;
            result = primitiveHandler.getDataFromValue();
        }
        return result;
    }

    public void setDiscriminator(CorbaObjectHandler disc) {
        discriminator = disc;
    }

    public void setDiscriminatorValueFromData(String data) {
        // The discriminator is handled by either the enum handler or the primitive handler.
        if (discriminator.getTypeCodeKind().value() == TCKind._tk_enum) {
            CorbaEnumHandler enumHandler = (CorbaEnumHandler)discriminator;
            enumHandler.setValue(data);
        } else {
            CorbaPrimitiveHandler primitiveHandler = (CorbaPrimitiveHandler)discriminator;
            primitiveHandler.setValueFromData(data);
        }
    }

    public List<CorbaObjectHandler> getCases() {
        return cases;
    }

    public CorbaObjectHandler getBranchByName(String caseName) {
        for (Iterator<CorbaObjectHandler> caseIter = cases.iterator(); caseIter.hasNext();) {
            CorbaObjectHandler obj = caseIter.next();
            if (obj.getName().getLocalPart().equals(caseName)) {
                return obj;
            }
        }

        return null;
    }

    public void addCase(CorbaObjectHandler unionCase) {
        cases.add(unionCase);
    }

    public CorbaObjectHandler getValue() {
        return value;
    }

    public void setValue(String caseName, CorbaObjectHandler val) {
        value = val;
    }

    public int getDefaultIndex() {
        return defaultIndex;
    }

    public String createDefaultDiscriminatorLabel() {
        String label = null;
        // According to the CORBA specification, an enumeration discriminator can be one of the
        // following types:
        //   - *integer* (short, long, ulong, either signed or unsigned)
        //   - boolean
        //   - character
        //   - enumeration
        // So when we need to create a default discriminator to accomodate for the lack of a
        // discriminator from, these are the four cases we must check for.
        if (discriminator.getTypeCodeKind().value() == TCKind._tk_boolean) {
            // We can only have a default case with a boolean discriminator if we have
            // only one case, either TRUE or FALSE.  Therefore, we only need to check
            // against the first label, if there is one.
            if (labels.isEmpty()) {
                label = "false";
            } else {
                boolean boolValue = Boolean.parseBoolean(labels.get(0));
                label = String.valueOf(!boolValue);
            }
        } else if (discriminator.getTypeCodeKind().value() == TCKind._tk_char) {
            if (labels.isEmpty()) {
                label = String.valueOf('0');
            } else {
                char charValue = labels.get(0).charAt(0);
                while (labels.contains(String.valueOf(charValue))) {
                    charValue++;
                }
                label = String.valueOf(charValue);
            }
        } else if (discriminator.getTypeCodeKind().value() == TCKind._tk_enum) {
            // Get the list of possible enumerations in the enumerator and compare these to the
            // labels we obtained from the Union definition.  In order for the union/enum
            // combination to be syntactically correct, there must be one enumeration not included
            // as a case for the default case to be valid.
            Enum enumType = (Enum)discriminator.getType();
            List<Enumerator> enumerators = enumType.getEnumerator();
            if (labels.isEmpty()) {
                // Any value will do since we only have a default case.
                label = enumerators.get(0).getValue();
            } else {
                String enumLabel = null;
                for (Iterator<Enumerator> enumIter = enumerators.iterator(); enumIter.hasNext();) {
                    enumLabel = enumIter.next().getValue();
                    if (!labels.contains(enumLabel)) {
                        label = enumLabel;
                        break;
                    }
                }
            }
        } else if ((discriminator.getTypeCodeKind().value() == TCKind._tk_short)
                   || (discriminator.getTypeCodeKind().value() == TCKind._tk_ushort)) {
            if (labels.isEmpty()) {
                label = String.valueOf(Short.MAX_VALUE);
            }
            for (int i = Short.MAX_VALUE; i >= Short.MIN_VALUE; i--) {
                if (!labels.contains(String.valueOf(i))) {
                    label = String.valueOf(i);
                    break;
                }
            }
        } else if ((discriminator.getTypeCodeKind().value() == TCKind._tk_long)
                   || (discriminator.getTypeCodeKind().value() == TCKind._tk_ulong)) {
            if (labels.isEmpty()) {
                label = String.valueOf(Integer.MAX_VALUE);
            }
            for (int i = Integer.MAX_VALUE; i >= Integer.MIN_VALUE; i--) {
                if (!labels.contains(String.valueOf(i))) {
                    label = String.valueOf(i);
                    break;
                }
            }
        }
        return label;
    }

    public void clear() {
        if (discriminator != null) {
            discriminator.clear();
        }
        if (value != null) {
            value.clear();
        }
    }
}
