package br.ufrgs.inf.prosoft.tigris.monitoring.storage.providers;

import br.ufrgs.inf.prosoft.tigris.exceptions.StorageException;
import br.ufrgs.inf.prosoft.tigris.monitoring.metadata.LogTrace;
import br.ufrgs.inf.prosoft.tigris.monitoring.storage.Repository;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * The type Async file repository.
 */
public class AsyncFileRepository implements Runnable, Repository<LogTrace> {
    private final File file;
    private final Writer out;
    private final BlockingQueue<Item> queue = new LinkedBlockingQueue<Item>();
    private final ObjectMapper mapper;
    private volatile boolean started = false;
    private volatile boolean stopped = false;

    /**
     * Instantiates a new Async file repository.
     *
     * @param file the file
     * @throws IOException the io exception
     */
    public AsyncFileRepository(File file) throws IOException {
        this.file = file;
        this.out = new BufferedWriter(new java.io.FileWriter(file));
        this.mapper = new ObjectMapper();
    }

    /**
     * Append.
     *
     * @param seq the seq
     */
    public void append(CharSequence seq) {
        if (!started) {
            throw new IllegalStateException("open() call expected before append()");
        }
        try {
            queue.put(new CharSeqItem(seq));
        } catch (InterruptedException ignored) {
        }
    }

    /**
     * Indent.
     *
     * @param indent the indent
     */
    public void indent(int indent) {
        if (!started) {
            throw new IllegalStateException("open() call expected before append()");
        }
        try {
            queue.put(new IndentItem(indent));
        } catch (InterruptedException ignored) {
        }
    }

    /**
     * Open.
     */
    public void open() {
        this.started = true;
        new Thread(this).start();
    }

    public void run() {
        while (!stopped) {
            try {
                Item item = queue.poll(100, TimeUnit.MICROSECONDS);
                if (item != null) {
                    try {
                        item.write(out);
                    } catch (IOException logme) {
                    }
                }
            } catch (InterruptedException e) {
            }
        }
        try {
            out.close();
        } catch (IOException ignore) {
        }
    }

    /**
     * Close.
     */
    public void close() {
        this.stopped = true;
    }

    @Override
    public void save(LogTrace logTrace) throws StorageException {
        try {
            append(mapper.writeValueAsString(logTrace));
        } catch (Exception e) {
            throw new StorageException("Json serialization fail", e);
        }
    }

    @Override
    public List<LogTrace> findAll() {
        return null;
    }

    @Override
    public void removeAll() {

    }

    @Override
    public void saveAll(List<LogTrace> toSave) {

    }

    private static interface Item {
        /**
         * Write.
         *
         * @param out the out
         * @throws IOException the io exception
         */
        void write(Writer out) throws IOException;
    }

    private static class CharSeqItem implements Item {
        private final CharSequence sequence;

        /**
         * Instantiates a new Char seq item.
         *
         * @param sequence the sequence
         */
        public CharSeqItem(CharSequence sequence) {
            this.sequence = sequence;
        }

        public void write(Writer out) throws IOException {
            out.append(sequence);
        }
    }

    private static class IndentItem implements Item {
        private final int indent;

        /**
         * Instantiates a new Indent item.
         *
         * @param indent the indent
         */
        public IndentItem(int indent) {
            this.indent = indent;
        }

        public void write(Writer out) throws IOException {
            for (int i = 0; i < indent; i++) {
                out.append(" ");
            }
        }
    }
}
