/*
 *
 * 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 org.apache.qpid.server.management.plugin.log;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.log4j.Appender;
import org.apache.log4j.DailyRollingFileAppender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.QpidCompositeRollingAppender;
import org.apache.log4j.RollingFileAppender;
import org.apache.log4j.varia.ExternallyRolledFileAppender;
import org.apache.qpid.test.utils.QpidTestCase;
import org.apache.qpid.test.utils.TestFileUtils;
import org.apache.qpid.util.FileUtils;

public class LogFileHelperTest extends QpidTestCase
{
    private Map<String, List<File>> _appendersFiles;
    private File _compositeRollingAppenderBackupFolder;
    private List<Appender> _appenders;
    private LogFileHelper _helper;

    public void setUp() throws Exception
    {
        super.setUp();
        _appendersFiles = new HashMap<String, List<File>>();
        _compositeRollingAppenderBackupFolder = new File(TMP_FOLDER, "_compositeRollingAppenderBackupFolder");
        _compositeRollingAppenderBackupFolder.mkdirs();

        _appendersFiles.put(FileAppender.class.getSimpleName(),
                Collections.singletonList(TestFileUtils.createTempFile(this, ".log", "FileAppender")));
        _appendersFiles.put(DailyRollingFileAppender.class.getSimpleName(),
                Collections.singletonList(TestFileUtils.createTempFile(this, ".log", "DailyRollingFileAppender")));
        _appendersFiles.put(RollingFileAppender.class.getSimpleName(),
                Collections.singletonList(TestFileUtils.createTempFile(this, ".log", "RollingFileAppender")));
        _appendersFiles.put(ExternallyRolledFileAppender.class.getSimpleName(),
                Collections.singletonList(TestFileUtils.createTempFile(this, ".log", "ExternallyRolledFileAppender")));

        File file = TestFileUtils.createTempFile(this, ".log", "QpidCompositeRollingAppender");
        File backUpFile = File.createTempFile(file.getName() + ".", ".1." + LogFileHelper.GZIP_EXTENSION);
        _appendersFiles.put(QpidCompositeRollingAppender.class.getSimpleName(), Arrays.asList(file, backUpFile));

        FileAppender fileAppender = new FileAppender();
        DailyRollingFileAppender dailyRollingFileAppender = new DailyRollingFileAppender();
        RollingFileAppender rollingFileAppender = new RollingFileAppender();
        ExternallyRolledFileAppender externallyRolledFileAppender = new ExternallyRolledFileAppender();
        QpidCompositeRollingAppender qpidCompositeRollingAppender = new QpidCompositeRollingAppender();
        qpidCompositeRollingAppender.setbackupFilesToPath(_compositeRollingAppenderBackupFolder.getPath());

        _appenders = new ArrayList<Appender>();
        _appenders.add(fileAppender);
        _appenders.add(dailyRollingFileAppender);
        _appenders.add(rollingFileAppender);
        _appenders.add(externallyRolledFileAppender);
        _appenders.add(qpidCompositeRollingAppender);

        for (Appender appender : _appenders)
        {
            FileAppender fa = (FileAppender) appender;
            fa.setName(fa.getClass().getSimpleName());
            fa.setFile(_appendersFiles.get(fa.getClass().getSimpleName()).get(0).getPath());
        }

        _helper = new LogFileHelper(_appenders);
    }

    public void tearDown() throws Exception
    {
        try
        {
            for (List<File> files : _appendersFiles.values())
            {
                for (File file : files)
                {
                    try
                    {
                        FileUtils.delete(file, false);
                    }
                    catch (Exception e)
                    {
                        // ignore
                    }
                }
            }
            FileUtils.delete(_compositeRollingAppenderBackupFolder, true);
        }
        finally
        {
            super.tearDown();
        }
    }

    public void testGetLogFileDetailsWithLocations() throws Exception
    {
        List<LogFileDetails> details = _helper.getLogFileDetails(true);

        assertLogFiles(details, true);
    }

    public void testGetLogFileDetailsWithoutLocations() throws Exception
    {
        List<LogFileDetails> details = _helper.getLogFileDetails(false);

        assertLogFiles(details, false);
    }

    public void testWriteLogFilesForAllLogs() throws Exception
    {
        List<LogFileDetails> details = _helper.getLogFileDetails(true);
        File f = TestFileUtils.createTempFile(this, ".zip");

        FileOutputStream os = new FileOutputStream(f);
        try
        {
            _helper.writeLogFiles(details, os);
        }
        finally
        {
            if (os != null)
            {
                os.close();
            }
        }

        assertWrittenFile(f, details);
    }

    public void testWriteLogFile() throws Exception
    {
        File file = _appendersFiles.get(FileAppender.class.getSimpleName()).get(0);

        File f = TestFileUtils.createTempFile(this, ".log");
        FileOutputStream os = new FileOutputStream(f);
        try
        {
            _helper.writeLogFile(file, os);
        }
        finally
        {
            if (os != null)
            {
                os.close();
            }
        }

        assertEquals("Unexpected log content", FileAppender.class.getSimpleName(), FileUtils.readFileAsString(f));
    }

    public void testFindLogFileDetails()
    {
        String[] logFileDisplayedPaths = new String[6];
        File[] files = new File[logFileDisplayedPaths.length];
        int i = 0;
        for (Map.Entry<String, List<File>> entry : _appendersFiles.entrySet())
        {
            String appenderName = entry.getKey();
            List<File> appenderFiles = entry.getValue();
            for (File logFile : appenderFiles)
            {
                logFileDisplayedPaths[i] = appenderName + "/" + logFile.getName();
                files[i++] = logFile;
            }
        }

        List<LogFileDetails> logFileDetails = _helper.findLogFileDetails(logFileDisplayedPaths);
        assertEquals("Unexpected details size", logFileDisplayedPaths.length, logFileDetails.size());

        boolean gzipFileFound = false;
        for (int j = 0; j < logFileDisplayedPaths.length; j++)
        {
            String displayedPath = logFileDisplayedPaths[j];
            String[] parts = displayedPath.split("/");
            LogFileDetails d = logFileDetails.get(j);
            assertEquals("Unexpected name", parts[1], d.getName());
            assertEquals("Unexpected appender", parts[0], d.getAppenderName());
            if (files[j].getName().endsWith(LogFileHelper.GZIP_EXTENSION))
            {
                assertEquals("Unexpected mime type for gz file", LogFileHelper.GZIP_MIME_TYPE, d.getMimeType());
                gzipFileFound = true;
            }
            else
            {
                assertEquals("Unexpected mime type", LogFileHelper.TEXT_MIME_TYPE, d.getMimeType());
            }
            assertEquals("Unexpected file location", files[j], d.getLocation());
            assertEquals("Unexpected file size", files[j].length(), d.getSize());
            assertEquals("Unexpected file last modified date", files[j].lastModified(), d.getLastModified());
        }
        assertTrue("Gzip log file is not found", gzipFileFound);
    }

    public void testFindLogFileDetailsForNotExistingAppender()
    {
        String[] logFileDisplayedPaths = { "NotExistingAppender/qpid.log" };
        List<LogFileDetails> details = _helper.findLogFileDetails(logFileDisplayedPaths);
        assertTrue("No details should be created for non-existing appender", details.isEmpty());
    }

    public void testFindLogFileDetailsForNotExistingFile()
    {
        String[] logFileDisplayedPaths = { "FileAppender/qpid-non-existing.log" };
        List<LogFileDetails> details = _helper.findLogFileDetails(logFileDisplayedPaths);
        assertTrue("No details should be created for non-existing file", details.isEmpty());
    }

    public void testFindLogFileDetailsForIncorrectlySpecifiedLogFilePath()
    {
        String[] logFileDisplayedPaths = { "FileAppender\\" + _appendersFiles.get("FileAppender").get(0).getName() };
        try
        {
            _helper.findLogFileDetails(logFileDisplayedPaths);
            fail("Exception is expected for incorrectly set path to log file");
        }
        catch (IllegalArgumentException e)
        {
            // pass
        }
    }

    private void assertLogFiles(List<LogFileDetails> details, boolean includeLocation)
    {
        for (Map.Entry<String, List<File>> appenderData : _appendersFiles.entrySet())
        {
            String appenderName = (String) appenderData.getKey();
            List<File> files = appenderData.getValue();

            for (File logFile : files)
            {
                String logFileName = logFile.getName();
                LogFileDetails d = findLogFileDetails(logFileName, appenderName, details);
                assertNotNull("Log file " + logFileName + " is not found for appender " + appenderName, d);
                if (includeLocation)
                {
                    assertEquals("Log file " + logFileName + " is different in appender " + appenderName, d.getLocation(),
                            logFile);
                }
            }
        }
    }

    private LogFileDetails findLogFileDetails(String logFileName, String appenderName, List<LogFileDetails> logFileDetails)
    {
        LogFileDetails d = null;
        for (LogFileDetails lfd : logFileDetails)
        {
            if (lfd.getName().equals(logFileName) && lfd.getAppenderName().equals(appenderName))
            {
                d = lfd;
                break;
            }
        }
        return d;
    }

    private void assertWrittenFile(File f, List<LogFileDetails> details) throws FileNotFoundException, IOException
    {
        FileInputStream fis = new FileInputStream(f);
        try
        {
            ZipInputStream zis = new ZipInputStream(fis);
            ZipEntry ze = zis.getNextEntry();

            while (ze != null)
            {
                String entryName = ze.getName();
                String[] parts = entryName.split("/");

                String appenderName = parts[0];
                String logFileName = parts[1];

                LogFileDetails d = findLogFileDetails(logFileName, appenderName, details);

                assertNotNull("Unexpected entry " + entryName, d);
                details.remove(d);

                File logFile = d.getLocation();
                String logContent = FileUtils.readFileAsString(logFile);

                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int len;
                while ((len = zis.read(buffer)) > 0)
                {
                    baos.write(buffer, 0, len);
                }
                baos.close();

                assertEquals("Unexpected log file content", logContent, baos.toString());

                ze = zis.getNextEntry();
            }

            zis.closeEntry();
            zis.close();

        }
        finally
        {
            if (fis != null)
            {
                fis.close();
            }
        }
        assertEquals("Not all log files have been output", 0, details.size());
    }

}
