/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package com.queplix.core.modules.eqlext.utils.images;

import com.queplix.core.error.GenericSystemException;
import com.queplix.core.utils.StringHelper;
import com.queplix.core.utils.log.AbstractLogger;
import com.queplix.core.utils.log.Log;
import org.apache.regexp.RE;
import org.apache.regexp.RESyntaxException;

import java.util.ArrayList;
import java.util.List;

/**
 * Memo attachment links transformer manager.
 * <br>Supported memo types:
 * <li>XML
 * <li>HTML
 * <li>Text
 *
 * @author [ALB] Baranov Andrey
 * @version $Revision: 1.1.1.1 $ $Date: 2005/09/12 15:30:44 $
 */

public class MemoTransformManager {

    // =============================================================== Constants

    /**
     * Logger.
     */
    protected static AbstractLogger logger = Log.getLog(
            MemoTransformManager.class);

    /**
     * XML format type.
     */
    public static final int XML_FORMAT = 0;

    /**
     * HTML format type.
     */
    public static final int HTML_FORMAT = 1;

    /**
     * Text format type.
     */
    public static final int TEXT_FORMAT = 2;

    /** @todo add support other MIME types */

    /**
     * Name position.
     */
    public static final int NAME_POS = 1;

    /**
     * Subfolder position.
     */
    public static final int SUBFOLDER_POS = 2;

    /**
     * Content (attachment description) position.
     */
    public static final int CONTENT_POS = 3;

    //
    // System regular expression to find attachment in the memo.
    //

    public static final String SYSTEM_REG_EXP =
            "<attachment\\s+href='([^']+)/([^']+)'>(.*?)</attachment>";

    public static final String SYSTEM_FORMAT =
            "<attachment href='{" + SUBFOLDER_POS + "}/{" + NAME_POS + "}'>{" +
                    CONTENT_POS + "}</attachment>";

    public static final ReStruct SYSTEM_RE_STRUCT =
            new ReStruct(SYSTEM_REG_EXP, SUBFOLDER_POS, NAME_POS, CONTENT_POS);

    // ===================================================== Variables

    // Memo field.
    private final String memo;

    // Flag indicates collect attachments in
    // <code>attachments</code> or not.
    private boolean collectAttachments;

    // Memo format.
    private final int memoFormat;

    // Formats.
    private String xmlFormat = null;
    private String htmlFormat = SYSTEM_FORMAT;
    private String textFormat = null;

    // RE structure lists.
    private final List xmlReStructs;
    private final List htmlReStructs;
    private final List textReStructs;

    // Attachments.
    private List attachments;

    // ===================================================== Constructors

    /**
     * Constructor
     *
     * @param memo value
     */
    public MemoTransformManager(String memo) {
        this(memo, false);
    }

    /**
     * Constructor
     *
     * @param memo               value
     * @param collectAttachments collect atatchments or no
     */
    public MemoTransformManager(String memo, boolean collectAttachments) {
        this.memo = memo;
        this.collectAttachments = collectAttachments;

        this.xmlReStructs = new ArrayList();
        this.htmlReStructs = new ArrayList();
        this.textReStructs = new ArrayList();

        // Init HTML RE list
        htmlReStructs.add(SYSTEM_RE_STRUCT);

        if(collectAttachments) {
            this.attachments = new ArrayList();
        }

        if(StringHelper.isXML(memo)) {
            memoFormat = XML_FORMAT;
        } else if(StringHelper.isHTML(memo)) {
            memoFormat = HTML_FORMAT;
        } else {
            memoFormat = TEXT_FORMAT;
        }

        if(logger.getLogger().isDebugEnabled()) {
            logger.DEBUG("Memo format: " + memoFormat);
            logger.DEBUG("Collect Attachments?: " + collectAttachments);
        }
    }

    // ===================================================== Public methods

    //
    // Setters.
    //

    public void setXMLFormat(String xmlFormat) {
        this.xmlFormat = xmlFormat;
    }

    public void setHTMLFormat(String htmlFormat) {
        this.htmlFormat = htmlFormat;
    }

    public void setTextFormat(String textFormat) {
        this.textFormat = textFormat;
    }

    public void setXMLReStruct(ReStruct xmlReStruct) {
        xmlReStructs.clear();
        xmlReStructs.add(xmlReStruct);
    }

    public void setXMLReStruct(List reStructs) {
        xmlReStructs.clear();
        xmlReStructs.addAll(reStructs);
    }

    public void setHTMLReStruct(ReStruct htmlReStruct) {
        htmlReStructs.clear();
        htmlReStructs.add(htmlReStruct);
    }

