/*
 *  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 groovy.util.logging

import groovy.test.GroovyTestCase

import java.lang.reflect.Field
import java.lang.reflect.Modifier
import java.util.logging.*
import groovy.mock.interceptor.MockFor
import org.codehaus.groovy.control.MultipleCompilationErrorsException

/**
 * Test to make sure the @Log annotation is working correctly. 
 */
class LogTest extends GroovyTestCase {

    void testPrivateFinalStaticLogFieldAppears() {

        Class clazz = new GroovyClassLoader().parseClass("""
          @groovy.util.logging.Log
          class MyClass {
          } """)

        assert clazz.declaredFields.find { Field field ->
            field.name == "log" &&
                    Modifier.isPrivate(field.getModifiers()) &&
                    Modifier.isStatic(field.getModifiers()) &&
                    Modifier.isTransient(field.getModifiers()) &&
                    Modifier.isFinal(field.getModifiers())
        }
    }

    void testPrivateFinalStaticNamedLogFieldAppears() {

        Class clazz = new GroovyClassLoader().parseClass("""
          @groovy.util.logging.Log('logger')
          class MyClass {
          } """)

        assert clazz.declaredFields.find { Field field ->
            field.name == "logger" &&
                    Modifier.isPrivate(field.getModifiers()) &&
                    Modifier.isStatic(field.getModifiers()) &&
                    Modifier.isTransient(field.getModifiers()) &&
                    Modifier.isFinal(field.getModifiers())
        }
    }

    void testExplicitPrivateFinalStaticLogFieldAppears() {
        Class clazz = new GroovyClassLoader().parseClass('''
            import static groovy.transform.options.Visibility.*
            @groovy.transform.VisibilityOptions(value = PRIVATE)
            @groovy.util.logging.Log
            class MyClass {
            } ''')

        assert clazz.declaredFields.find { Field field ->
            field.name == "log" &&
                    Modifier.isPrivate(field.getModifiers()) &&
                    Modifier.isStatic(field.getModifiers()) &&
                    Modifier.isTransient(field.getModifiers()) &&
                    Modifier.isFinal(field.getModifiers())
        }
    }

    void testPackagePrivateFinalStaticLogFieldAppears() {
        Class clazz = new GroovyClassLoader().parseClass('''
            import static groovy.transform.options.Visibility.*
            @groovy.transform.VisibilityOptions(value = PACKAGE_PRIVATE)
            @groovy.util.logging.Log
            class MyClass {
            } ''')

        assert clazz.declaredFields.find { Field field ->
            field.name == "log" &&
                    !Modifier.isPrivate(field.getModifiers()) &&
                    !Modifier.isProtected(field.getModifiers()) &&
                    !Modifier.isPublic(field.getModifiers()) &&
                    Modifier.isStatic(field.getModifiers()) &&
                    Modifier.isTransient(field.getModifiers()) &&
                    Modifier.isFinal(field.getModifiers())
        }
    }

    void testProtectedFinalStaticLogFieldAppears() {
        Class clazz = new GroovyClassLoader().parseClass('''
            import static groovy.transform.options.Visibility.*
            @groovy.transform.VisibilityOptions(value = PROTECTED)
            @groovy.util.logging.Log
            class MyClass {
            } ''')

        assert clazz.declaredFields.find { Field field ->
            field.name == "log" &&
                    Modifier.isProtected(field.getModifiers()) &&
                    Modifier.isStatic(field.getModifiers()) &&
                    Modifier.isTransient(field.getModifiers()) &&
                    Modifier.isFinal(field.getModifiers())
        }
    }

    void testPublicFinalStaticLogFieldAppears() {
        Class clazz = new GroovyClassLoader().parseClass('''
            import static groovy.transform.options.Visibility.*
            @groovy.transform.VisibilityOptions(value = PUBLIC)
            @groovy.util.logging.Log
            class MyClass {
            } ''')

        assert clazz.declaredFields.find { Field field ->
            field.name == "log" &&
                    Modifier.isPublic(field.getModifiers()) &&
                    Modifier.isStatic(field.getModifiers()) &&
                    Modifier.isTransient(field.getModifiers()) &&
                    Modifier.isFinal(field.getModifiers())
        }
    }

