package org.apache.log4j;

import java.text.*;
import java.io.*;
import org.apache.log4j.helpers.*;
import java.util.*;
import java.util.zip.GZIPOutputStream;

import org.apache.log4j.spi.*;

/**
 * 替换log4j包里的DailyRollingFileAppender类，
 * 增加文件压缩流程，
 * @author xu.zhou
 * @date 20211108
 */
public class CompressDailyRollingFileAppender extends FileAppender
{
    static final int TOP_OF_TROUBLE = -1;
    static final int TOP_OF_MINUTE = 0;
    static final int TOP_OF_HOUR = 1;
    static final int HALF_DAY = 2;
    static final int TOP_OF_DAY = 3;
    static final int TOP_OF_WEEK = 4;
    static final int TOP_OF_MONTH = 5;
    private String datePattern;
    private String scheduledFilename;
    private long nextCheck;
    Date now;
    SimpleDateFormat sdf;
    RollingCalendar rc;
    int checkPeriod;
    static final TimeZone gmtTimeZone;
    
    public CompressDailyRollingFileAppender() {
        this.datePattern = "'.'yyyy-MM-dd";
        this.nextCheck = System.currentTimeMillis() - 1L;
        this.now = new Date();
        this.rc = new RollingCalendar();
        this.checkPeriod = -1;
    }
    
    public CompressDailyRollingFileAppender(final Layout layout, final String filename, final String datePattern) throws IOException {
        super(layout, filename, true);
        this.datePattern = "'.'yyyy-MM-dd";
        this.nextCheck = System.currentTimeMillis() - 1L;
        this.now = new Date();
        this.rc = new RollingCalendar();
        this.checkPeriod = -1;
        this.datePattern = datePattern;
        this.activateOptions();
    }
    
    public void setDatePattern(final String pattern) {
        this.datePattern = pattern;
    }
    
    public String getDatePattern() {
        return this.datePattern;
    }
    
    public void activateOptions() {
        super.activateOptions();
        if (this.datePattern != null && super.fileName != null) {
            this.now.setTime(System.currentTimeMillis());
            this.sdf = new SimpleDateFormat(this.datePattern);
            final int type = this.computeCheckPeriod();
            this.printPeriodicity(type);
            this.rc.setType(type);
            final File file = new File(super.fileName);
            this.scheduledFilename = super.fileName + this.sdf.format(new Date(file.lastModified()));
        }
        else {
            LogLog.error("Either File or DatePattern options are not set for appender [" + super.name + "].");
        }
    }
    
    void printPeriodicity(final int type) {
        switch (type) {
            case 0: {
                LogLog.debug("Appender [" + super.name + "] to be rolled every minute.");
                break;
            }
            case 1: {
                LogLog.debug("Appender [" + super.name + "] to be rolled on top of every hour.");
                break;
            }
            case 2: {
                LogLog.debug("Appender [" + super.name + "] to be rolled at midday and midnight.");
                break;
            }
            case 3: {
                LogLog.debug("Appender [" + super.name + "] to be rolled at midnight.");
                break;
            }
            case 4: {
                LogLog.debug("Appender [" + super.name + "] to be rolled at start of week.");
                break;
            }
            case 5: {
                LogLog.debug("Appender [" + super.name + "] to be rolled at start of every month.");
                break;
            }
            default: {
                LogLog.warn("Unknown periodicity for appender [" + super.name + "].");
                break;
            }
        }
    }
    
    int computeCheckPeriod() {
        final RollingCalendar rollingCalendar = new RollingCalendar(CompressDailyRollingFileAppender.gmtTimeZone, Locale.ENGLISH);
        final Date epoch = new Date(0L);
        if (this.datePattern != null) {
            for (int i = 0; i <= 5; ++i) {
                final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(this.datePattern);
                simpleDateFormat.setTimeZone(CompressDailyRollingFileAppender.gmtTimeZone);
                final String r0 = simpleDateFormat.format(epoch);
                rollingCalendar.setType(i);
                final Date next = new Date(rollingCalendar.getNextCheckMillis(epoch));
                final String r2 = simpleDateFormat.format(next);
                if (r0 != null && r2 != null && !r0.equals(r2)) {
                    return i;
                }
            }
        }
        return -1;
    }
    
