package org.itscool.stylist.io;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.StringTokenizer;

import org.itscool.commons.document.XmlConfigReader;
import org.itscool.commons.util.FileUtil;
import org.itscool.stylist.tag.HtmlAttribute;
import org.itscool.stylist.tag.HtmlRootTag;
import org.itscool.stylist.tag.HtmlTag;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * XHTMLを解析します<BR>
 * 解析結果はHtmlTagクラスに作成されます
 * @author KANO
 * @version 1.00A 2006/08/09 新規作成
 */
public class HtmlConfigReader extends XmlConfigReader{
    public static final String LINE = System.getProperty("line.separator");
    private String defaultEncode;
    
    /**
     * DocumentBuilderを使ってXMLファイルを読み込みます
     * <!DOCTYPE句は読み飛ばします
     * @param uri コンフィグファイルのパスを指定します
     * @param nodeList 読み込んだDOMを格納するインスタンスを指定します
     * @throws Exception ファイルの読み込みに失敗した場合は例外Exceptionをスローします
     */
    public void create(String uri, byte[] buf, LinkedList nodeList ) throws IOException{
        String bufStr = new String(buf);
        int xmlStartPos = bufStr.indexOf("<?xml");
        int xmlEndPos = bufStr.indexOf("?>");
        int dtdStartPos = bufStr.indexOf("<!DOCTYPE");
        int dtdEndPos = bufStr.indexOf("dtd\">");
        
        String xml = null;
        String dtd = null;
        String xhtml = null;
        
        //xmlタグの定義がない場合はparseしない
        if(xmlStartPos < 0 ){
            return;
        }
        xml = bufStr.substring(0, xmlEndPos+2);
//		int encStartPos = xml.indexOf("encoding=\"");
//		if( encStartPos < 0 ){
//			throw new IOException("No defined encoding to xml tag.");
//		}
//		String stack = xml.substring(encStartPos+("encoding=\"").length());
//		int encEndPos = stack.indexOf("\"");
//		if( encEndPos < 0 ){
//			throw new IOException("Can't convert encoding to xml tag.");
//		}
//		String encode = stack.substring(0, encEndPos);
        
        if( dtdEndPos > 0 ){
            dtd = bufStr.substring(dtdStartPos, dtdEndPos+5);
            xhtml = bufStr.substring(dtdEndPos+5, bufStr.length());
        }else if(xmlStartPos >= 0){
            xhtml = bufStr.substring(xmlEndPos+2, bufStr.length());
        }
//		System.out.println("xml:"+xml);
//		System.out.println("encode:"+encode);
//		System.out.println("dtd:"+dtd);
//		System.out.println("xhtml:"+xhtml);
        
        String cnvStr;
        if(xml != null){
            cnvStr = xml + xhtml;
        }else if(dtdEndPos > 0){
            cnvStr = xhtml;
        }else{
            cnvStr = bufStr;
        }
        HtmlRootTag top;
        if( nodeList instanceof HtmlRootTag ){
            top = (HtmlRootTag)nodeList;
        }else{
            throw new ClassCastException("filed cast HtmlRootTag => " +
                nodeList.getClass().getName());
        }
        top.setXml(xml);
        top.setDtd(dtd);
//		top.setEncode(encode);
        super.create(uri, cnvStr.getBytes(), nodeList);
    }
    
