package jp.sourceforge.shovel.taglib;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;

import org.seasar.framework.container.S2Container;

import freemarker.template.utility.StringUtil;

public class AutoFormatTag extends TagSupport {
    static final long serialVersionUID = -1L;
    S2Container container_;
    
    String data_;
    boolean popup_;
    int length_ = 200;
    int maxUrl_ = 40;
    String url_;
    int tabSpace_;
    
    static final String URL = "https?://[a-zA-Z0-9/_.?#&;=$+:@%~,\\-]+";
    static final String REPLY = "[^\\w]?@([\\w|-]+)";
    static final String POPUP = " target=\"_blank\"";
    static final String SUFFIX = "...";
    
    class Match {
        int type_;
        int start_;
        int end_;
        String[] regs_;
        
        Match() {
        }
        Match(int type, int start, int length) {
            setType(type);
            setStart(start);
            setEnd(start + length);
        }
        public void setType(int type) {
            type_ = type;
        }
        public int getType() {
            return type_;
        }
        public void setStart(int start) {
            start_ = start;
        }
        public int getStart() {
            return start_;
        }
        public void setEnd(int end) {
            end_ = end;
        }
        public int getEnd() {
            return end_;
        }
        public int getLength() {
            return end_ - start_;
        }
        public void setRegs(List<String> regList) {
            regs_ = regList.toArray(new String[regList.size()]);
        }
        public String[] getRegs() {
            return regs_;
        }
    };
    
    public String getData() {
        return data_;
    }
    public void setData(String data) {
        data_ = data;
    }
    public boolean isPopup() {
        return popup_;
    }
    public void setPopup(boolean popup) {
        popup_ = popup;
    }
    public int getLength() {
        return length_;
    }
    public void setLength(int length) {
        length_ = length;
    }
    public String getUrl() {
        return url_;
    }
    public void setUrl(String url) {
        url_ = url;
    }
    public int getMaxUrl() {
        return maxUrl_;
    }
    public void setMaxUrl(int maxUrl) {
        maxUrl_ = maxUrl;
    }
    
    SortedMap<Long, Match> getMatches(SortedMap<Long, Match> matchMap, int type, String pattern, String body, int group) {
        if (matchMap == null) {
            matchMap = new TreeMap<Long, Match>();
        }
        
        Matcher matcher = Pattern.compile(pattern).matcher(body);
        while (matcher.find()) {
            Match match = new Match();
            match.setType(type);
            match.setStart(matcher.start(group));
            match.setEnd(matcher.end(group));
            int groupCount = matcher.groupCount();
            List<String> regs = new ArrayList<String>();
            for(int i = 0; i <= groupCount; i++) {
                regs.add(matcher.group(i));
            }
            match.setRegs(regs);
            matchMap.put((long)matcher.start(), match);
        }
        return matchMap;
    }
    
    String truncateString(String data, String encoding, int length, boolean contain) {
        try {
            byte[] bytes = data.getBytes(encoding);
            if( bytes.length > length ) {
                if ( contain ) {
                    length = length - SUFFIX.length();
                }
    
                int i = 0;
                boolean fin = false;
                while (true) {
                    byte b = bytes[i];
                    int unsigned;
                    unsigned = b & 0xFF;
                    if ((unsigned >= 0x81 && unsigned <= 0x9F) ||
                        (unsigned >= 0xE0 && unsigned <= 0xFC)) {
                        i += 2;
                        if(i > length) {
                            i -= 2;
                            fin = true;
                        }
                    } else {
                        i++;
                    }
                    if (i >= length || fin) {
                        byte[] newBytes = new byte[i];
                        System.arraycopy(bytes, 0, newBytes, 0, i);
                        return new String(newBytes, encoding);
                    }
                }
            } else {
                return data;
            }
        } catch (UnsupportedEncodingException e) {
        }
        return null;
    }
    
    public int doEndTag() throws JspException {
        if (data_ != null && data_.length() > 0) {
            String popup = isPopup() ? POPUP : "";
            
            String truncatedData = data_;
            boolean truncated = false;
            if (length_ > 0) {
                truncatedData = truncateString(data_, "Windows-31J", length_, false);
                truncated = data_.length() > truncatedData.length();
            }
            
            SortedMap<Long, Match> matchMap = null;
            matchMap = getMatches(matchMap, 0, URL, data_, 0);
            matchMap = getMatches(matchMap, 1, REPLY, data_, 1);
            matchMap.put((long)data_.length(), new Match(4, data_.length(), 0));
            Match[] matches = matchMap.values().toArray(new Match[matchMap.size()]);
            
            try {
                int i = 0;
                StringBuilder data = new StringBuilder();
                for (Match match : matches) {
                    int start = match.getStart(); 
                    if(start < i) {
                        continue;
                    }
                    String[] regs = match.getRegs();
                    
                    if (start - i > 0) {
                        if (truncatedData.length() <= start) {
                            start = truncatedData.length();
                            data.append(StringUtil.HTMLEnc(data_.substring(i, start)));
                            break;
                        } else {
                            data.append(StringUtil.HTMLEnc(data_.substring(i, start)));
                        }
                    }

                    int end = match.getEnd();
                    if (truncatedData.length() < end) {
                        end = truncatedData.length();
                    }
                    String token = truncatedData.substring(match.getStart(), end);
                    token = StringUtil.HTMLEnc(token);

                    switch (match.getType()) {
                        case 0:
                            StringBuilder builder = new StringBuilder();
                            try {
                                //hrefに入れられたJavaScriptは黙認する（イントラだし）
                                URI uri = URI.create(regs[0]);
                                builder.append(uri.toString());
                            } catch (Exception e) {
                                //不正なURI？例外が起きたることもあるので手動に切り替える
                                int pos = regs[0].indexOf("?");
                                if (pos < 0 || pos == regs[0].length() - 1) {
                                    builder.append(regs[0]);
                                } else {
                                    builder.append(regs[0].substring(0, pos));
                                    builder.append("?");
                                    String query = regs[0].substring(pos + 1);
                                    builder.append(StringUtil.URLEnc(query, "UTF-8"));
                                }
                                
                            }
                            //長いURLは丸める
                            if (token.length() > maxUrl_) {
                                token = token.substring(0, maxUrl_);
                                token += SUFFIX;
                            }
                            
                            data.append("<a href=\"");
                            data.append(builder.toString());
                            data.append("\"");
                            data.append(popup);
                            data.append(">");
                            data.append(token);
                            data.append("</a>");
                            break;
                        case 1:
                            String foreignKey = StringUtil.HTMLEnc(regs[1]);
                            data.append("<a href=\"/");
                            data.append(foreignKey);
                            data.append("\">");
                            data.append(token);
                            data.append("</a>");
                            break;
                        default:
                            break;
                    }
                    i = start + match.getLength();
                    if (truncatedData.length() < i) {
                        break;
                    }
                }
                
                if (truncated) {
                    if (url_ == null || url_.length() <= 0) {
                        data.append(SUFFIX);
                    } else {
                        data.append("<a href=\"" + url_ + "\">");
                        data.append(SUFFIX );
                        data.append("</a>");
                    }
                }
                
                pageContext.getOut().print(data.toString());
            } catch( Exception e ) {
                throw new JspException(e.getMessage());
            }
        }
        return EVAL_PAGE;
    }
}