    /**
     * 日志滚动
     * @throws IOException
     */
    void rollOver() throws IOException {
        if (this.datePattern == null) {
            super.errorHandler.error("Missing DatePattern option in rollOver().");
            return;
        }
        String dateStr = this.sdf.format(this.now);
        final String datedFilename = super.fileName + dateStr;
        //要删除的文件是否与最后的日志文件相同，说明此文件还不要处理，正在写入
        if (this.scheduledFilename.equals(datedFilename)) {
            return;
        }
        this.closeFile();
        //生成要滚动的文件
        final File target = new File(this.scheduledFilename);
        if (target.exists()) {
        	//System.out.println("删除文件=>"+target.getName());
            target.delete();
        }
        //System.out.println("datePattern=>"+datePattern);
        //System.out.println("datedFilename=>"+datedFilename);
        //System.out.println("scheduledFilename=>"+this.scheduledFilename);
        //System.out.println("super.fileName=>"+super.fileName);
        final File file = new File(super.fileName);
        //把正在写入的日志文件更名为带日期的日志文件
        final boolean result = file.renameTo(target);
        if (result) {
            LogLog.debug(super.fileName + " -> " + this.scheduledFilename);
        }
        else {
            LogLog.error("Failed to rename [" + super.fileName + "] to [" + this.scheduledFilename + "].");
        }
        try {
            this.setFile(super.fileName, false, super.bufferedIO, super.bufferSize);
        }
        catch (IOException e) {
            super.errorHandler.error("setFile(" + super.fileName + ", false) call failed.");
        }
        
        //压缩日志后删除文件
        zipfile(this.scheduledFilename);
        
        this.scheduledFilename = datedFilename;
        
    }
    /**
     * 压缩日志后删除文件
     * @param fileName
     */
    private void zipfile(String fileName){
    	//System.out.println("zipfile.fileName========================"+fileName);
    	 File file = new File(fileName);
    	  
    	 //creat zip output stream to build zip file
    	 GZIPOutputStream gzout = null;
    	 FileInputStream fin = null;
    	 byte[] buf = new byte[1024];
    	  
        //file -> gz
        try {
             fin = new FileInputStream(file);
             gzout = new GZIPOutputStream(new FileOutputStream(fileName + ".gz"));
    	   
    	     int num;
    	     while ((num = fin.read(buf, 0, buf.length)) != -1) {
    	    	 gzout.write(buf, 0, num);
    	     }
    	     gzout.flush();
    	     gzout.finish();
    	  
    	     LogLog.debug(fileName + " -> " + fileName + ".gz" + " successful!");
    	 } catch (IOException e) {
    		 LogLog.error("add gz file(" + fileName + ".gz" + ") failed.");
    	 } finally {
             try {
                 if (gzout != null) {
                    gzout.close();
                }
                if (fin != null)
                	fin.close();
             } catch (IOException e) {
            	 LogLog.error("close Stream failed");
             }
    	 }
    	
    	//delete old file
    	 file.delete();
    }
    
    protected void subAppend(final LoggingEvent event) {
        final long n = System.currentTimeMillis();
        if (n >= this.nextCheck) {
            this.now.setTime(n);
            this.nextCheck = this.rc.getNextCheckMillis(this.now);
            try {
                this.rollOver();
            }
            catch (IOException ioe) {
                LogLog.error("rollOver() failed.", ioe);
            }
        }
        super.subAppend(event);
    }
    
    static {
        gmtTimeZone = TimeZone.getTimeZone("GMT");
    }
}
