/*
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2005 satoshi akabane(akabane@logical-paradox.org)
 * $Id: RandomHtmlGenerator.java,v 1.3 2005/10/21 11:01:07 rampil Exp $
 */
package org.logical_paradox.httpd.html.random;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.logical_paradox.common.lang.Random;
import org.logical_paradox.common.util.StringUtils;
import org.logical_paradox.httpd.ClientRequest;
import org.logical_paradox.httpd.HttpdProperties;
import org.logical_paradox.httpd.ResponseHeader;
import org.logical_paradox.httpd.ServerResponse;
import org.logical_paradox.httpd.exception.HttpException;
import org.logical_paradox.httpd.html.HtmlGenerator;

/**
 * 適当なHTMLを生成して返すタイプのHTMLジェネレータ
 * 生成されるページには、適当な数量のリンクが設定されている
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.3 $
 */
public class RandomHtmlGenerator extends HtmlGenerator {
	/** ロボット検索制御ファイルのファイル名 */
	public static final String ROBOTS_TXT = "robots.txt";
	/** タグのテンプレート */
	public static final String[] TAG_TEMPLATES = {
			"<FONT COLOR=RED>__d__</FONT>", "<b>__d__</b>", "<span style=\"font-size: 10px\">__d__</span>",
			"<br>","<a name=test>__d__</a><br>","<p>"
		};
	/** トップドメイン */
	public static final String[] DOMAIN_SUFFIXES = {
			"com", "net", "co.jp", "co.kr", "ne.jp", "ne.kr", "cn", "com.tw", "com.uk", "com.hk",
			"org", "jp", "biz", "tv", "info", "gov", "go.jp"
		};

	/** ロガー */
	private static final Log log = LogFactory.getLog(RandomHtmlGenerator.class);

	/** ランダムトークン */
	private List tokens;
	/** ランダム接続詞 */
	private List conjuctions;
	/** 乱数ジェネレータ */
	private Random r = new Random(Random.PLUS_OR_ZERO);

	/** ランダム単語辞書ファイルのプロパティキー */
	public static final String KEY_RANDOM_TOKEN_DICTIONARY = "random.token.dic";
	/** ランダム接続詞辞書ファイルのプロパティキー */
	public static final String KEY_RANDOM_CONJUCTION_DICTIONARY = "random.conjuction.dic";