    public void setHTMLReStruct(List reStructs) {
        htmlReStructs.clear();
        htmlReStructs.addAll(reStructs);
    }

    public void setTextReStruct(ReStruct textReStruct) {
        textReStructs.clear();
        textReStructs.add(textReStruct);
    }

    public void setTextReStruct(List reStructs) {
        textReStructs.clear();
        textReStructs.addAll(reStructs);
    }

    public void addXMLReStruct(ReStruct xmlReStruct) {
        xmlReStructs.add(xmlReStruct);
    }

    public void addHTMLReStruct(ReStruct htmlReStruct) {
        htmlReStructs.add(htmlReStruct);
    }

    public void addTextReStruct(ReStruct textReStruct) {
        textReStructs.add(textReStruct);
    }

    //
    // Getters.
    //

    public int getMemoFormat() {
        return memoFormat;
    }

    public Attachment[] getAttachments() {
        if(!collectAttachments) {
            throw new IllegalStateException();
        }

        if(attachments.size() == 0) {
            return null;
        } else {
            return (Attachment[]) attachments.toArray(new Attachment[0]);
        }
    }

    /**
     * Get the array of attachments encountered in memo.
     *
     * @return array or NULL
     */
    public Attachment[] findAttachments() {
        List l;
        switch(memoFormat) {
            case XML_FORMAT:
                l = findAttachments(memo, xmlReStructs);
                break;
            case HTML_FORMAT:
                l = findAttachments(memo, htmlReStructs);
                break;
            default:
                l = findAttachments(memo, textReStructs);
        }

        if(l == null || l.size() == 0) {
            return null;
        } else {
            return (Attachment[]) l.toArray(new Attachment[0]);
        }
    }

    /**
     * Cut all client attachment tags from the memo text.
     * Insert system tags instead.
     *
     * @param reStructs client regular expression structure list to find neccessary links
     * @return converted memo
     */
    public String convertToSystem(List reStructs) {
        switch(memoFormat) {
            case XML_FORMAT:
                return convert(reStructs, xmlFormat);
            case HTML_FORMAT:
                return convert(reStructs, htmlFormat);
            default:
                return convert(reStructs, textFormat);
        }
    }

    /**
     * Cut all client attachment tags from the memo text.
     * Insert system tags instead.
     *
     * @param reStruct client regular expression structure to find neccessary links
     * @return converted memo
     */
    public String convertToSystem(ReStruct reStruct) {
        switch(memoFormat) {
            case XML_FORMAT:
                return convert(reStruct, xmlFormat);
            case HTML_FORMAT:
                return convert(reStruct, htmlFormat);
            default:
                return convert(reStruct, textFormat);
        }
    }

    /**
     * Cut all system attachment tags from the memo text.
     * Insert client tags instead.
     *
     * @param format client format string for MessageFormat object
     * @return converted memo
     * @see java.text.MessageFormat
     */
    public String convertToClient(String format) {
        switch(memoFormat) {
            case XML_FORMAT:
                return convert(xmlReStructs, format);
            case HTML_FORMAT:
                return convert(htmlReStructs, format);
            default:
                return convert(textReStructs, format);
        }
    }

    // ===================================================== Protected methods

    //
    // Convert methods.
    // Return converted string.
    //

    protected String convert(List reStructs, String format) {
        int size = (reStructs == null) ? 0:reStructs.size();
        String s = memo;
        for(int i = 0; i < size; i++) {
            s = __convert(s, (ReStruct) reStructs.get(i), format);
        }
        return s;
    }

    protected String convert(ReStruct reStruct, String format) {
        return __convert(memo, reStruct, format);
    }

    protected String __convert(String memo, ReStruct reStruct, String format) {

        if(logger.getLogger().isDebugEnabled()) {
            logger.DEBUG("Start convertion. ReStruct: " + reStruct
                    + ". Format: " + format);
        }

        // If memo is empty or no ReStruct or no format - do nothing.
        if(StringHelper.isEmpty(memo)) {
            return memo;
        }
        if(reStruct == null) {
            logger.WARN("No suitable regexp structure...");
            return memo;
        }
        if(format == null) {
            logger.WARN("No suitable format...");
            return memo;
        }

        try {
            RE re = new RE(reStruct.getRegexp(),
                    RE.MATCH_CASEINDEPENDENT | RE.MATCH_MULTILINE);
            int i = 0;
            int curPos = 0;
            while(re.match(memo, curPos)) {
                Attachment attachment = nextAttachment(re, memo, reStruct);

                if(logger.getLogger().isDebugEnabled()) {
                    logger.DEBUG("Found attachment: " + attachment);
                }

                // add attachment in map
                if(attachments != null) {
                    attachments.add(attachment);
                }

                // replace tags
                String link = constructLink(attachment, format, reStruct);

                int start = re.getParenStart(0);
                int end = re.getParenEnd(0);
                String head = memo.substring(0, start);
                String tail = memo.substring(end);
                memo = head + link + tail;
                curPos = start + link.length();

                // counter
                i++;
            }

            if(logger.getLogger().isDebugEnabled()) {
                logger.DEBUG(
                        "Finish convertion. Memo matched " + i + " times...");
            }

        } catch (RESyntaxException ex) {
            logger.ERROR(ex);
            throw new GenericSystemException("Regexp exception: " +
                    ex.getMessage(), ex);
        }

        return memo;
    }