    void testClassAlreadyHasLogField() {

        shouldFail {

            Class clazz = new GroovyClassLoader().parseClass("""
              @groovy.util.logging.Log
              class MyClass {
                  String log
              } """)

            assert clazz.newInstance()
        }
    }

    void testClassAlreadyHasNamedLogField() {

        shouldFail {

            Class clazz = new GroovyClassLoader().parseClass("""
              @groovy.util.logging.Log('logger')
              class MyClass {
                  String logger
              } """)

            assert clazz.newInstance()
        }
    }

    void testLogFromStaticMethods() {
        Class clazz = new GroovyClassLoader().parseClass("""
            @groovy.util.logging.Log
            class MyClass {
                static loggingMethod() {
                  log.info   ("info    called")
                }
            } """)

        def logSpy = new LoggerSpy()
        def logger = new MockFor(Logger)
        logger.demand.getLogger { logSpy }
        logger.use {
            clazz.loggingMethod()
        }

        assert logSpy.infoParameter == 'info    called'
    }

    void testLogInfo() {

        Class clazz = new GroovyClassLoader().parseClass("""
          @groovy.util.logging.Log
          class MyClass {

              def loggingMethod() {
                log.severe ("severe  called")
                log.warning("warning called")
                log.info   ("info    called")
                log.fine   ("fine    called")
                log.finer  ("finer   called")
                log.finest ("finest  called")
              }
          } """)

        def logSpy = new LoggerSpy()
        def logger = new MockFor(Logger)
        logger.demand.getLogger { logSpy }
        logger.use {
            def s = clazz.newInstance()
            s.loggingMethod()
        }

        assert logSpy.severeParameter == 'severe  called'
        assert logSpy.warningParameter == 'warning called'
        assert logSpy.infoParameter == 'info    called'
        assert logSpy.fineParameter == 'fine    called'
        assert logSpy.finerParameter == 'finer   called'
        assert logSpy.finestParameter == 'finest  called'
    }

    void testLogInfoWithName() {

        Class clazz = new GroovyClassLoader().parseClass("""
          @groovy.util.logging.Log('logger')
          class MyClass {

              def loggingMethod() {
                logger.severe ("severe  called")
                logger.warning("warning called")
                logger.info   ("info    called")
                logger.fine   ("fine    called")
                logger.finer  ("finer   called")
                logger.finest ("finest  called")
              }
          }  """)

        def logSpy = new LoggerSpy()
        def logger = new MockFor(Logger)
        logger.demand.getLogger { logSpy }
        logger.use {
            def s = clazz.newInstance()
            s.loggingMethod()
        }

        assert logSpy.severeParameter == 'severe  called'
        assert logSpy.warningParameter == 'warning called'
        assert logSpy.infoParameter == 'info    called'
        assert logSpy.fineParameter == 'fine    called'
        assert logSpy.finerParameter == 'finer   called'
        assert logSpy.finestParameter == 'finest  called'
    }

    void testLogGuard() {
        Class clazz = new GroovyClassLoader().parseClass("""
               @groovy.util.logging.Log
               class MyClass {
                   def loggingMethod() {
                       log.setLevel(java.util.logging.Level.OFF)
                       log.severe(prepareLogMessage())
                       log.warning(prepareLogMessage())
                       log.info   (prepareLogMessage())
                       log.fine   (prepareLogMessage())
                       log.finer  (prepareLogMessage())
                       log.finest (prepareLogMessage())
                   }

                   def prepareLogMessage() {
                     return "formatted log message"
                   }

               }  """)

        def logSpy = new LoggerSpy()
        def logger = new MockFor(Logger)
        logger.demand.getLogger { logSpy }
        logger.use {
            def s = clazz.newInstance()
            s.loggingMethod()
        }

        assert !logSpy.severeParameter
        assert !logSpy.warningParameter
        assert !logSpy.infoParameter
        assert !logSpy.fineParameter
        assert !logSpy.finerParameter
        assert !logSpy.finestParameter
    }

