using System.Collections.Generic;
using CodeStatistics.Handling.Languages.Java.Elements;
using CodeStatistics.Handling.Languages.Java.Utils;
using CodeStatisticsCore.Input;

namespace CodeStatistics.Handling.Languages.Java{
    class JavaState{
        private readonly Dictionary<File,JavaFileInfo> fileInfo = new Dictionary<File,JavaFileInfo>();
        private readonly HashSet<string> packages = new HashSet<string>();

        public readonly JavaGlobalInfo GlobalInfo = new JavaGlobalInfo();

        public int PackageCount { get { return packages.Count; } }

        public JavaFileInfo GetFile(File file){
            return fileInfo[file];
        }

        public JavaFileInfo Process(File file){
            JavaFileInfo info = new JavaFileInfo();
            fileInfo.Add(file,info);

            JavaCodeParser parser = new JavaCodeParser(JavaParseUtils.PrepareCodeFile(file.Contents));
            parser.AnnotationCallback += IncrementAnnotation;
            parser.CodeBlockCallback += blockParser => ReadCodeBlock(blockParser,GlobalInfo);

            ReadPackage(parser,info);
            ReadImportList(parser,info);
            ReadTopLevelTypes(parser,info);

            UpdateLocalData(info);

            return info;
        }

        private void UpdateLocalData(JavaFileInfo info){
            packages.Add(info.Package);

            foreach(Type type in info.Types){
                UpdateLocalDataForType(type);
            }
        }

        private void UpdateLocalDataForType(Type type){
            foreach(Type nestedType in type.NestedTypes){
                UpdateLocalDataForType(nestedType);
            }

            foreach(Field field in type.GetData().Fields){
                GlobalInfo.FieldTypes.Increment(field.Type.ToStringGeneral());
            }

            foreach(Method method in type.GetData().Methods){
                GlobalInfo.MethodReturnTypes.Increment(method.ReturnType.ToStringGeneral());

                foreach(TypeOf parameterType in method.ParameterTypes){
                    GlobalInfo.MethodParameterTypes.Increment(parameterType.ToStringGeneral());
                }
            }
        }

        private void IncrementAnnotation(Annotation annotation){
            GlobalInfo.AnnotationUses.Increment(annotation.SimpleName);
        }

        private static void ReadPackage(JavaCodeParser parser, JavaFileInfo info){
            parser.SkipSpaces();
            parser.SkipReadAnnotationList();
            parser.SkipSpaces();

            info.Package = parser.ReadPackageDeclaration();
        }

        private static void ReadImportList(JavaCodeParser parser, JavaFileInfo info){
            while(true){
                parser.SkipSpaces();

                Import? import = parser.ReadImportDeclaration();
                if (!import.HasValue)break;

                info.Imports.Add(import.Value);
            }
        }

        private static void ReadTopLevelTypes(JavaCodeParser parser, JavaFileInfo info){
            while(true){
                parser.SkipSpaces();
                if (parser.IsEOF)break;

                Type type = parser.ReadType();

                if (type != null)info.Types.Add(type);
                else break;
            }
        }

        private static void ReadCodeBlock(JavaCodeBlockParser blockParser, JavaGlobalInfo info){
            string keyword;
            
            while((keyword = blockParser.ReadNextKeywordSkip()).Length > 0){
                switch(keyword){
                    case "do":
                        ReadCodeBlock((JavaCodeBlockParser)blockParser.ReadBlock('{','}'),info);

                        if (blockParser.ReadNextKeywordSkip() != "while"){
                            blockParser.RevertKeywordSkip(); // should not happen
                        }

                        ++info.Statements[FlowStatement.DoWhile];
                        break;

                    case "while":
                        ++info.Statements[FlowStatement.While];
                        break;

                    case "for":
                        JavaCodeBlockParser cycleDeclBlock = (JavaCodeBlockParser)blockParser.ReadBlock('(',')');
                        ReadCodeBlock(cycleDeclBlock,info);
                        
                        JavaCodeParser cycleDeclParser = new JavaCodeParser(cycleDeclBlock.Contents);
                        cycleDeclParser.ReadTypeOf(false);
                        cycleDeclParser.SkipSpaces().ReadIdentifier();

                        ++info.Statements[cycleDeclParser.SkipSpaces().Char == ':' ? FlowStatement.EnhancedFor : FlowStatement.For];
                        break;

                    case "switch":
                        JavaCodeBlockParser switchBlock = (JavaCodeBlockParser)blockParser.ReadBlock('{','}');
                        ReadCodeBlock(switchBlock,info);

                        switchBlock.Reset();
                        string switchKeyword;
                        int cases = 0;

                        while((switchKeyword = switchBlock.ReadNextKeywordSkip()).Length > 0){
                            if (switchKeyword == "switch"){
                                switchBlock.SkipBlock('{','}');
                            }
                            else if (switchKeyword == "case"){
                                ++info.Statements[FlowStatement.SwitchCase];
                                ++cases;
                            }
                            else if (switchKeyword == "default"){
                                ++info.Statements[FlowStatement.SwitchDefault]; // TODO count as a case?
                            }
                        }

                        if (cases < info.MinSwitchCases)info.MinSwitchCases = cases;
                        if (cases > info.MaxSwitchCases)info.MaxSwitchCases = cases;

                        ++info.Statements[FlowStatement.Switch];
                        break;

                    case "try":
                        bool isTryWithResources = blockParser.SkipSpaces().Char == '(';

                        ReadCodeBlock((JavaCodeBlockParser)blockParser.ReadBlock('{','}'),info);

                        string tryKeyword;
                        int catches = 0;

                        while((tryKeyword = blockParser.ReadNextKeywordSkip()).Length > 0){
                            if (tryKeyword == "catch"){
                                ++info.Statements[FlowStatement.Catch];
                                ++catches;
                            }
                            else if (tryKeyword == "finally"){
                                ++info.Statements[FlowStatement.Finally];
                                
                                if (isTryWithResources)++info.TryWithResourcesWithFinally;
                                else ++info.TryCatchWithFinally;
                            }
                            else if (tryKeyword != "try"){
                                blockParser.RevertKeywordSkip();
                                break;
                            }

                            ReadCodeBlock((JavaCodeBlockParser)blockParser.ReadBlock('{','}'),info);
                        }
                        
                        if (catches < info.MinCatchBlocks)info.MinCatchBlocks = catches;
                        if (catches > info.MaxCatchBlocks)info.MaxCatchBlocks = catches;

                        ++info.Statements[isTryWithResources ? FlowStatement.TryWithResources : FlowStatement.TryCatch];
                        ++info.Statements[FlowStatement.Try];
                        break;

                    case "if":
                        ++info.Statements[FlowStatement.If];
                        break;

                    case "else":
                        ++info.Statements[FlowStatement.Else];

                        if (blockParser.ReadNextKeywordSkip() != "if"){
                            blockParser.RevertKeywordSkip();
                        }

                        break;

                    case "return":
                        ++info.Statements[FlowStatement.Return];
                        break;

                    case "continue":
                        ++info.Statements[FlowStatement.Continue];
                        break;

                    case "break":
                        ++info.Statements[FlowStatement.Break];
                        break;
                }
            }
        }
    }
}