    /**
     * DocumentBuilderを使ってXMLファイルを読み込みます
     * <!DOCTYPE句は読み飛ばします
     * ところで、ローカル・ファイルの読み込みにjava.io.FileReaderクラスを
     * 使うのはお勧めできない。
     * なぜならFileReaderクラスはJVMのデフォルト・エンコーディングにしか
     * 対応しておらず、読み込み時の文字コードを明示的に指定できないからである。
     * デフォルト・エンコーディングは、Windows上のJVMではWindows-31J（MS932）
     * となり、UNIXではLANG変数で設定された文字コード（EUC-JPやUTF-8など）
     * になる。
     * そのため、例えばWindows上で開発した成果物をLinuxサーバ上で運用する
     * ケースなどにおいて、デフォルト・エンコーティングの違いによる文字化け
     * が発生しやすい。
     *
     * Javaアプリケーション開発では、自分が書いたコードがデフォルト・
     * エンコーティングに依存しないように心掛けよう。
     * 例えばテキスト・ファイルを読み込む場合は、以下のように
     * FileInputStreamクラスとInputStreamReaderクラスで実装にすればよいだろう。
     *
     * InputStreamReader isr = new InputStreamReader(
     *     new FileInputStream("filename.txt"), "Windows-31J");
     * @param path コンフィグファイルのパスを指定します
     * @param nodeList 読み込んだDOMを格納するインスタンスを指定します
     * @throws Exception ファイルの読み込みに失敗した場合は例外Exceptionをスローします
     */
    public void create(String path, LinkedList nodeList ) throws IOException{
        HtmlRootTag top;
        if( nodeList instanceof HtmlRootTag ){
            top = (HtmlRootTag)nodeList;
        }else{
            throw new ClassCastException("filed cast HtmlRootTag => " +
                nodeList.getClass().getName());
        }
        long lastModified = FileUtil.getLastModified(path);
        top.setLastModified(lastModified);
        String encode = top.getEncode();
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
//    	BufferedReader br = new BufferedReader(new FileReader(path));
        InputStreamReader isr = new InputStreamReader(
            new FileInputStream(path), encode);
        BufferedReader br = new BufferedReader(isr);
        String line ;
        boolean xmlSec = false;
        while((line=br.readLine()) != null){
            if(line.indexOf("<?xml")>=0 && xmlSec == false ){
                if( line.trim().lastIndexOf("?>")!=-1 ){
                    top.setXml(line);
                }else{
                    StringBuffer xml = new StringBuffer(line);
                    while((line=br.readLine()) != null){
                        xml.append(line);
                        if( line.trim().lastIndexOf("?>")!=-1 ){
                            break;
                        }
                    }
                    top.setXml(xml.toString());
                }
                xmlSec = true;
            }
            if( xmlSec == false )
            {
                //デフォルトエンコードを使ってXMLヘッダを作成
                String defaultXml = "<?xml version=\"1.0\" encoding=\"" + encode + "\" ?>";
                top.setXml(defaultXml);
                bout.write(defaultXml.getBytes());
                bout.write(LINE.getBytes());
                xmlSec = true;
            }
            if(line == null ){
                continue;
            }
            if(line.trim().indexOf("<!DOCTYPE")==0 ){
                if( line.trim().lastIndexOf(">")!=-1 ){
                    top.setDtd(line);
                }else{
                    StringBuffer dtd = new StringBuffer(line);
                    while((line=br.readLine()) != null){
                        dtd.append(line);
                        if( line.trim().lastIndexOf(">")!=-1 ){
                            break;
                        }
                    }
                    top.setDtd(dtd.toString());
                }
                continue;
            }
            bout.write(line.getBytes(encode));
            bout.write(LINE.getBytes());
        }
//        if( top.getXml() == null || top.getXml().length() == 0 ){
//            return;
//        }
        super.create(path, bout.toByteArray(), nodeList);
        top = (HtmlRootTag)nodeList;
    }

    /**
     * DocumentBuilderを使ってXMLファイルを読み込みます
     * <!DOCTYPE句は読み飛ばします
     * @param path コンフィグファイルのパスを指定します
     * @param encode 文字エンコードを指定します
     * @throws Exception ファイルの読み込みに失敗した場合は例外Exceptionをスローします
     */
    public String create(String path, String encode) throws IOException{
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
//    	BufferedReader br = new BufferedReader(new FileReader(path));
        InputStreamReader isr = new InputStreamReader(
            new FileInputStream(path), encode);
        BufferedReader br = new BufferedReader(isr);
        String line ;
        StringBuffer sb = new StringBuffer();
        while((line=br.readLine()) != null){
            sb.append(line).append(LINE);
        }
        
        return sb.toString();
    }
    
    /**
     * XMLファイルから&lt;table&gt;要素を解析します<br>
     * 解析結果はdataListにセットします<br>
     * @param dataList XMLの解析結果
     */
    protected void doCreate(LinkedList dataList) throws IOException{
        /* ルートタグ名を取得 */
        Element root = document.getDocumentElement();
        if( root == null ){
            String err = "Can not root element at weber-message.xml(" +
                getClass().getName()+")";
            throw new IOException(err);
        }
        
        /* action-mappings の取得*/
        HtmlRootTag tag = (HtmlRootTag)dataList;
        tag.setTagKind(HtmlTag.KIND_TAG);
        tag.setTagName(root.getTagName());
        NamedNodeMap attrs = root.getAttributes();
        addAttribute(tag, attrs);
        NodeList nodeList = root.getChildNodes();
        createTagTree(nodeList, tag);
    }
    
