package org.openas2.message;

import org.apache.commons.logging.LogFactory;
import org.openas2.OpenAS2Exception;
import org.openas2.Session;
import org.openas2.WrappedException;
import org.openas2.lib.helper.ICryptoHelper;
import org.openas2.params.InvalidParameterException;
import org.openas2.partner.Partnership;
import org.openas2.processor.msgtracking.TrackingModule;
import org.openas2.util.Properties;

import javax.mail.Header;
import javax.mail.MessagingException;
import javax.mail.internet.ContentDisposition;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.ParseException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;


public abstract class BaseMessage implements Message {
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    private DataHistory history;
    private InternetHeaders headers;
    private Map<String, String> attributes;
    private MessageMDN MDN;
    private MimeBodyPart data;
    private Partnership partnership;
    private String compressionType = ICryptoHelper.COMPRESSION_NONE;
    private boolean rxdMsgWasSigned = false;
    private boolean rxdMsgWasEncrypted = false;
    private Map<Object, Object> options = new HashMap<Object, Object>();
    private String calculatedMIC = null;
    private String logMsg = null;
    private String status = MSG_STATUS_MSG_INIT;
    private Map<String, String> customOuterMimeHeaders = new HashMap<String, String>();
    private String payloadFilename = null;


    public BaseMessage() {
        super();
    }

    public String getAppTitle() {
        return Properties.getProperty(Properties.APP_TITLE_PROP, "OpenAS2 Server");
    }

    public Map<Object, Object> getOptions() {
        if (options == null) {
            options = new HashMap<Object, Object>();
        }
        return options;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public Map<String, String> getCustomOuterMimeHeaders() {
        return customOuterMimeHeaders;
    }


    public void setCustomOuterMimeHeaders(Map<String, String> customOuterMimeHeaders) {
        this.customOuterMimeHeaders = customOuterMimeHeaders;
    }

    public void addCustomOuterMimeHeader(String key, String value) {
        this.customOuterMimeHeaders.put(key, value);
    }

    public void setOption(Object key, Object value) {
        getOptions().put(key, value);
    }

    public Object getOption(Object key) {
        return getOptions().get(key);
    }

    public void setAttribute(String key, String value) {
        getAttributes().put(key, value);
    }

    public String getAttribute(String key) {
        return getAttributes().get(key);
    }

    public Map<String, String> getAttributes() {
        if (attributes == null) {
            attributes = new HashMap<String, String>();
        }

        return attributes;
    }

    public void setAttributes(Map<String, String> attributes) {
        this.attributes = attributes;
    }

    public String getContentType() {
        return getHeader("Content-Type");
    }

    public void setContentType(String contentType) {
        setHeader("Content-Type", contentType);
    }

    public String getCompressionType() {
        return (compressionType);
    }

    public void setCompressionType(String myCompressionType) {
        compressionType = myCompressionType;
    }

    /**
     * Gets the "Content-Disposition" header from the message object
     *
     * @return the string value of the header
     */
    public String getContentDisposition() {
        return getHeader("Content-Disposition");
    }

    /**
     * Sets the "Content-Disposition" header in the message object
     *
     * @param contentDisposition the string value to be set
     */
    public void setContentDisposition(String contentDisposition) {
        setHeader("Content-Disposition", contentDisposition);
    }

    public void setData(MimeBodyPart data, DataHistoryItem historyItem) {
        this.data = data;

        if (data != null) {
            try {
                setContentType(data.getContentType());
            } catch (MessagingException e) {
                setContentType(null);
            }
            try {
                setContentDisposition(data.getHeader("Content-Disposition", null));
            } catch (MessagingException e) {
                setContentDisposition(null); // TODO: why ignore?????
            }
        }

        if (historyItem != null) {
            getHistory().getItems().add(historyItem);
        }
    }

    public DataHistoryItem setData(MimeBodyPart data) throws OpenAS2Exception {
        try {
            DataHistoryItem historyItem = new DataHistoryItem(data.getContentType());
            setData(data, historyItem);

            return historyItem;
        } catch (Exception e) {
            throw new WrappedException(e);
        }
    }

    public MimeBodyPart getData() {
        return data;
    }

    public void setHeader(String key, String value) {
        getHeaders().setHeader(key, value);
    }

    public String getHeader(String key) {
        return getHeader(key, ", ");
    }

    public String getHeader(String key, String delimiter) {
        return getHeaders().getHeader(key, delimiter);
    }

    public InternetHeaders getHeaders() {
        if (headers == null) {
            headers = new InternetHeaders();
        }

        return headers;
    }

    public void setHeaders(InternetHeaders headers) {
        this.headers = headers;
    }

    public DataHistory getHistory() {
        if (history == null) {
            history = new DataHistory();
        }

        return history;
    }

    public void setHistory(DataHistory history) {
        this.history = history;
    }

    public MessageMDN getMDN() {
        return MDN;
    }

    public void setMDN(MessageMDN mdn) {
        this.MDN = mdn;
    }

    public String getMessageID() {
        return getHeader("Message-ID");
    }

    public void setMessageID(String messageID) {
        setHeader("Message-ID", messageID);
    }

    public Partnership getPartnership() {
        if (partnership == null) {
            partnership = new Partnership();
        }

        return partnership;
    }

    public void setPartnership(Partnership partnership) {
        this.partnership = partnership;
    }

    public abstract String generateMessageID() throws InvalidParameterException;

    public String getSubject() {
        return getHeader("Subject");
    }

    public void setSubject(String subject) {
        setHeader("Subject", subject);
    }

    public boolean isRxdMsgWasSigned() {
        return rxdMsgWasSigned;
    }

    public void setRxdMsgWasSigned(boolean rxdMsgWasSigned) {
        this.rxdMsgWasSigned = rxdMsgWasSigned;
    }

    public boolean isRxdMsgWasEncrypted() {
        return rxdMsgWasEncrypted;
    }

    public void setRxdMsgWasEncrypted(boolean rxdMsgWasEncrypted) {
        this.rxdMsgWasEncrypted = rxdMsgWasEncrypted;
    }

    public String getXForwardedFor() {
        return getHeader("X-Forwarded-For");
    }

    public String getXRealIP() {
        return getHeader("X-Real-IP");
    }

    public void addHeader(String key, String value) {
        getHeaders().addHeader(key, value);
    }

    public String toString() {
        StringBuffer buf = new StringBuffer();
        buf.append("Message From:").append(getPartnership().getSenderIDs());
        buf.append("To:").append(getPartnership().getReceiverIDs());

        Enumeration<Header> headerEn = getHeaders().getAllHeaders();
        buf.append(System.getProperty("line.separator") + "Headers:{");

        while (headerEn.hasMoreElements()) {
            Header header = headerEn.nextElement();
            buf.append(header.getName()).append("=").append(header.getValue());

            if (headerEn.hasMoreElements()) {
                buf.append(", ");
            }
        }

        buf.append("}");
        buf.append(System.getProperty("line.separator") + "Attributes:").append(getAttributes());

        MessageMDN mdn = getMDN();

        if (mdn != null) {
            buf.append(System.getProperty("line.separator") + "MDN:");
            buf.append(mdn.toString());
        }

        return buf.toString();
    }

    public void updateMessageID() throws InvalidParameterException {
        setMessageID(generateMessageID());
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        // read in partnership
        partnership = (Partnership) in.readObject();

        // read in attributes
        attributes = (Map<String, String>) in.readObject();

        // read in data history
        history = (DataHistory) in.readObject();

        try {
            // read in message headers
            headers = new InternetHeaders(in);

            // read in mime body 
            if (in.read() == 1) {
                data = new MimeBodyPart(in);
            }
        } catch (MessagingException me) {
            throw new IOException("Messaging exception: " + me.getMessage());
        }

        // read in MDN
        MDN = (MessageMDN) in.readObject();

        if (MDN != null) {
            MDN.setMessage(this);
        }

        customOuterMimeHeaders = new HashMap<String, String>();
    }

    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        // write partnership info
        out.writeObject(partnership);

        // write attributes
        out.writeObject(attributes);

        // write data history
        out.writeObject(history);

        // write message headers
        Enumeration<String> en = headers.getAllHeaderLines();

        while (en.hasMoreElements()) {
            out.writeBytes(en.nextElement() + "\r\n");
        }

        out.writeBytes("\r\n");

        // write the mime body
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        try {
            if (data != null) {
                baos.write(1);
                data.writeTo(baos);
            } else {
                baos.write(0);
            }
        } catch (MessagingException e) {
            throw new IOException("Messaging exception: " + e.getMessage());
        }

        out.write(baos.toByteArray());
        baos.close();

        // write the message's MDN
        out.writeObject(MDN);
    }

