package com.fsck.k9.mail.internet;


import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import android.support.annotation.Nullable;

import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.filter.CountingOutputStream;
import com.fsck.k9.mail.filter.SignSafeOutputStream;
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
import org.apache.james.mime4j.util.MimeUtil;
import timber.log.Timber;


public class TextBody implements Body, SizeAware {
    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];


    private final String text;
    private String encoding;
    private String charset = "UTF-8";
    // Length of the message composed (as opposed to quoted). I don't like the name of this variable and am open to
    // suggestions as to what it should otherwise be. -achen 20101207
    @Nullable
    private Integer composedMessageLength;
    // Offset from position 0 where the composed message begins.
    @Nullable
    private Integer composedMessageOffset;

    public TextBody(String body) {
        this.text = body;
    }

    @Override
    public void writeTo(OutputStream out) throws IOException, MessagingException {
        if (text != null) {
            byte[] bytes = text.getBytes(charset);
            if (MimeUtil.ENC_QUOTED_PRINTABLE.equalsIgnoreCase(encoding)) {
                writeSignSafeQuotedPrintable(out, bytes);
            } else if (MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
                out.write(bytes);
            } else {
                throw new IllegalStateException("Cannot get size for encoding!");
            }
        }
    }

    public String getRawText() {
        return text;
    }

    @Override
    public InputStream getInputStream() throws MessagingException {
        try {
            byte[] b;
            if (text != null) {
                b = text.getBytes(charset);
            } else {
                b = EMPTY_BYTE_ARRAY;
            }
            return new ByteArrayInputStream(b);
        } catch (UnsupportedEncodingException uee) {
            Timber.e(uee, "Unsupported charset: %s", charset);
            return null;
        }
    }

    @Override
    public void setEncoding(String encoding) {
        boolean isSupportedEncoding = MimeUtil.ENC_QUOTED_PRINTABLE.equalsIgnoreCase(encoding) ||
                MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding);
        if (!isSupportedEncoding) {
            throw new IllegalArgumentException("Cannot encode to " + encoding);
        }

        this.encoding = encoding;
    }

    public void setCharset(String charset) {
        this.charset = charset;
    }

    @Nullable
    public Integer getComposedMessageLength() {
        return composedMessageLength;
    }

    public void setComposedMessageLength(@Nullable Integer composedMessageLength) {
        this.composedMessageLength = composedMessageLength;
    }

    @Nullable
    public Integer getComposedMessageOffset() {
        return composedMessageOffset;
    }

    public void setComposedMessageOffset(@Nullable Integer composedMessageOffset) {
        this.composedMessageOffset = composedMessageOffset;
    }

    @Override
    public long getSize() {
        try {
            byte[] bytes = text.getBytes(charset);

            if (MimeUtil.ENC_QUOTED_PRINTABLE.equalsIgnoreCase(encoding)) {
                return getLengthWhenQuotedPrintableEncoded(bytes);
            } else if (MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
                return bytes.length;
            } else {
                throw new IllegalStateException("Cannot get size for encoding!");
            }
        } catch (IOException e) {
            throw new RuntimeException("Couldn't get body size", e);
        }
    }

    private long getLengthWhenQuotedPrintableEncoded(byte[] bytes) throws IOException {
        CountingOutputStream countingOutputStream = new CountingOutputStream();
        writeSignSafeQuotedPrintable(countingOutputStream, bytes);
        return countingOutputStream.getCount();
    }

    private void writeSignSafeQuotedPrintable(OutputStream out, byte[] bytes) throws IOException {
        SignSafeOutputStream signSafeOutputStream = new SignSafeOutputStream(out);
        try {
            QuotedPrintableOutputStream signSafeQuotedPrintableOutputStream =
                    new QuotedPrintableOutputStream(signSafeOutputStream, false);
            try {
                signSafeQuotedPrintableOutputStream.write(bytes);
            } finally {
                signSafeQuotedPrintableOutputStream.close();
            }
        } finally {
            signSafeOutputStream.close();
        }
    }

    public String getEncoding() {
        return encoding;
    }
}