    //
    // Find attachments methods.
    // Return the list with Attachment objects.
    //

    protected List findAttachments(String memo, List reStructs) {
        List attachments = new ArrayList();
        int size = (reStructs == null) ? 0:reStructs.size();
        for(int i = 0; i < size; i++) {
            __findAttachments(memo, (ReStruct) reStructs.get(i), attachments);
        }
        return attachments;
    }

    protected List findAttachments(String memo, ReStruct reStruct) {
        List attachments = new ArrayList();
        __findAttachments(memo, reStruct, attachments);
        return attachments;
    }

    protected List __findAttachments(String memo, ReStruct reStruct,
                                     List attachments) {

        // If empty - do nothing.
        if(StringHelper.isEmpty(memo)) {
            return attachments;
        }

        try {
            RE re = new RE(reStruct.getRegexp(),
                    RE.MATCH_CASEINDEPENDENT | RE.MATCH_MULTILINE);
            int curPos = 0;
            while(re.match(memo, curPos)) {
                Attachment attachment = nextAttachment(re, memo, reStruct);

                if(logger.getLogger().isDebugEnabled()) {
                    logger.DEBUG("Found attachment: " + attachment);
                }

                attachments.add(attachment);
                curPos = re.getParenEnd(0);
            }

        } catch (RESyntaxException ex) {
            logger.ERROR(ex);
            throw new GenericSystemException(
                    "Regexp exception: " + ex.getMessage(), ex);
        }

        return attachments;
    }

    // Build link on attachment
    protected String constructLink(Attachment attachment,
                                   String format,
                                   ReStruct reStruct) {
        try {
            RE re = new RE("\\{1\\}");
            format = re.subst(format, getLinkPos1(attachment, reStruct),
                    RE.REPLACE_ALL);

            re = new RE("\\{2\\}");
            format = re.subst(format, getLinkPos2(attachment, reStruct),
                    RE.REPLACE_ALL);

            re = new RE("\\{3\\}");
            format = re.subst(format, getLinkPos3(attachment, reStruct),
                    RE.REPLACE_ALL);

        } catch (RESyntaxException ex) {
            logger.ERROR(ex);
            throw new GenericSystemException(
                    "Regexp exception: " + ex.getMessage(),
                    ex);
        }
        return format;
    }

    // Get value for link position #1
    protected String getLinkPos1(Attachment attachment, ReStruct reStruct) {
        return attachment.getName();
    }

    // Get value for link position #2
    protected String getLinkPos2(Attachment attachment, ReStruct reStruct) {
        return attachment.getSubfolder();
    }

    // Get value for link position #3
    protected String getLinkPos3(Attachment attachment, ReStruct reStruct) {
        String content = attachment.getContent();
        if(content == null) {
            content = StringHelper.EMPTY_VALUE;
        }
        return content;
    }

    // Read next attachment.
    protected Attachment nextAttachment(RE re,
                                        String memo,
                                        ReStruct reStruct) {

        // read name (mandatory)
        String name = memo.substring(re.getParenStart(reStruct.getNamePos()),
                re.getParenEnd(reStruct.getNamePos()));

        // read subfolder (mandatory)
        String subfolder = memo.substring(re.getParenStart(
                reStruct.getSubfolderPos()),
                re.getParenEnd(reStruct.getSubfolderPos()));

        // read content (optional)
        String content = null;
        int contentStart = re.getParenStart(reStruct.getContentPos());
        int contentEnd = re.getParenEnd(reStruct.getContentPos());
        if(contentStart >= 0 && contentEnd >= 0) {
            content = memo.substring(contentStart, contentEnd);
        }

        // construct Attachment
        return new Attachment(name, subfolder, content);
    }
}