    void testInheritance() {

        def clazz = new GroovyShell().evaluate("""
            class MyParent {
                private log
            }

            @groovy.util.logging.Log
            class MyClass extends MyParent {

                def loggingMethod() {
                    log.severe(prepareLogMessage())
                    log.warning(prepareLogMessage())
                    log.info   (prepareLogMessage())
                    log.fine   (prepareLogMessage())
                    log.finer  (prepareLogMessage())
                    log.finest (prepareLogMessage())
                }
                def prepareLogMessage() {
                    "formatted log message"
                }
            }

            return MyClass
            """)

        assert clazz.declaredFields.find { Field field ->
            field.name == "log" &&
                    Modifier.isPrivate(field.getModifiers()) &&
                    Modifier.isStatic(field.getModifiers()) &&
                    Modifier.isTransient(field.getModifiers()) &&
                    Modifier.isFinal(field.getModifiers())
        }
    }

    void testInheritance_ProtectedShadowing() {

        shouldFail(MultipleCompilationErrorsException) {
            new GroovyClassLoader().parseClass("""
                class MyParent {
                    protected log
                }

                @groovy.util.logging.Log
                class MyClass extends MyParent {
                } """)
        }
    }

    void testInheritance_PublicShadowing() {

        shouldFail(MultipleCompilationErrorsException) {
            new GroovyClassLoader().parseClass("""
                class MyParent {
                    public log
                }

                @groovy.util.logging.Log
                class MyClass extends MyParent {
                } """)
        }
    }

    void testDefaultCategory() {
        Class clazz = new GroovyClassLoader().parseClass("""
            @groovy.util.logging.Log
            class MyClass {
                static loggingMethod() {
                  log.info("info called")
                }
            }""")
        LogFormatterSpy logFormatterSpy = registerLogFormatterSpy('MyClass')

        clazz.newInstance().loggingMethod()

        assert logFormatterSpy.messageReceived
    }

    void testCustomCategory() {
        String categoryName = 'customCategory'
        Class clazz = new GroovyClassLoader().parseClass("""
            @groovy.util.logging.Log(category='$categoryName')
            class MyClass {
                static loggingMethod() {
                  log.info("info called")
                }
            }""")
        LogFormatterSpy logFormatterSpy = registerLogFormatterSpy(categoryName)

        clazz.newInstance().loggingMethod()

        assert logFormatterSpy.messageReceived
    }

    private LogFormatterSpy registerLogFormatterSpy(String loggerName) {
        Logger logger = Logger.getLogger(loggerName)
        ConsoleHandler handler = new ConsoleHandler()
        LogFormatterSpy loggerNameSpy = new LogFormatterSpy()
        handler.setFormatter(loggerNameSpy)
        logger.addHandler(handler)
        return loggerNameSpy
    }
}

@groovy.transform.PackageScope
class LoggerSpy extends Logger {

    String severeParameter = null
    String warningParameter = null
    String infoParameter = null
    String fineParameter = null
    String finerParameter = null
    String finestParameter = null

    LoggerSpy() {
        super(null, null)
    }

    @Override
    void severe(String s) {
        if (severeParameter) throw new AssertionError("Severe already called once with parameter $severeParameter")
        severeParameter = s
    }

    @Override
    void warning(String s) {
        if (warningParameter) throw new AssertionError("Warning already called once with parameter $warningParameter")
        warningParameter = s
    }

    @Override
    void info(String s) {
        if (infoParameter) throw new AssertionError("Info already called once with parameter $infoParameter")
        infoParameter = s
    }

    @Override
    void fine(String s) {
        if (fineParameter) throw new AssertionError("Fine already called once with parameter $fineParameter")
        fineParameter = s
    }

    @Override
    void finer(String s) {
        if (finerParameter) throw new AssertionError("Finer already called once with parameter $finerParameter")
        finerParameter = s
    }

    @Override
    void finest(String s) {
        if (finestParameter) throw new AssertionError("Finest already called once with parameter $finestParameter")
        finestParameter = s
    }
}

class LogFormatterSpy extends Formatter {

    boolean messageReceived = false

    @Override
    String format(LogRecord record) {
        messageReceived = true
        return record.message
    }
}