/*
 *        JacORB - a free Java ORB
 *
 *   Copyright (C) 1997-2014 Gerald Brose / The JacORB Team.
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.jacorb.idl;

import java.io.File;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;

/**
 * IDL sequences.
 *
 *
 * @author Gerald Brose
 */

public class SequenceType
    extends VectorType
{
    private boolean written = false;

    /** used to generate unique name vor local variables */
    private static int idxNum = 0;

    /** markers for recursive sequences */
    private boolean recursive = false;

    public ConstExpr max = null;
    int length = 0;

    public SequenceType(int num)
    {
        super(num);
        name = null;
        typedefd = false;
    }

    public Object clone()
    {
        SequenceType copy = new SequenceType(IdlSymbol.new_num());
        copy.type_spec = this.type_spec;
        copy.max = this.max;
        copy.length = this.length;
        copy.name = this.name;
        copy.pack_name = this.pack_name;
        copy.included = this.included;
        copy.typedefd = this.typedefd;
        copy.recursive = this.recursive;
        copy.set_token(this.get_token());
        copy.setEnclosingSymbol(this.getEnclosingSymbol());
        return copy;
    }


    public void setEnclosingSymbol(IdlSymbol symbol)
    {
        if (enclosing_symbol != null && enclosing_symbol != symbol)
        {
            throw new RuntimeException("Compiler Error: trying to reassign container for " + name);
        }
        enclosing_symbol = symbol;
    }


    /**
     * since the sequence type's name depends on a declarator
     * given in the typedef, the name varilabe has to be set explicitly
     * by the TypeDef object before this sequence type can
     * be used.
     */

    public TypeSpec typeSpec()
    {
        return this;
    }

    public void setPackage(String pkg)
    {
        pkg = parser.pack_replace(pkg);
        if (pack_name.length() > 0)
        {
            pack_name = pkg + "." + pack_name;
        }
        else
        {
            pack_name = pkg;
        }
        type_spec.setPackage(pkg);
        if (max != null)
        {
            max.setPackage(pkg);
        }
    }

    /**
     */

    public int length()
    {
        return length;
    }



    void setRecursive()
    {
        if (parser.logger.isLoggable(Level.WARNING))
        {
            parser.logger.log(Level.WARNING, "Sequence " + typeName +
            " set recursive ------- this: " + this);
        }
        recursive = true;
    }

    /**
     * @return a string for an expression of type TypeCode that describes this type
     */

    public String getTypeCodeExpression()
    {
        return getTypeCodeExpression(new HashSet());
    }

    public String getTypeCodeExpression(Set knownTypes)
    {
        if (parser.logger.isLoggable(Level.ALL))
        {
            parser.logger.log(Level.ALL, "Sequence getTypeCodeExpression " + name);
        }

        //If type is already known, create recursive typeCode
        //TODO: Check
        if (knownTypes.contains(this))
        {
        	return "org.omg.CORBA.ORB.init().create_recursive_tc(\"" +
        	    elementTypeSpec().id() + "\")";
        }
        knownTypes.add(this);

        String originalType = null;

        if (recursive)
        {
            originalType = "org.omg.CORBA.ORB.init().create_sequence_tc(" +
                length + ", org.omg.CORBA.ORB.init().create_recursive_tc(\"" +
                elementTypeSpec().id() + "\"))";
        }
        else
        {
            originalType = "org.omg.CORBA.ORB.init().create_sequence_tc(" +
                length + ", " + elementTypeExpression(knownTypes) + ")";
        }
        return originalType;
    }

    public static int getNumber()
    {
        return idxNum++;
    }

    /**
     * We have to distinguish between sequence types that have been
     * explicitly declared as types with a typedef and those that
     * are declared as anonymous types in structs or unions. In
     * the latter case, we have to generate marshalling code in-line
     * because there are no helpers for anonymous types
     */

    public String printReadStatement(String var_name, String streamname)
    {
        if (parser.logger.isLoggable(Level.ALL))
        {
            parser.logger.log(Level.ALL, "Sequence printReadStatement for " + typeName());
        }

        StringBuffer buffer = new StringBuffer();
        String type = typeName();
        String lgt = "_l" + var_name.replace('.', '_');

        // if [i] is part of the name, trim that off
        if (lgt.indexOf('[') > 0)
        {
            lgt = lgt.substring(0, lgt.indexOf('[')) + "_";
        }

        // make local variable name unique
        lgt = lgt + getNumber();

        buffer.append("int " + lgt + " = " + streamname + ".read_long();" + Environment.NL);
        if (length != 0)
        {
            buffer.append("\t\tif (" + lgt + " > " + length + ")" + Environment.NL);
            buffer.append("\t\t\tthrow new org.omg.CORBA.MARSHAL(\"Sequence length incorrect!\");" + Environment.NL);
        }

        buffer.append("\t\ttry" + Environment.NL + "\t\t{" + Environment.NL );
        buffer.append("\t\t\t int x = " + streamname + ".available();" + Environment.NL );
        buffer.append("\t\t\t if ( x > 0 && " + lgt + " > x )" + Environment.NL );
        buffer.append("\t\t\t\t{" + Environment.NL );
        buffer.append("\t\t\t\t\tthrow new org.omg.CORBA.MARSHAL(\"Sequence length too large. Only \" + x + \" available and trying to assign \" + "
                      + lgt + ");" + Environment.NL);
        buffer.append("\t\t\t\t}" + Environment.NL );
        buffer.append("\t\t}" + Environment.NL + "\t\tcatch (java.io.IOException e)" + Environment.NL
                      + "\t\t{" + Environment.NL + "\t\t}" + Environment.NL);


        buffer.append("\t\t" + var_name + " = new " + type.substring(0, type.indexOf('[')) +
                  "[" + lgt + "]" + type.substring(type.indexOf(']') + 1) + ";" + Environment.NL);

        TypeSpec elemType = elementTypeSpec();
        while (elemType instanceof AliasTypeSpec)
        {
            //get real type
            elemType = ((AliasTypeSpec) elemType).originalType();
        }

        if (elemType instanceof BaseType &&
            !(elemType instanceof AnyType))
        {
            String _tmp = elemType.printReadExpression(streamname);
            buffer.append("\t\t");
            buffer.append(_tmp.substring(0, _tmp.indexOf('(')));
            buffer.append("_array(");
            buffer.append(var_name);
            buffer.append(",0,");
            buffer.append(lgt);
            buffer.append(");");
        }
        else
        {
            char idx_variable = 'i';
            String indent = "";
            if (var_name.endsWith("]"))
            {
                idx_variable = (char)(var_name.charAt(var_name.length() - 2) + 1);
                indent = "    ";
            }
            buffer.append("\t\t" + indent + "for (int " + idx_variable + "=0;"
                          + idx_variable + "<" + var_name + ".length;" + idx_variable
                          + "++)" + Environment.NL + "\t\t" + indent + "{" + Environment.NL);

            buffer.append("\t\t\t" + indent +
                      elementTypeSpec().printReadStatement(var_name +
                                                           "[" + idx_variable + "]",
                                                           streamname)
                      + Environment.NL);

            buffer.append("\t\t" + indent + "}" + Environment.NL);

        }
        return buffer.toString();
    }


    public String printWriteStatement(String var_name, String streamname)
    {
        StringBuffer buffer = new StringBuffer();
        if (length != 0)
        {
            buffer.append("\t\tif (" + var_name + ".length > " + length + ")" + Environment.NL);
            buffer.append("\t\t\tthrow new org.omg.CORBA.MARSHAL(\"Incorrect sequence length\");");
        }
        buffer.append(Environment.NL);
        buffer.append("\t\t" + streamname + ".write_long(" + var_name + ".length);" + Environment.NL);

        TypeSpec elemType = elementTypeSpec();
        while (elemType instanceof AliasTypeSpec)
        {
            //get real type
            elemType = ((AliasTypeSpec) elemType).originalType();
        }

        if (elemType instanceof BaseType &&
            !(elemType instanceof AnyType))
        {
            String _tmp = elemType.printWriteStatement(var_name, streamname);
            buffer.append("\t\t");
            buffer.append(_tmp.substring(0, _tmp.indexOf('(')));
            buffer.append("_array(");
            buffer.append(var_name);
            buffer.append(",0,");
            buffer.append(var_name);
            buffer.append(".length);");
        }
        else
        {
            char idx_variable = 'i';
            String indent = "";
            if (var_name.endsWith("]"))
            {
                idx_variable = (char)(var_name.charAt(var_name.length() - 2) + 1);
                indent = "    ";
            }
            buffer.append("\t\t" + indent + "for (int " + idx_variable + "=0; "
                          + idx_variable + "<" + var_name + ".length;"
                          + idx_variable + "++)"  + Environment.NL
                          + "\t\t" + indent + "{" + Environment.NL);

            buffer.append("\t\t\t" + indent +
                      elementTypeSpec().printWriteStatement(var_name
                                                            + "[" + idx_variable + "]",
                                                            streamname) + Environment.NL);
            buffer.append("\t\t" + indent + "}" + Environment.NL);
        }
        return buffer.toString();
    }


    public String holderName()
    {
        if (!typedefd)
        {
            throw new RuntimeException("Compiler Error: should not be called (helpername on not typedef'd SequenceType " + name + ")");
        }

        String name = full_name();
        if (pack_name.length() > 0)
        {
            name = getFullName(name);
        }

        return name + "Holder";
    }


    public String helperName()
    {
        if (!typedefd)
        {
            throw new RuntimeException("Compiler Error: should not be called (helperName() on not typedef'd SequenceType)");
        }

        String name = full_name();
        if (pack_name.length() > 0)
        {
            name = getFullName(name);
        }

        return name + "Helper";
    }

    public String className()
    {
        String fullName = full_name();
        String cName;
        if (fullName.indexOf('.') > 0)
        {
            pack_name = fullName.substring(0, fullName.lastIndexOf('.'));
            cName = fullName.substring(fullName.lastIndexOf('.') + 1);
        }
        else
        {
            pack_name = "";
            cName = fullName;
        }
        return cName;

    }

    /**
     * The parsing phase.
     */

    public void parse()
    {
        if (max != null)
        {
            max.parse();
            length = max.pos_int_const();
        }

        if (type_spec.typeSpec() instanceof ScopedName)
        {
            TypeSpec typeSpec =
                ((ScopedName)type_spec.typeSpec()).resolvedTypeSpec();
            if (typeSpec != null)
            {
                type_spec = typeSpec;
            }

            if (type_spec instanceof AliasTypeSpec)
            {
                addImportedAlias(type_spec.full_name());
            }
            else
            {
                addImportedName(type_spec.typeName());
            }

            addImportedName(type_spec.typeSpec().typeName());
        }
        try
        {
            NameTable.define(full_name(), IDLTypes.TYPE);
        }
        catch (NameAlreadyDefined n)
        {
            // ignore, sequence types can be defined a number
            // of times under different names
        }
    }

    public String full_name()
    {
        if (name == null)
        {
            return "<" + pack_name + ".anon>";
        }
        if (pack_name.length() > 0)
        {
            return ScopedName.unPseudoName(pack_name + "." + name);
        }
        return ScopedName.unPseudoName(name);
    }


    private void printHolderClass(String className, PrintWriter out)
    {
        if (!pack_name.equals(""))
        {
            out.println("package " + pack_name + ";" + Environment.NL);
        }

        String type = typeName();

        printImport(out);

        printClassComment("sequence", className, out);

        out.println("public" + parser.getFinalString() + " class " + className + "Holder");
        out.println("\timplements org.omg.CORBA.portable.Streamable");

        out.println("{");
        out.println("\tpublic " + type + " value;");
        out.println("\tpublic " + className + "Holder ()");
        out.println("\t{");
        out.println("\t}");

        out.println("\tpublic " + className + "Holder (final " + type + " initial)"
                    + Environment.NL + "\t{");
        out.println("\t\tvalue = initial;");
        out.println("\t}");

        out.println("\tpublic org.omg.CORBA.TypeCode _type ()");
        out.println("\t{");
        out.println("\t\treturn " + className + "Helper.type ();");
        out.println("\t}");

        out.println("\tpublic void _read (final org.omg.CORBA.portable.InputStream _in)");
        out.println("\t{");
        out.println("\t\tvalue = " + className + "Helper.read (_in);");
        out.println("\t}");

        out.println("\tpublic void _write (final org.omg.CORBA.portable.OutputStream _out)");
        out.println("\t{");
        out.println("\t\t" + className + "Helper.write (_out,value);");
        out.println("\t}");

        out.println("}");
    }


    private void printHelperClass(String className, PrintWriter out)
    {
        if (!pack_name.equals(""))
        {
            out.println("package " + pack_name + ";");
        }

        String type = typeName();
        printImport(out);

        printClassComment("sequence", className, out);

        out.println("public abstract class " + className + "Helper");
        out.println("{");
        out.println("\tprivate static org.omg.CORBA.TypeCode _type = " +
                   getTypeCodeExpression() + ";");

        TypeSpec.printHelperClassMethods(out, type);
        printIdMethod(out); // from IdlSymbol

        /** read */

        out.println("\tpublic static " + type +
                   " read (final org.omg.CORBA.portable.InputStream in)");

        out.println("\t{");
        out.println("\t\tint l = in.read_long();");

        if (length != 0)
        {
            out.println("\t\tif (l > " + length + ")");
            out.println("\t\t\tthrow new org.omg.CORBA.MARSHAL();");
        }

        out.println("\t\t" + type + " result = new " +
                   type.substring(0, type.indexOf('[')) + "[l]" +
                   type.substring(type.indexOf(']') + 1) + ";");

        if (elementTypeSpec() instanceof BaseType &&
            !(elementTypeSpec() instanceof AnyType))
        {
            String _tmp = elementTypeSpec().printReadExpression("in");
            out.println("\t\t" + _tmp.substring(0, _tmp.indexOf('(')) +
                       "_array(result,0,result.length);");
        }
        else
        {
            out.println("\t\tfor (int i = 0; i < l; i++)");
            out.println("\t\t{");
            out.println("\t\t\t" + elementTypeSpec().printReadStatement("result[i]", "in"));
            out.println("\t\t}");
        }

        out.println("\t\treturn result;");
        out.println("\t}");

        /* write */

        out.println("\tpublic static void write (final org.omg.CORBA.portable.OutputStream out, "
                   + "final " + type + " s)");
        out.println("\t{");
        if (length != 0)
        {
            out.println("\t\tif (s.length > " + length + ")");
            out.println("\t\t\tthrow new org.omg.CORBA.MARSHAL();");
        }
        out.println("\t\tout.write_long(s.length);");

        if (elementTypeSpec() instanceof BaseType &&
            !(elementTypeSpec() instanceof AnyType))
        {
            String _tmp = elementTypeSpec().printWriteStatement("s", "out");
            out.println(_tmp.substring(0, _tmp.indexOf('(')) + "_array(s,0,s.length);");
        }
        else
        {
            out.println("\t\tfor (int i = 0; i < s.length; i++)");
            out.println("\t\t\t" + elementTypeSpec().printWriteStatement("s[i]", "out"));
        }

        out.println("\t}");
        out.println("}");
    }


    public void print(PrintWriter out)
    {
        try
        {
            // only generate class files for explicitly
            // defined sequence types, i.e. for typedef'd ones

            if ((!written) && typedefd)
            {
                // write holder file

                String fullName = full_name();
                String className;
                if (fullName.indexOf('.') > 0)
                {
                    pack_name = fullName.substring(0, fullName.lastIndexOf('.'));
                    className = fullName.substring(fullName.lastIndexOf('.') + 1);
                }
                else
                {
                    pack_name = "";
                    className = fullName;
                }

                String path = parser.out_dir + fileSeparator +
                    pack_name.replace('.', fileSeparator);

                File dir = new File(path);
                if (!dir.exists() && !dir.mkdirs())
                {
                    org.jacorb.idl.parser.fatal_error("Unable to create " +
                            path, null);
                }

                String fname = className + "Holder.java";
                File file = new File(dir, fname);

                if (GlobalInputStream.isMoreRecentThan(file))
                {
                    // print the mapped java class
                    PrintWriter holderOut = new PrintWriter(new java.io.FileWriter(file));
                    printHolderClass(className, holderOut);
                    holderOut.close();
                }

                fname = className + "Helper.java";
                file = new File(dir, fname);

                if (GlobalInputStream.isMoreRecentThan(file))
                {
                    // print the mapped java class
                    PrintWriter helperOut = new PrintWriter(new java.io.FileWriter(file));
                    printHelperClass(className, helperOut);
                    helperOut.close();
                }

                written = true;
            }
        }
        catch (java.io.IOException e)
        {
            throw new RuntimeException("File IO error" + e);
        }
    }

    public void printInsertIntoAny(PrintWriter out,
                                   String anyname,
                                   String varname)
    {
        out.println("\t" + helperName() + ".insert(" + anyname + ", " + varname + " );");
    }

    public void printExtractResult(PrintWriter out,
                                   String resultname,
                                   String anyname,
                                   String resulttype)
    {
       throw new RuntimeException("DII Stubs not yet complete for Sequence types");
    }
}
