/*
 * Copyright 2008 nori090
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * 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 org.coderepos.nori090.news;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.coderepos.nori090.lib2chj.BBSMenu;
import org.coderepos.nori090.lib2chj.Category;
import org.coderepos.nori090.lib2chj.Comment;
import org.coderepos.nori090.lib2chj.CommentImpl;
import org.coderepos.nori090.lib2chj.Ita;
import org.coderepos.nori090.lib2chj.Subject;
import org.coderepos.nori090.lib2chj.Thread;
import org.coderepos.nori090.lib2chj.ThreadAverage;

/**
 * @author nori090
 * @version $Rev: 98 $ $Date: 2008-06-26 21:53:17 +0900 (Thu, 26 Jun 2008) $
 */
public class News2ch {
    // 抽出対象の正規表現
    // private String selectItaNames = "ビジネスnews\\+|ニュース速報\\+|経済$";

    private String selectItaNames = "経済$";

    private Pattern p = Pattern.compile( selectItaNames );

    VelocityContext context = new VelocityContext();

    Template thread_template;

    Template comment_template;

    {
        try {
            Properties p = new Properties();
            p.load( this.getClass().getClassLoader().getResourceAsStream(
                                                                          "org/coderepos/nori090/lib2chj/velocity.properties" ) );
            Velocity.init( p );
            thread_template = Velocity.getTemplate( "org/coderepos/nori090/lib2chj/thread.vm", "UTF-8" );
            comment_template = Velocity.getTemplate( "org/coderepos/nori090/lib2chj/comment.vm", "UTF-8" );
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    void start()
        throws IOException {
        Set<Category> categorys = BBSMenu.getCategorys();
        HashMap<String, Ita> map = new HashMap<String, Ita>();
        // 取得対象の抽出
        for ( Category c : categorys ) {
            for ( Ita ita : c.getItaList() ) {
                String name = ita.getItaName_ja();
                Matcher m = p.matcher( name );
                if ( m.find() ) {
                    map.put( name, ita );
                }
            }
        }
        List<Thread> indexlist = new ArrayList<Thread>();
        for ( String key : map.keySet() ) {
            Ita ita = map.get( key );
            System.out.println( ita.getItaName_ja() );
            for ( Subject sub : ita.getSubjectsFromWeb() ) {
                ThreadAverage ave = ThreadAverage.getAverage( sub );
                if ( ave.getMonth() == 0 && ave.getDate() == 0 && ave.getHour() == 0 ) {
                    System.out.println( "\t" + ave.getMonth() + "ヶ月" + ave.getDate() + "日" + ave.getHour() + "時間" +
                        ave.getResidue() + "\t" + sub.getTitle() );
                    sub.getThread().getThreadFromWeb();
                    indexlist.add( sub.getThread() );
                    output( sub.getThread() );
                }
            }
        }
        outputIndex( indexlist );
    }

    /**
     * index.htmlの出力
     * 
     * @param list
     */
    void outputIndex( List<Thread> list ) {
        StringBuilder sb = new StringBuilder();
        sb.append( "<html><title>2ch mob</title><body>" );
        String ita = "";
        for ( Thread t : list ) {
            if ( !ita.equals( t.getIta().getItaName_ja() ) ) {
                sb.append( "<hr>" ).append( t.getIta().getItaName_ja() ).append( "<br></hr>" );
            }
            sb.insert( sb.length() - 5, "<a href=\"" ).insert( sb.length() - 5, makeUrl( t, 1 ) ).insert(
                                                                                                          sb.length() - 5,
                                                                                                          "\">" ).insert(
                                                                                                                          sb.length() - 5,
                                                                                                                          t.getTitle() ).insert(
                                                                                                                                                 sb.length() - 5,
                                                                                                                                                 "</a><br>" );
            ita = t.getIta().getItaName_ja();
        }
        sb.append( "</body></html>" );
        try {
            FileWriter fw = new FileWriter( "./tmp/index.html" );
            fw.write( sb.toString() );
            fw.flush();
            fw.close();
        }
        catch ( IOException e ) {
            e.printStackTrace();
        }
    }

    /**
     * スレッドのhtml生成
     * 
     * @param thread
     */
    void output( org.coderepos.nori090.lib2chj.Thread thread ) {
        List<Comment> list = thread.getComments();
        List<Comment> outlist = new ArrayList<Comment>();
        int flush = 0;
        int count = 1;
        try {
            for ( Comment c : list ) {
                Comment other = new CommentImpl();
                // 内部を破壊させるので参照性を削除する。
                copyComment( c, other );
                // アンカーリンクを削除する。
                replaceLink( other );
                // 250byteを超えるレスは省略する。
                if ( other.getBody().getBytes( "windows-31j" ).length > 250 ) {
                    // １ファイルとして出力するためにパスを決定する。
                    String url = makeUrl1res( thread, c.getNumberOfCount() );
                    context.put( "thread", thread );
                    context.put( "c", c );
                    // １ファイル１レスのhtmlを出力する。
                    output1res( context, "./tmp/" + url );
                    // 省略表記のコメントを生成しセットする。
                    splitComment( other, url.replaceAll( thread.getIta().getItaName(), "." ) );
                }
                outlist.add( other );
                flush++;
                // 1ファイル(html)は5０レスとする。
                if ( flush == 50 ) {
                    String url = makeUrl( thread, count );
                    context.put( "thread", thread );
                    context.put( "comments", outlist );
                    context.put( "next", makeUrl( thread, count + 1 ).replaceAll( thread.getIta().getItaName(), "." ) );
                    // ファイル出力する。
                    outputCommentList( context, "./tmp/" + url );
                    flush = 0;
                    count++;
                    outlist.clear();
                }
            }
            // 50レスに満たない残りを出力
            if ( flush != 0 ) {
                String url = makeUrl( thread, count );
                context.put( "thread", thread );
                context.put( "comments", outlist );
                context.put( "next", "." );
                outputCommentList( context, "./tmp/" + url );
            }
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    /**
     * 破壊的アンカリンクの削除。
     * 
     * @param c
     */
    void replaceLink( Comment c ) {
        String s = c.getBody();
        StringBuilder sb = new StringBuilder();
        int index = -1;
        int pos = 0;
        while ( ( index = s.indexOf( "<a href=\"../test/read.cgi/" ) ) != -1 ) {
            sb.append( s.substring( pos, index ) );
            s = s.substring( index );
            s = s.substring( s.indexOf( ">" ) + 1 );
            sb.append( s.substring( 0, s.indexOf( "</a>" ) ) );
            s = s.substring( s.indexOf( "</a>" ) + 4 );
        }
        sb.append( s );
        c.setBody( sb.toString() );
    }

    /**
     * 250byteを超える部分を省略リンクへ置き換える。（破壊的）
     * 
     * @param c
     * @param url
     */
    void splitComment( Comment c, String url ) {
        String body = c.getBody();
        StringBuilder sb = new StringBuilder();
        int count = 0;
        int last = 0;
        boolean tag = false;
        try {
            for ( char ch : body.toCharArray() ) {
                count += String.valueOf( ch ).getBytes( "windows-31j" ).length;
                // 改行タグ内で分割しないようにフラグを持つ
                if ( ch == '<' )
                    tag = true;
                if ( ch == '>' )
                    tag = false;
                sb.append( ch );
                if ( count >= 250 && !tag ) {
                    break;
                }
            }
            last = c.getBody().getBytes( "windows-31j" ).length - count;
        }
        catch ( UnsupportedEncodingException e ) {
            e.printStackTrace();
        }
        sb.append( "<a href=\"" ).append( url ).append( "\">省略" ).append( last ).append( "</a>" );
        c.setBody( sb.toString() );
    }

    /**
     * {@link Comment}を簡易コピーする。主にボディのみ。
     * 
     * @param src
     * @param target
     */
    void copyComment( Comment src, Comment target ) {
        target.setBody( src.getBody() );
        target.setId( src.getId() );
        target.setMailto( src.getMailto() );
        target.setName( src.getName() );
        target.setDate( src.getDate() );
        target.setNumberOfCount( src.getNumberOfCount() );
    }

    /**
     * 通常のレス一覧用htmlを出力する。
     * 
     * @param context
     * @param url
     */
    void outputCommentList( VelocityContext context, String url ) {
        try {
            File f = new File( url.substring( 0, url.lastIndexOf( "/" ) ) );
            if ( !f.exists() ) {
                f.mkdirs();
            }
            FileWriter fw = new FileWriter( url );
            thread_template.merge( context, fw );
            fw.flush();
            fw.close();
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    /**
     * １レス用のhtmlを出力する。
     * 
     * @param context
     * @param url
     */
    void output1res( VelocityContext context, String url ) {
        try {
            File f = new File( url.substring( 0, url.lastIndexOf( "/" ) ) );
            if ( !f.exists() ) {
                f.mkdirs();
            }
            FileWriter fw = new FileWriter( url );
            comment_template.merge( context, fw );
            fw.flush();
            fw.close();
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    /**
     * ファイル出力用のurlを生成する。
     * 
     * @param thread
     * @param count
     * @return
     */
    String makeUrl( org.coderepos.nori090.lib2chj.Thread thread, int count ) {
        return thread.getIta().getItaName() + "/" + thread.getId() + "-" + count + ".html";
    }

    /**
     * １レス用ファイル出力のurlを生成する。
     * 
     * @param thread
     * @param num
     * @return
     */
    String makeUrl1res( org.coderepos.nori090.lib2chj.Thread thread, int num ) {
        return thread.getIta().getItaName() + "/" + thread.getId() + "_" + num + ".html";
    }

    /**
     * FTPディレクトリを削除する。指定path配下は空でないと削除できないので、 再帰的にファイルおよびディレクトリを削除する。
     * 
     * @param client
     * @param path
     * @return
     * @throws IOException
     */
    static boolean deleteDirectory( FTPClient client, String path )
        throws IOException {
        cleanDirectory( client, path );
        return client.removeDirectory( path );
    }

    /**
     * FTPディレクトリのファイルおよびディレクトリを削除する。 指定pathのディレクトリを子（ファイル＆ディレクトリ）が無い状態にする。 ディレクトリ削除については、再帰的に削除を行う。
     * 
     * @param client
     * @param path
     * @throws IOException
     */
    static void cleanDirectory( FTPClient client, String path )
        throws IOException {
        FTPFile[] fs = client.listFiles( path );
        for ( FTPFile f : fs ) {
            String p = path.endsWith( "/" ) ? path.concat( f.getName() ) : path.concat( "/" ).concat( f.getName() );
            if ( f.isDirectory() ) {
                if ( ".".equals( f.getName() ) )
                    continue;
                deleteDirectory( client, p );
            }
            else {
                client.deleteFile( p );
            }
        }
    }

    static void copyDirectory( FTPClient client, String server_path, String local_path )
        throws IOException {
        File f1 = new File( local_path );
        File[] fs = f1.listFiles();
        for ( File f : fs ) {
            if ( f.isDirectory() ) {
                String p = server_path.concat( f.getName() );
                client.makeDirectory( p );
                copyDirectory( client, p.concat( "/" ), local_path.concat( f.getName() ).concat( "/" ) );
            }
            else {
                String p1 = local_path.concat( f.getName() );
                String p2 = server_path.concat( f.getName() );
                FileInputStream fis = new FileInputStream( p1 );
                boolean ok = client.storeFile( p2, fis );
                System.out.println( ok + ":" + p2 );
                fis.close();
            }
        }
    }

    public static void main( String[] args ) {
        try {
            File f = new File( "tmp" );
            if ( f.exists() ) {
                FileUtils.deleteDirectory( f );
            }
            new News2ch().start();
            Properties p = new Properties();
            p.load( News2ch.class.getResourceAsStream( "/org/coderepos/nori090/lib2chj/user.properties" ) );
            FTPClient client = new FTPClient();
            client.connect( p.getProperty( "ftp_address" ) );
            if ( !FTPReply.isPositiveCompletion( client.getReplyCode() ) ) {
                System.err.println( "connect fail" );
                return;
            }
            if ( !client.login( p.getProperty( "ftp_id" ), p.getProperty( "ftp_pass" ) ) ) {
                System.err.println( "login fail" );
                return;
            }
            client.setFileType( FTP.BINARY_FILE_TYPE );
            client.setControlEncoding( "SJIS" );
            client.enterLocalPassiveMode();
            System.out.println( "削除結果：" + deleteDirectory( client, "m" ) );
            client.makeDirectory( "m" );
            copyDirectory( client, "m/", "./tmp/" );
            client.disconnect();
        }
        catch ( IOException e ) {
            e.printStackTrace();
        }
    }
}
