/*
	Copyright (c) 2009-2011 Olivier Chafik, All Rights Reserved
	
	This file is part of JNAerator (http://jnaerator.googlecode.com/).
	
	JNAerator is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	JNAerator 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 Lesser General Public License for more details.
	
	You should have received a copy of the GNU Lesser General Public License
	along with JNAerator.  If not, see <http://www.gnu.org/licenses/>.
*/
package com.ochafik.lang.jnaerator;

import com.ochafik.lang.jnaerator.parser.*;
import static com.ochafik.lang.jnaerator.parser.Identifier.*;
import static com.ochafik.lang.jnaerator.parser.ElementsHelper.*;
import com.ochafik.lang.jnaerator.parser.TypeRef.Primitive;
import com.ochafik.lang.jnaerator.parser.Declarator.DirectDeclarator;
import com.ochafik.lang.jnaerator.parser.Declarator.FunctionDeclarator;
import com.ochafik.lang.jnaerator.parser.Declarator.MutableByDeclarator;
import com.ochafik.lang.jnaerator.parser.StoredDeclarations.TypeDef;
import com.ochafik.lang.jnaerator.parser.TypeRef.FunctionSignature;
import com.ochafik.lang.jnaerator.parser.TypeRef.SimpleTypeRef;
import com.ochafik.lang.jnaerator.parser.TypeRef.TaggedTypeRef;
import com.ochafik.lang.jnaerator.parser.TypeRef.TargettedTypeRef;
import com.ochafik.util.string.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;

public class CToJavaPreScanner extends Scanner {
	public CToJavaPreScanner() {
	}
	
	@Override
	public void visitStruct(Struct struct) {
		super.visitStruct(struct);
		if (struct.isForwardDeclaration() && struct.getTag() != null) {
			Element parent = struct.getParentElement();
			if (!(parent instanceof TaggedTypeRefDeclaration)) {
				TypeRef tr = new TypeRef.SimpleTypeRef(struct.getTag());
				for (Modifier mod : struct.getModifiers()) {
					if (mod.isA(ModifierKind.StorageClassSpecifier))
						tr.addModifiers(mod);
				}
				struct.replaceBy(tr);
				tr.accept(this);
			}
		}
	}
    
    void moveModifiersOfType(ModifierKind kind, ModifiableElement source, ModifiableElement destination) {
        List<Modifier> mods = null;
        
        for (Modifier m : source.getModifiers()) {
            if (m.isA(kind)) {
                if (mods == null)
                    mods = new ArrayList<Modifier>();
                
                mods.add(m);
            }
        }
        if (mods != null) {
            Modifier[] modsArray = mods.toArray(new Modifier[mods.size()]);
            source.removeModifiers(modsArray);
            destination.addModifiers(modsArray);
        }
    }

    static Modifier longAlias = ModifierType.Long.resolveAlias(), shortAlias = ModifierType.Short.resolveAlias();
    static List<Modifier> getLongShortModifiers(ModifiableElement e) {
        List<Modifier> ret = null;
        for (Modifier mod : e.getModifiers()) {
            Modifier res = mod.resolveAlias();
            if (res.equals(longAlias) || res.equals(shortAlias)) {
                if (ret == null)
                    ret = new ArrayList<Modifier>();
                ret.add(mod);
            }
        }
        return ret;
    }
    
    static TypeRef longShortModsToTypeRef(List<Modifier> mods) {
        TypeRef tr = new Primitive();
        tr.addModifiers(mods);
        return tr;
    }
    static void attachLongOrShortModifiersToPointedType(TypeRef tr, List<Modifier> mods) {
        while (tr instanceof TypeRef.TargettedTypeRef) {
            TargettedTypeRef ttr = (TargettedTypeRef)tr;
            if (ttr.getTarget() == null) {
                ttr.setTarget(longShortModsToTypeRef(mods));
                return;
            } else {
                tr = ttr.getTarget();
            }
        }
        tr.addModifiers(mods);
    }
    @Override
    public void visitFunction(Function function) {
        List<Modifier> mods = getLongShortModifiers(function);
        if (mods != null) {
            // long and short can be incorrectly attached as modifiers to the function instead of to its return type: fix this here.
            if (function.getValueType() == null) {
                function.setValueType(longShortModsToTypeRef(mods));
            } else {
                attachLongOrShortModifiersToPointedType(function.getValueType(), mods);
            }
            function.removeModifiers(mods);
        } 
        if (function.getValueType() != null) {
            moveModifiersOfType(ModifierKind.CallingConvention, function.getValueType(), function);
        } else {
            Element parent = function.getParentElement();
            boolean returnsInt = false;
            if (parent instanceof Struct) {
                Struct parentStruct = (Struct)parent;
                switch (parentStruct.getType()) {
                    case CPPClass:
                    case CStruct:
                        if (!function.getName().equals(parentStruct.getTag()))
                            returnsInt = true;
                        break;
                    default:
                        break;
                }
            } else {
                returnsInt = true;
            }
            if (returnsInt) {
                function.setValueType(typeRef("int"));
            }
        }
        super.visitFunction(function);
    }
    
    
	
	@Override
	public void visitTaggedTypeRefDeclaration(TaggedTypeRefDeclaration taggedTypeRefDeclaration) {
		super.visitTaggedTypeRefDeclaration(taggedTypeRefDeclaration);
		TaggedTypeRef tr = taggedTypeRefDeclaration.getTaggedTypeRef();
		if (tr != null) {
			String before = tr.getCommentBefore();
			tr.setCommentBefore(taggedTypeRefDeclaration.getCommentBefore());
			tr.addToCommentBefore(before);
			taggedTypeRefDeclaration.setCommentBefore(null);
		}
	}

