package com.jscape.util.io.primitive;

import com.jscape.util.io.DataCodec;

import java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

import static com.jscape.util.Parameters.assertNotNull;

/**
 * @author Alex Usun
 */
public class MapCodec
        implements DataCodec<Map<String, String>> {

    private final Charset charset;
    private final String separator;
    private final RegularState regularState;
    private final EscapedState escapedState;
    private final NewLineState newLineState;

    public MapCodec(Charset charset, String separator) {
        assertNotNull(charset);
        this.charset = charset;

        assertNotNull(separator);
        this.separator = separator;

        this.regularState = new RegularState();
        this.escapedState = new EscapedState();
        this.newLineState = new NewLineState();
    }

    public void write(Map<String, String> data, OutputStream out)
            throws IOException {
        Writer writer = new OutputStreamWriter(out, this.charset);

        for (Map.Entry<String, String> entry : data.entrySet()) {
            writer.write(entry.getKey());
            writer.write(this.separator);
            writer.write(entry.getValue() != null ? entry.getValue() : "");
            writer.write('\n');

        }

        writer.flush();
    }

    public Map<String, String> read(InputStream in)
            throws IOException {
        Reader reader = new InputStreamReader(in, this.charset);
        Map<String, String> ret = new HashMap<>();

        StringBuffer buffer = new StringBuffer();
        String line;
        while ((line = readLine(reader, buffer)) != null) {
            if (line.length() == 0) {
                continue;
            }

            int pos = line.indexOf(this.separator);

            if (pos == -1 || pos == line.length() - 1) {
                ret.put(line, "");
            } else if (pos == 0) {
                ret.put("", line.trim());
            } else {
                ret.put(line.substring(0, pos), line.substring(pos + 1).trim());
            }
        }

        return ret;
    }

    private String readLine(Reader reader, StringBuffer buffer)
            throws IOException {
        int ch;
        ReadState state = new RegularState();

        while ((ch = reader.read()) != -1) {
            state = state.onChar((char) ch, buffer);
            if (!state.continueReading()) {
                break;
            }
        }

        if (ch == -1 && buffer.length() == 0) {
            return null;
        }

        String ret = buffer.toString();
        buffer.setLength(0);

        return ret;
    }


    public interface ReadState {

        ReadState onChar(char ch, StringBuffer buffer);

        boolean continueReading();
    }

    public class RegularState
            implements ReadState {

        public ReadState onChar(char ch, StringBuffer buffer) {
            switch (ch) {
                case '\r':
                    return this;
                case '\\':
                    return escapedState;
                case '\n':
                    return newLineState;
                default:
                    buffer.append(ch);
                    return this;
            }
        }

        public boolean continueReading() {
            return true;
        }
    }

    public class EscapedState
            implements ReadState {

        public ReadState onChar(char ch, StringBuffer buffer) {
            buffer.append(ch);
            return regularState;
        }

        public boolean continueReading() {
            return true;
        }
    }

    public static class NewLineState
            implements ReadState {

        public ReadState onChar(char ch, StringBuffer buffer) {
            return this;
        }

        public boolean continueReading() {
            return false;
        }
    }


}
