/*
 *        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;

/**
 * @author Gerald Brose
 *
 */

public class ArrayTypeSpec
    extends VectorType
{
    ArrayDeclarator declarator = null;
    String dimensionStr = "";
    public int[] dims = null;
    int my_dim = 0;

    private boolean written = false;

    public ArrayTypeSpec(int num, TypeSpec elem,
                         ArrayDeclarator ad, String pack_name)
    {
        super(num);
        declarator = ad;
        name = declarator.name();
        set_token(ad.get_token());
        setEnclosingSymbol(ad.getEnclosingSymbol());
        this.pack_name = pack_name;
        type_spec = elem;

        if (parser.logger.isLoggable(Level.ALL))
            parser.logger.log(Level.ALL, "ArrayTypeSpec with declarator " + ad.name());

    }

    /**
     * private constructor, only to be called from public constructor for
     * multi-dimensional arrays, i.e. nested arrays. Used to create
     * nested ArrayTypeSpecs
     */

    private ArrayTypeSpec(int num,
                          TypeSpec elem,
                          ArrayDeclarator ad,
                          String pack_name,
                          int my_dim)
    {
        super(num);
        declarator = ad;
        name = declarator.name();
        dims = declarator.dimensions();
        set_token(ad.get_token());
        setEnclosingSymbol(ad.getEnclosingSymbol());
        this.pack_name = pack_name;
        this.my_dim = my_dim;
        if (dims.length > my_dim + 1)
        {
            type_spec =
                new ArrayTypeSpec(new_num(), elem, ad, pack_name, my_dim + 1);
        }
        else
            type_spec = elem;

        // needs to be done here because nested array type specs are not parsed
        StringBuffer sb = new StringBuffer();
        for (int i = my_dim; i < dims.length; i++)
        {
            sb.append("[]");
        }
        dimensionStr = sb.toString();
    }

    /**
     * clone this ArrayTypeSpec. The cloned object will not be parsed again.
     */

    public Object clone()
    {
        ArrayTypeSpec st =
            new ArrayTypeSpec(new_num(), type_spec, declarator, pack_name);
        st.dims = this.dims;
        st.included = this.included;
        st.typedefd = this.typedefd;
        st.inhibitionFlag = this.inhibitionFlag;
        st.dims = dims;
        st.my_dim = my_dim;
        st.dimensionStr = this.dimensionStr;
        st.set_token(get_token());
        st.setEnclosingSymbol(getEnclosingSymbol());
        return st;
    }

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


    public TypeSpec typeSpec()
    {
        return this;
    }

    public void setPackage(String s)
    {
        s = parser.pack_replace(s);
        throw new RuntimeException("ArrayTypeSpec.setPackage should never be called!");
    }

    /**
     * we have to be able to distinguish between explicitly typedef'd
     * type names and anonymously defined type names
     */

    public void markTypeDefd()
    {
        typedefd = true;
    }

    public void parse()
        throws ParseException
    {
        if (parser.logger.isLoggable(Level.ALL))
            parser.logger.log(Level.ALL, "ArrayTypeSpec.parse " + declarator.name());

        dims = declarator.dimensions();
        if (dims.length > 1)
        {
            type_spec =
                new ArrayTypeSpec(new_num(), type_spec, declarator, pack_name, 1);
        }
        else if (type_spec.typeSpec() instanceof ConstrTypeSpec)
        {
            // locally defined, nested structs must be parsed (fixes
            // bug #84) This will also result in an attempt to parse
            // structs referred to through a scoped name in struct
            // members, which have been inlined earlier in
            // Member.java. Not a problem, structs will skip a second
            // parse attempt.
            type_spec.parse();
        }
        else if (type_spec.typeSpec() instanceof ScopedName)
        {
            TypeSpec ts = ((ScopedName)type_spec.typeSpec()).resolvedTypeSpec();
            if (ts != null)
                type_spec = ts;
        }

        StringBuffer sb = new StringBuffer();

        for (int i = my_dim; i < dims.length; i++)
            sb.append("[]");

        dimensionStr = sb.toString();

        try
        {
            if (!typedefd)
            {
                NameTable.define(full_name(), IDLTypes.TYPE);

                // change for Prismtech JAC#572 here
                //
                if (!NameTable.isDefined(typeName(), IDLTypes.TYPE))
                    NameTable.define(typeName(), IDLTypes.TYPE);
            }
        }
        catch (NameAlreadyDefined n)
        {
            parser.fatal_error("Name " + full_name() + " already defined.", null);
        }
    }

    /**
     * @return a string for an expression of type TypeCode that
     * describes this type
     *
     * Array and sequence types always have this expression inlined in
     * their containing classes because arrays and sequences can be
     * locally defined (e,g, in a struct) without there being helper
     * classes (so Helper.type() is not an option)
     */

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

    public String getTypeCodeExpression(Set knownTypes)
    {
        //TODO: what happens, when actual type is in knownTypes?
        String originalType =
            "org.omg.CORBA.ORB.init().create_array_tc(" + dims[ my_dim ] + ","
            + elementTypeSpec().getTypeCodeExpression(knownTypes) + ")";

        return originalType;
    }

    public int getTCKind()
    {
    	return org.omg.CORBA.TCKind._tk_array;
    }

    public String helperName()
    {
        return ScopedName.unPseudoName(full_name()) + "Helper";
    }

    public String holderName()
    {
        return ScopedName.unPseudoName(full_name()) + "Holder";
    }

    public String className()
    {
        String fullName;

        if (pack_name.length() > 0)
            fullName = ScopedName.unPseudoName(pack_name + "." + name);
        else
            fullName = ScopedName.unPseudoName(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;

    }

    /**
     */

    public int length()
    {
        return dims[ my_dim ];
    }


    public String printReadStatement(String var_name, String streamname)
    {
        if (parser.logger.isLoggable(Level.WARNING))
            parser.logger.log(Level.WARNING, "Array printReadStatement");

        StringBuffer sb = new StringBuffer();
        String type = typeName();

        sb.append(var_name + " = new " + type.substring(0, type.indexOf("[")));
        sb.append("[" + length() + "]");

        sb.append(type.substring(type.indexOf(']') + 1) + ";" + Environment.NL);


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

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

    public String printWriteStatement(String var_name, String streamname)
    {
        StringBuffer sb = new StringBuffer();
        sb.append("\t\tif (" + var_name + ".length<" + length() + ")" + Environment.NL +
                  "\t\t\tthrow new org.omg.CORBA.MARSHAL(\"Incorrect array size \"+" +
                  var_name + ".length+\", expecting " + length() + "\");" + Environment.NL);

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


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

        String type = typeName();

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

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

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

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

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

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

        ps.println("}");
    }


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

        String type = typeName();

        ps.println("public abstract class " + className + "Helper");
        ps.println("{");

        ps.println("\tprivate static org.omg.CORBA.TypeCode _type = " +
                   getTypeCodeExpression() + ";");
        TypeSpec.printHelperClassMethods(ps, type);
        printIdMethod(ps);

        /* read */

        ps.println("\tpublic static " + type +
                   " read (final org.omg.CORBA.portable.InputStream _in)");
        ps.println("\t{");

        ps.print("\t\t" + type + " result = new " +
                 type.substring(0, type.indexOf('[')) + "[" + length() + "]");

        ps.println(type.substring(type.indexOf(']')+1) + "; // " + type);


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

        /* write */

        ps.println("\tpublic static void write (final org.omg.CORBA.portable.OutputStream out, final " + type + " s)");
        ps.println("\t{");
        if (declarator.dimensions()[ 0 ] != 0)
        {
            ps.println("\t\tif (s.length != " + declarator.dimensions()[ 0 ] + ")");
            ps.println("\t\t\tthrow new org.omg.CORBA.MARSHAL(\"Incorrect array size\");");
        }
        if (elementTypeSpec() instanceof BaseType &&
            !(elementTypeSpec() instanceof AnyType))
        {
            String _tmp = elementTypeSpec().printWriteStatement("s", "out");
            ps.println("\t\t" + _tmp.substring(0, _tmp.indexOf("(")) + "_array(s,0," + length() + ");");
        }
        else
        {
            ps.println("\t\tfor (int i = 0; i < s.length; i++)" + Environment.NL + "\t\t{");
            ps.println("\t\t\t" + elementTypeSpec().printWriteStatement("s[i]", "out")
                       + Environment.NL + "\t\t}");
        }
        ps.println("\t}");
        ps.println("}");
    }


    public void print(PrintWriter _ps)
    {
        if (included && !generateIncluded())
            return; // no code generation

        try
        {
            // print the element type, may be a locally defined member type, e.g.
            // a struct member type
            type_spec.print(_ps);

            // only generate class files for explicitly
            // defined sequence types, i.e. for typedef'd ones

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

                String className = className();
                String path = parser.out_dir + fileSeparator + pack_name.replace('.', fileSeparator);
                File dir = new File(path);
                if (!dir.exists())
                {
                    if (!dir.mkdirs())
                    {
                        org.jacorb.idl.parser.fatal_error("Unable to create " + path, null);
                    }
                }

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

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

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

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

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

    public void printInsertIntoAny(PrintWriter ps,
                                   String anyname,
                                   String varname)
    {
        String helpername = className() + "Helper";
        ps.println("\t\t" + pack_name + "." + helpername + ".insert(" + anyname + ", " + varname + " );");
    }

    public void printExtractResult(PrintWriter ps,
                                   String resultname,
                                   String anyname,
                                   String resulttype)
    {
        throw new RuntimeException("DII-stubs not completely implemented for array types!");
    }
}