    @Override
    protected void visitStoredDeclarations(StoredDeclarations d) {
        super.visitStoredDeclarations(d);
        if (d.getDeclarators().size() == 1) {
            Declarator declarator = d.getDeclarators().get(0);
            if (declarator instanceof FunctionDeclarator) {
                FunctionDeclarator fd = (FunctionDeclarator)declarator;
                Function f = new Function(Function.Type.CFunction, null, d.getValueType(), fd.getArgs());
                f.addModifiers(fd.getTarget().getModifiers());
                FunctionSignature fs = new FunctionSignature(f);
                d.setValueType(fs);
                d.setDeclarators(Arrays.asList((Declarator)new DirectDeclarator(fd.resolveName(), fd.getDefaultValue())));
                d.accept(this);
            }
        }
    }

    @Override
    public void visitPrimitive(Primitive primitive) {
        super.visitPrimitive(primitive);
        
        if (ModifierType.Long.isContainedBy(primitive.getModifiers())) {
            Identifier name = primitive.getName();
            if (name != null && name.equals("int")) {
                primitive.setName(ident("long"));
                primitive.removeModifiers(ModifierType.Long);
            }
        } else if (ModifierType.Short.isContainedBy(primitive.getModifiers())) {
            Identifier name = primitive.getName();
            if (name != null && name.equals("int")) {
                primitive.setName(ident("short"));
                primitive.removeModifiers(ModifierType.Short);
            }
        }
        
    }
	
	@Override
	public void visitTypeDef(TypeDef v) {
		super.visitTypeDef(v);
		
		Element toAddAfter = v;
		
		TypeRef valueType = v.getValueType();
		String bestName = null, origName = null;
		TaggedTypeRef ttr = null;
		if (valueType instanceof TaggedTypeRef) {
			ttr = (TaggedTypeRef) valueType;
			bestName = JNAeratorUtils.findBestPlainStorageName(v);
			if (bestName != null)
				origName = ttr.getTag() != null ? ttr.getTag().toString() : null;
		}
		
		/// Explode comma-separated variables declarations
		for (Declarator vs : v.getDeclarators()) {
			if (vs == null)// || vs instanceof DirectDeclarator && v.getDeclarators().size() == 1)
				continue;
			
			String name = vs.resolveName();
			if (vs instanceof DirectDeclarator && name.equals(bestName) && ttr != null && origName != null) {
				DirectDeclarator rep = new DirectDeclarator(origName);
				vs.replaceBy(rep);
				ttr.setTag(ident(bestName));
				vs = rep;
				name = origName;
			}
			Declaration decl = null;
		
			Declarator.MutableByDeclarator type = vs.mutateType(v.getValueType());
			if (type instanceof TypeRef) {
				TypeRef tr = (TypeRef)type;
				decl = new StoredDeclarations.TypeDef(tr, new DirectDeclarator(name));
				decl.importDetails(v, false);
				decl.importDetails(vs, false);
				decl.importDetails(tr, true);
			} else {
				continue;
			}
			
			toAddAfter.insertSibling(decl, false);
			toAddAfter = decl;

			//decl.accept(this);//super.visitVariablesDeclaration(decl);
		}
		if (toAddAfter != v)
			v.replaceBy(null);
	}
	
	@Override
	public void visitArg(Arg arg) {
		Declarator d = arg.getDeclarator();
		if (d != null && !(d instanceof DirectDeclarator)) {
			MutableByDeclarator type = d.mutateType(arg.getValueType());
			if (type instanceof TypeRef) {
				arg.setValueType((TypeRef)type);
				arg.setDeclarator(new DirectDeclarator(d.resolveName(), d.getBits(), arg.getDefaultValue()));
			} else {
				type = null;
			}
		}
		super.visitArg(arg);
	}
    
    private static final boolean mutateDeclaratorTypes = true;
            
	@Override
	public void visitVariablesDeclaration(VariablesDeclaration v) {
		super.visitVariablesDeclaration(v);
		
		Element toAddAfter = v;
		
		/// Explode comma-separated variables declarations
        int nDecl = v.getDeclarators().size();
        
        for (Declarator vs : v.getDeclarators()) {
			if (vs == null || vs instanceof DirectDeclarator && nDecl == 1)
				continue;
			
			Declaration decl = null;
		
            
			Declarator.MutableByDeclarator mutatedType = vs.mutateType(v.getValueType());
			if (mutatedType instanceof Function) {
				Function f = (Function)mutatedType;
				f.setName(new SimpleIdentifier(vs.resolveName()));
				decl = (Function)mutatedType;
				decl.importDetails(v, false);
				decl.importDetails(vs, false);
			} else {
                if (mutateDeclaratorTypes) {
                    if (mutatedType instanceof TypeRef) {
                        TypeRef tr = (TypeRef)mutatedType;
                        decl = new VariablesDeclaration(tr, new DirectDeclarator(vs.resolveName(), vs.getBits(), vs.getDefaultValue()));
                        decl.importDetails(v, false);
                        decl.importDetails(vs, false);
                        decl.importDetails(tr, true);
                    } else if (mutatedType instanceof Declaration) {
                        decl = (Declaration)mutatedType;
                    }
                }
                if (decl == null) {
                    TypeRef vt = v.getValueType();
                    decl = new VariablesDeclaration(vt == null ? null : vt.clone(), vs.clone());
					decl.importDetails(v, false);
					decl.importDetails(vs, false);
					decl.importDetails(v.getValueType(), true);
                }
			}
			
			toAddAfter.insertSibling(decl, false);
			toAddAfter = decl;

			//decl.accept(this);//super.visitVariablesDeclaration(decl);
		}
		if (toAddAfter != v)
			v.replaceBy(null);
	}
	
}