    /**
     * HTMLタグ情報のツリーを構築します
     * @param nodeList NodeListインスタンス
     * @param parent 親タグ情報
     * @throws IOException
     */
    protected void createTagTree(NodeList nodeList, HtmlTag parent)
    throws IOException {
        String nodeValue;
        String nodeName;
        for( int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            NamedNodeMap attrs = node.getAttributes();
            NodeList childList = node.getChildNodes();
            HtmlTag childTag;
            int childSize = childList.getLength();
            
            if( node.getNodeValue() != null &&
                node.getNodeValue().length() > 0 ){
                nodeValue = node.getNodeValue().trim();
            }else{
                nodeValue = "";
            }
            nodeName = node.getNodeName();
            if( nodeName.equals(HtmlTag.TEXT) && nodeValue.length()==0){
                continue;
            }
            
            if( nodeName.equals(HtmlTag.TEXT) ){
                childTag = createHtmlTag(HtmlTag.KIND_TEXT, nodeValue, attrs);
            }else if( nodeName.equals(HtmlTag.COMMENT)){
                childTag = createHtmlTag(HtmlTag.KIND_COMMENT, nodeValue, attrs);
            }else if( childSize > 0 || nodeName.equals(HtmlTag.TAG_TEXTAREA)){
                childTag = createHtmlTag(HtmlTag.KIND_TAG, nodeName, attrs);
            }else if( nodeName.equals("#cdata-section")){
//            	System.out.println("nodeName="+node.getNodeName());
//            	System.out.println("nodeValue="+node.getNodeValue());
//            	System.out.println("nodeType="+node.getNodeType());
                childTag = createHtmlTag(HtmlTag.KIND_CDATA, nodeValue, attrs);
            }else{
                childTag = createHtmlTag(HtmlTag.KIND_SIMPLE_TAG, nodeName, attrs);
            }
            childTag.setParent(parent);
            parent.add(childTag);
            createTagTree(childList, childTag);
            
        }
    }
    
    /**
     * タグ情報を親タグ情報に追加する
     * @param tagKind タグ種別
     * @param tagName タグ名称
     * @return この処理によって生成されたタグ情報
     */
    protected HtmlTag createHtmlTag(String tagKind, String tagName,
        NamedNodeMap attrs){
        
        HtmlTag tag = new HtmlTag();
        tag.setTagName(tagName);
        tag.setTagKind(tagKind);
        addAttribute(tag, attrs);
        
        return tag;
    }
    
    /**
     * 属性情報を追加します
     * @param node タグ情報
     * @param attrs 属性情報
     */
    protected void addAttribute(HtmlTag node, NamedNodeMap attrs){
        if( attrs == null ){
            return;
        }
        int size = attrs.getLength();
        for(int i=0; i<size; i++){
            Node attrNode = attrs.item(i);
            String attrName = attrNode.getNodeName();
            String attrValue = attrNode.getNodeValue();
            if( attrName.equals(HtmlAttribute.TITLE) ){
                //以下のメソッドは将来的に廃止予定(2007/04/05)
                parseControlAttribute(node, attrNode);
//add 2007/04/05 kanostart title属性の記述も残す
                HtmlAttribute attr = new HtmlAttribute();
                attr.setName(attrName);
                attr.setValue(attrValue);
                node.addAttribute(attr);
//add 2007/04/05 kano end
            }else{
                HtmlAttribute attr = new HtmlAttribute();
                attr.setName(attrName);
                attr.setValue(attrValue);
                node.addAttribute(attr);
            }
            
//add 2007/04/05 kano start 属性名がw_kindの場合は、コントロールタグ           
            if( attrName!=null && attrName.equals(HtmlAttribute.W_KIND)){
                //タグ種別を変更
                node.setTagKind(attrValue);
            }
//add 2007/04/05 kano end
        }
    }
    
    /**
     * プロパティリストには以下の書式である必要があります。
     * <P>
     * 項目名1=値1:項目名2=値2:項目名3=値3:項目名4=値4･･･
     * <p>
     * @param node HtmlTagインスタンス
     * @param attrNode 属性情報
     * @return タグ種別を返します
     */
    public void parseControlAttribute(HtmlTag node, Node attrNode) {
        String attrName   = attrNode.getNodeName();
        String properties = attrNode.getNodeValue();
        
//add 2007/04/06 kano start
        //title属性にw_kindキーワードが無い場合は、処理を抜ける
        if( properties==null || properties.indexOf(HtmlAttribute.W_KIND)<0){
            return;
        }
//add 2007/04/06 kano end
        
        if(properties==null || properties.length() == 0){
            return;
        }
        StringTokenizer dataSt = new StringTokenizer(properties, HtmlAttribute.SP);
        
        while (dataSt.hasMoreTokens()) {
            String data = dataSt.nextToken().trim();
            StringTokenizer propertySt = new StringTokenizer(data, HtmlAttribute.EQ);
            //オブジェクト名の取得
            String name = propertySt.nextToken();
            if( name==null){
                throw new RuntimeException("name is null query="+properties);
            }else{
                name = name.trim();
            }
            //プロパティ名の取得
            String property = null;
            if( propertySt.hasMoreTokens() ){
                property = propertySt.nextToken();
            }
            HtmlAttribute attr = new HtmlAttribute();
            attr.setName(name);
            attr.setValue(property);
            node.addAttribute(attr);
            if( name!=null && property!=null && name.equals(HtmlAttribute.W_KIND)){
                //タグ種別を変更
                node.setTagKind(property);
            }
        }
    }

    public String getDefaultEncode() {
        return defaultEncode;
    }

    public void setDefaultEncode(String defaultEncode) {
        this.defaultEncode = defaultEncode;
    }
    
}
