import ch.qos.logback.core.UnsynchronizedAppenderBase;
import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.recovery.ResilientFileOutputStream;
import ch.qos.logback.core.status.ErrorStatus;
import ch.qos.logback.core.util.FileUtil;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.locks.ReentrantLock;


/**
 * limit log count in the file log
 * Created by RoyChan on 2018/2/26.
 */
public class CountingFileAppender<E> extends UnsynchronizedAppenderBase<E> {

    /**
     * All synchronization in this class is done via the lock object.
     */
    protected final ReentrantLock lock = new ReentrantLock(true);

    /**
     * limit log count
     */
    static int DEFAULT_LIMIT = 10;
    int counter = 0;
    int limit = DEFAULT_LIMIT;

    /**
     * The name of the active log file.
     */
    protected String fileName = null;

    /**
     * This is the {@link OutputStream outputStream} where output will be written.
     */
    private OutputStream outputStream;
    protected boolean append = false;

    //format output in a special pattern
    protected Encoder<E> encoder;

    /**
     * logger init
     * safetly check
     */
    public void start() {
        int errors = 0;

        if (getFile() != null) {
            addInfo("File property is set to [" + fileName + "]");

            try {
                openFile(getFile());
            } catch (java.io.IOException e) {
                errors++;
                addError("openFile(" + fileName + ") call failed.", e);
            }
        } else {
            errors++;
            addError("\"File\" property not set for appender named [" + name + "].");
        }

        // only error free appenders should be activated
        if (errors == 0) {
            super.start();
        }
    }

    /**
     * log output
     * core implement method
     * @param evt
     */
    @Override
    protected void append(E evt) {
        if (!isStarted()) {
            return;
        }
        try {
            // the synchronization prevents the OutputStream from being closed while we
            // are writing. It also prevents multiple threads from entering the same
            // converter. Converters assume that they are in a synchronized block.
            lock.lock();
            try {
                if (counter >= limit) {
                    return;
                }
                this.encoder.doEncode(evt);
                counter++;
            } finally {
                lock.unlock();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            addStatus(new ErrorStatus("IO failure in appender", this, ex));
        }
    }

    public void openFile(String file_name) throws IOException {
        lock.lock();
        try {
            File file = new File(file_name);
            boolean result = FileUtil.createMissingParentDirectories(file);
            if (!result) {
                addError("Failed to create parent directories for [" + file.getAbsolutePath() + "]");
            }

            ResilientFileOutputStream resilientFos = new ResilientFileOutputStream(file, append);
            resilientFos.setContext(context);
            setOutputStream(resilientFos);
        } finally {
            lock.unlock();
        }
    }

    public void setOutputStream(OutputStream outputStream) {
        lock.lock();
        try {
            // close any previously opened output stream
            closeOutputStream();

            this.outputStream = outputStream;
            if (encoder == null) {
                addWarn("Encoder has not been set. Cannot invoke its init method.");
                return;
            }

            encoderInit();
        } finally {
            lock.unlock();
        }
    }

    protected void closeOutputStream() {
        if (this.outputStream != null) {
            try {
                // before closing we have to output out layout's footer
                encoderClose();
                this.outputStream.close();
                this.outputStream = null;
            } catch (IOException e) {
                addStatus(new ErrorStatus("Could not close output stream for OutputStreamAppender.", this, e));
            }
        }
    }

    void encoderInit() {
        if (encoder != null && this.outputStream != null) {
            try {
                encoder.init(outputStream);
            } catch (IOException ioe) {
                this.started = false;
                addStatus(new ErrorStatus("Failed to initialize encoder for appender named [" + name + "].", this, ioe));
            }
        }
    }

    void encoderClose() {
        if (encoder != null && this.outputStream != null) {
            try {
                encoder.close();
            } catch (IOException ioe) {
                this.started = false;
                addStatus(new ErrorStatus("Failed to write footer for appender named [" + name + "].", this, ioe));
            }
        }
    }

    public int getLimit() {
        return limit;
    }

    public void setLimit(int limit) {
        this.limit = limit;
    }

    public String getFile() {
        return fileName;
    }

    public void setFile(String file) {
        if (file == null) {
            fileName = file;
        } else {
            // Trim spaces from both ends. The users probably does not want
            // trailing spaces in file names.
            fileName = file.trim();
        }
    }

    public Encoder<E> getEncoder() {
        return encoder;
    }

    public void setEncoder(Encoder<E> encoder) {
        this.encoder = encoder;
    }

}