	/**
	 * HTMLページを返す
	 * @param request クライアントからのリクエスト
	 * @return 生成されたHTML
	 * @throws HttpException ドキュメント生成中に発生した例外(404や403など)
	 */
	public ServerResponse generateHtml(ClientRequest request) throws HttpException {
		if(tokens == null || conjuctions == null) {
			loadDictionary();
		}
		String contentType = null;
		String content = "";
		int httpStatusCode = 200;

		if(r.nextInt() % 20 == 0) {
			// 1/20の確率でNot Foundを返す
			httpStatusCode = 404;
		} else if(r.nextInt() % 20 == 1) {
			// 1/20の確率でForbiddenを返す
			httpStatusCode = 403;
		} else if(r.nextInt() % 30 == 2) {
			// 1/30の確率でInternal Server Errorを返す
			httpStatusCode = 500;
		}

		if(httpStatusCode != 200) {
			// OK以外のステータスの場合，エラーページとするために例外をスローする
			throw new HttpException(httpStatusCode);
		}

		if(request.getQuery().endsWith(ROBOTS_TXT)) {
			// robots.txtが要求された場合，適当なものを生成して返す(なしの場合もある)
			// robots.txtはtext/plainである
			contentType = "text/plain";
		} else {
			// 適当にHTMLを生成する
			content = generateHtmlDocument(request);
		}

		// レスポンスの生成
		ServerResponse response = new ServerResponse(new ResponseHeader(), content);

		if(r.nextInt() % 10 == 0) {
			// 1/10の確率で、Moved Temporarilyを返す
			response.setHttpStatusCode(302);
			String forwardTo = generateURL(request);
			response.put("Location", forwardTo);
			log.info("====> " + forwardTo);
			return response;
		}

		if(contentType != null) {
			// 何らかのレスポンスヘッダが指定されてる場合，それを採用する
			response.put(ResponseHeader.H_CONTENT_TYPE, contentType);
		}
		return response;
	}
	/**
	 * ホスト名を生成して返す．
	 * @param request リクエストパラメータ
	 * @return 生成されたホスト名
	 */
	public String generateHost(ClientRequest request) {
		String hostname = null;

		if("true".equals(HttpdProperties.getProperty(HttpdProperties.LINK_TO_ONLY_LOCALHOST).toLowerCase())) {
			// localhostに対するリンクのみ生成
			hostname = request.getConfig().getHostName() + ":" + request.getConfig().getPort();
		} else {
			// 適当なホスト名と適当なポート番号を生成
			hostname = StringUtils.randomTextRange(4, 15) + "." + selectTopDomain();
			if(r.nextInt() % 100 < 5) {
				// 5%ぐらいの確率で，8000番からのポート番号を追加する
				hostname += ":" + ((r.nextInt() % 100) + 8000);
			}
		}

		return hostname;
	}
	/**
	 * 適当に生成したrobots.txtを返す
	 * @return 生成されたrobots.txt(null: なし)
	 */
	public String generateRobotsTxt() {
		return "";
	}
	/**
	 * HTML文書を作成する
	 * 内容はランダムである
	 * @param request クライアントからのリクエスト
	 * @return 自動生成されたHTML文書
	 */
	public String generateHtmlDocument(ClientRequest request) {
		StringBuffer sb = new StringBuffer();

		// ドキュメントのprefixを追加
		sb.append("<HTML><HEAD><TITLE>" + getRandomWordCpx() + "</TITLE></HEAD><BODY>");

		int documentSize = (r.nextInt() % 8 +1) * 1000;
		while(sb.length() < documentSize) {
			if(r.nextInt() % 10 == 0) {
				// 1/10の確率でリンクを生成する
				sb.append("<br><a href=" + generateURL(request) + ">" + getRandomWordCpx() + "</a><br>");
			} else {
				String randomSentence = getRandomSentence();
				if(r.nextInt() % 10 <= 3) {
					// 30%の確率でタグを挿入
					String chosenTag = getRandomTag();
					sb.append(chosenTag.replaceAll("__d__", randomSentence) + "<br>\n");
				}
			}
		}

		// ドキュメントのpostfixを追加
		sb.append("</BODY></HTML>");

		return sb.toString();
	}
	/**
	 * 適当なURLを生成して返す
	 * FQDNが返される場合もあるし，相対パスとして返される場合もある
	 * @return 生成された適当なURL
	 */
	public String generateURL(ClientRequest request) {
		StringBuffer path = new StringBuffer();

		// 一定の確率で、ドメイン指定なし
		if(r.nextInt() % 10 < 2) {
			path.append("http://" + generateHost(request) + "/");
		} else {
			if(r.nextInt() % 2 == 0) {
				// 1/2の確率で何らかのパス指定
				if(r.nextInt() % 10 >= 7) {
					path.append("./");				// 30%
				} else if(r.nextInt() % 10 == 0) {
					path.append("../../");			// 10%
				} else {
					path.append("../");
				}
			}
		}

		/*
		 * ファイル名の生成
		 * 階層がいくつであってもRSSとしては関係ないので適当にファイル名だけ返す
		 */
		path.append(StringUtils.randomTextRange(4, 15) + ".html");

		return path.toString();
	}
	/**
	 * トップドメインを選択する．
	 * @return トップドメイン
	 */
	public String selectTopDomain() {
		int index = r.nextInt() % DOMAIN_SUFFIXES.length;
		return DOMAIN_SUFFIXES[index];
	}
	/**
	 * タグテンプレートから適当なタグパターンを選択して返す
	 * @return 選択されたタグパターン
	 */
	public String getRandomTag() {
		return TAG_TEMPLATES[r.nextInt() % TAG_TEMPLATES.length];
	}
	/**
	 * ランダムな名詞を返す
	 * @return 自動選択された名詞
	 */
	public String getRandomWord() {
		return (String)tokens.get(r.nextInt() % tokens.size());
	}
	/**
	 * ランダムな接続詞を返す
	 * @return 自動選択された接続詞
	 */
	public String getRandomConjuction() {
		return (String)conjuctions.get(r.nextInt() % conjuctions.size());
	}
	/**
	 * いくつかの名詞をつなぎ合わせた複合名詞を返す
	 * @return 自動選択された名詞
	 */
	public String getRandomWordCpx() {
		// 名詞の結合数は1〜4
		int connections = r.nextInt() % 4 + 1;
		StringBuffer sb = new StringBuffer();
		for(int i = 0; i < connections; i++) {
			sb.append(getRandomWord());
		}

		return sb.toString();
	}
	/**
	 * 複合名詞を接続詞で結合した適当な文章を作成して返す
	 * @return 自動生成された文節
	 */
	public String getRandomSentence() {
		// 名詞と接続詞の結合数は6〜40
		int connections = r.nextInt() % 35 + 6;
		StringBuffer sb = new StringBuffer();

		for(int i = 0; i < connections; i++) {
			sb.append(getRandomWordCpx());				// 複合名詞
			sb.append(getRandomConjuction());			// 接続詞
		}

		return sb.toString();
	}
	/**
	 * 単語と接続詞の辞書をロードする
	 */
	protected synchronized void loadDictionary() {
		try {
			// 単語ファイルをロードする
			ArrayList tokenList = new ArrayList();
			BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(HttpdProperties.getProperty(KEY_RANDOM_TOKEN_DICTIONARY))));
			String line = null;
			while((line = br.readLine()) != null) {
				tokenList.add(line.trim());
			}
			br.close();
			// immutableな辞書として登録する
			tokens = Collections.unmodifiableList(tokenList);

			// 接続詞ファイルをロードする
			ArrayList conjuctionList = new ArrayList();
			br = new BufferedReader(new InputStreamReader(new FileInputStream(HttpdProperties.getProperty(KEY_RANDOM_CONJUCTION_DICTIONARY))));
			line = null;
			while((line = br.readLine()) != null) {
				conjuctionList.add(line.trim());
			}
			br.close();
			// immutableな接続詞辞書として登録する
			conjuctions = Collections.unmodifiableList(conjuctionList);
		} catch (Exception e) {
			e.printStackTrace();
			// 正しくファイルが読めない場合，停止のしようがないので適当な辞書を作成
			tokens = new ArrayList();
			conjuctions = new ArrayList();

			tokens.add("単語辞書のロードに失敗");
			conjuctions.add("接続詞辞書のロードに失敗");
		}
	}
}