    public String getLogMsgID() {
        return " [" + getMessageID() + "]";
    }

    public String getLogMsg() {
        return logMsg;
    }

    public void setLogMsg(String msg) {
        logMsg = msg;
    }

    public String getCalculatedMIC() {
        return calculatedMIC;
    }

    public void setCalculatedMIC(String calculatedMIC) {
        this.calculatedMIC = calculatedMIC;
    }

    public String getPayloadFilename() {
        return payloadFilename;
    }

    public void setPayloadFilename(String filename) {
        payloadFilename = filename;
    }

    public void trackMsgState(Session session) {
        // Log a start sending fail state but do not allow exceptions to stop the process
        try {
            options.put("OPTIONAL_MODULE", "true");
            session.getProcessor().handle(TrackingModule.DO_TRACK_MSG, this, options);
        } catch (Exception et) {
            setLogMsg("Unable to persist message tracking state: " + org.openas2.logging.Log.getExceptionMsg(et));
            LogFactory.getLog(BaseMessage.class.getSimpleName()).error(this, et);
        }

    }

    public String extractPayloadFilename() throws ParseException {
        String s = getContentDisposition();
        if (s == null || s.length() < 1) {
            return null;
        }
        // TODO: This should be a case insensitive lookup per RFC6266
        String tmpFilename = null;

        ContentDisposition cd = new ContentDisposition(s);
        tmpFilename = cd.getParameter("filename");

        if (tmpFilename == null || tmpFilename.length() < 1) {
            /* Try to extract manually */
            int n = s.indexOf("filename=");
            if (n > -1) {
                tmpFilename = s.substring(n);
                tmpFilename = tmpFilename.replaceFirst("filename=", "");

                int n1 = tmpFilename.indexOf(",");
                if (n1 > -1) {
                    s = s.substring(0, n1 - 1);
                }
                tmpFilename = tmpFilename.replaceAll("\"", "");
                s = s.trim();
            } else {
                /* Try just using file separator */
                int pos = s.lastIndexOf(File.separator);
                if (pos >= 0) {
                    tmpFilename = s.substring(pos + 1);
                }
            }
        }
        return tmpFilename;
    }
}