/*
 * Paraselene
 * Copyright (c) 2009, 2010  Akira Terasaki
 * このファイルは同梱されているLicense.txtに定めた条件に同意できる場合にのみ
 * 利用可能です。
 */
package paraselene.mockup;


import java.util.*;
import java.io.*;
import java.net.*;
import paraselene.*;
import paraselene.tag.*;
import paraselene.supervisor.*;
import paraselene.dyna.*;

class FunctionInfo {
	String	class_name;
	String	func_name;
	FunctionInfo( String c, String f ){
		class_name = c;
		func_name = f;
	}
}

/**
 * 解析後バッファ。
 */
public class PrePage extends DynamicPage implements OutputNo {
	class PrePageID implements PageID {
		private int id = 0;
		PrePageID( int p ) {
			id = p;
		}
		public boolean isSame( Page p ) {
			return p.getID().getID() == id;
		}
		public int getID() {
			return id;
		}
		public PageFactory getPageFactory() {
			return null;
		}
		public PageID getPageID( int hash ) {
			return null;
		}
	}

	private ArrayList<TableSource>	table = new ArrayList<TableSource>();


	// 出力タイプ 0:view 1:logic
	static String[] out_dir = { "view", "logic" };
	static void setDir( String v, String l ) {
		out_dir[0] = v;
		out_dir[1] = l;
	}

	private String toName( String name ) {
		char[]	ch = name.toCharArray();
		ch[0] = Character.toUpperCase( ch[0] );
		for ( int i = 1; i < ch.length; i++ ) {
			ch[i] = Character.toLowerCase( ch[i] );
		}
		return new String( ch );
	}

	private HashMap<String,Integer>	method = new HashMap<String,Integer>();

	private FunctionInfo makeFunctionInfo( String cls, String tag, String name ) {
		StringBuilder	buf = new StringBuilder( "get" );
		String[]	part = name.split( "[\\p{Punct}\\p{Space}]+" );
		for ( int i = 0; i < part.length; i++ ) {
			if ( part[i].length() == 0 ) {
				buf = buf.append( "_" );
			}
			else {
				buf = buf.append( toName( part[i] ) );
			}
		}
		buf = buf.append( toName( tag ) );
		name = buf.toString();
		Integer	cnt = method.get( name );
		if ( cnt == null ) {
			method.put( name, 1 );
		}
		else {
			cnt++;
			buf = buf.append( cnt );
			addNote( "警告: " + name + " に類似するメソッドを作成しました。" );
		}
		return new FunctionInfo( cls, buf.toString() );
	}

	private StringBuilder toName( FunctionInfo info ) {
		StringBuilder	buf = new StringBuilder( " " );
		buf = buf.append( info.func_name );
		return buf.append( "() {\n" );
	}

	static StringBuilder getCom( String name, String note, TagSource t ) {
		StringBuilder	buf = new StringBuilder( "/**\n" );
		buf = buf.append( " * " );
		buf = buf.append( name );
		if ( note != null )	buf = buf.append( note );
		buf = buf.append( "." );
		if ( t != null ) {
			buf = buf.append( "\n" );
			buf = buf.append( " * <br>" );
			buf = buf.append( t.getTagString() );
		}
		return buf.append( "\n **/\npublic " );
	}

	private HashMap<String, NameDefine>	define = new HashMap<String, NameDefine>();

	private StringBuilder getMethod( String name, Tag t ) throws TagException {
		StringBuilder	buf = getCom( name, null, (TagSource)t );
		String	cls = TagMap.getClassString( t );
		try {
			define.put( name, new NameDefine( name, TagMap.getClass( t ) ) );
		}
		catch( Exception e ) {
			e.printStackTrace();
			System.exit( 1 );
		}
		buf = buf.append( cls );
		FunctionInfo	info = makeFunctionInfo( cls, t.getName(), name );
		buf = buf.append( toName( info ) );
		buf = buf.append( "return (" );
		buf = buf.append( cls );
		buf = buf.append( ")getTag( \"" );
		buf = buf.append( name );
		buf = buf.append( "\" );\n}\n" );
		int	cnt = table.size();
		for ( int i = 0; i < cnt; i++ ) {
			TableSource	tb = table.get( i );
			boolean	f = getAllTag( tb.getAttributeToString( "name" ) ).length == 1;
			buf = buf.append( tb.getOneAccesser( info, name, f ) );
		}
		return buf;
	}

	private static final String TAG_ARRAY = TagMap.TAG + "[]";

	private StringBuilder getMethod( String name ) throws TagException {
		if ( define.get( name ) == null ) {
			define.put( name, new NameDefine( name ) );
		}
		Tag[]	t = getAllTag( name );
		ArrayList<Tag>	left_tag = new ArrayList<Tag>();
		for ( int i = 0; i < t.length; i++ ) {
			if ( Param.isIgnoreTagAccesser( t[i] ) )	continue;
			left_tag.add( t[i] );
		}
		StringBuilder	buf = new StringBuilder();
		if ( left_tag.size() == 0 )	return buf;
		t = left_tag.toArray( new Tag[0] );

		if ( t.length == 1 )	buf = buf.append( getMethod( name, t[0] ) );
		else	addNote( "name=" + name + "のタグは配列化されました。" );
		buf = buf.append( getCom( name, null, null ) );
		buf = buf.append( TAG_ARRAY );
		FunctionInfo	info = makeFunctionInfo( TAG_ARRAY, "Tags", name );
		buf = buf.append( toName( info ) );
		buf = buf.append( "return getAllTag( \"" );
		buf = buf.append( name );
		return buf.append( "\" );\n}\n" );
	}

	public String getString( int no ) {
		switch( no ) {
		case 1:
			return Param.PACKAGE.get();
		case 2:
			return package_name;
		case 3:
			return Output.toHtmlPath( path );
		case 4:
			return class_name;

		case 5:
			{
				TagSource	tag = (TagSource)getMainTag();
				StringBuilder	buf = new StringBuilder( tag.toSource() );
				buf = buf.append( "setMainTag(" );
				buf = buf.append( tag.getValName() );
				buf = buf.append( ");\n" );

				buf = buf.append( "setDoctype(" );
				buf = buf.append( Boolean.toString( isXML() ) );
				buf = buf.append( "," );
				buf = buf.append( Make.escape( getDoctype() ) );
				buf = buf.append( ");\n" );

				buf = buf.append( "addVersionMeta(" );
				buf = buf.append( Make.escape( Version.getTitle() ) );
				buf = buf.append( "," );
				buf = buf.append( Make.escape( new Date().toString() ) );
				buf = buf.append( ");\n" );

				return buf.toString();
			}
		case 6:
			return getEnumName( true );
		case 7:
			{
				StringBuilder	buf = new StringBuilder();
				int	cnt = table.size();
				for ( int i = 0; i < cnt; i++ ) {
					TableSource	tb = table.get( i );
					boolean	f = getAllTag( tb.getAttributeToString( "name" ) ).length == 1;
					buf = buf.append( tb.getEnum( f ) );
				}
				String[]	name = getDefineNameIndex();
				for ( int i = 0; i < name.length; i++ ) {
					try {
						buf = buf.append( getMethod( name[i] ) );
					}
					catch( Exception e ) {}
				}

				buf = buf.append( "/**\n * name 属性の検証。\n * @param page 検証するページ\n * @param exclude 検証から除外する name。\n * @return エラーとなった name。\n */\n" );
				buf = buf.append( "public NameDefine[] inspectName(Page page,String ... exclude){\nreturn DynamicPage.inspectName(page,new NameDefine[]{\n" );
				String	sep = "";
				for ( String key : define.keySet() ) {
					buf = buf.append( sep );
					NameDefine	def = define.get( key );
					buf = buf.append( "new NameDefine(" );
					buf = buf.append( Make.escape( def.getName() ) );
					Class<?>	cls = def.getDefineClass();
					if ( cls != null ) {
						buf = buf.append( "," );
						buf = buf.append( cls.getName() );
						buf = buf.append( ".class" );
					}
					buf = buf.append( ")" );
					sep = ",\n";
				}
				buf = buf.append( "},exclude);\n}\n" );

				return buf.toString();
			}
		case 10:
			{
				int	cnt = imple.size();
				if ( cnt == 0 )	return "";
				StringBuilder	buf = new StringBuilder( "implements " );
				buf = buf.append( imple.get( 0 ) );
				for ( int i = 1; i < cnt; i++ ) {
					buf = buf.append( "," );
					buf = buf.append( imple.get( i ) );
				}
				return buf.toString();
			}
		case 33:
			return Param.PACKAGE_BASE.get();
		case 34:
			return Param.PACKAGE_VIEW.get();
		case 35:
			return Param.PACKAGE_LOGIC.get();
		}
		return null;
	}

	String getClassName() {
		StringBuilder	buf = new StringBuilder( Param.PACKAGE.get() );
		buf = buf.append( "." );
		buf = buf.append( out_dir[1] );
		if ( package_name.length() > 0 ) {
			buf = buf.append( package_name );
		}
		buf = buf.append( "." );
		buf = buf.append( class_name );
		return buf.toString();
	}

	String getEnumName( boolean only_f ) {
		if ( only_f )	return enum_name;
		StringBuilder	buf = new StringBuilder( Param.PACKAGE.get() );
		buf = buf.append( ".base.PageType." );
		buf = buf.append( enum_name );
		return buf.toString();
	}

	void makeSourceMain( int dir, Output output ) throws Exception {
		Linker.readme.setCurrentPage( this );
		StringBuilder	out_path = new StringBuilder( Make.SRC_PATH );
		out_path = out_path.append( File.separator );
		out_path = out_path.append( out_dir[dir] );
		out_path = out_path.append( File.separator );
		out_path = out_path.append( java_path );
		String	name = out_path.toString();
		File	file = new File( name );
		Linker.readme.echoln( name + " output starts." );
		if ( dir == 1 ) {
			if ( file.exists() ) {
				Linker.readme.echoln( name + " was not output, because it already existed." );
				return;
			}
		}
		Output.Status	stat = output.write( file, this, html_last_time );
		switch( stat ) {
		case OK:
			Linker.readme.echoln( name + " output ended." );
			break;
		case SKIP:
			Linker.readme.echoln( name + " is not modified." );
			break;
		}
	}

	void makeSource() throws Exception {
		Output[]	output = {
			new paraselene.mockup.output.source.view.view(),
			new paraselene.mockup.output.source.logic.logic()
		};
		for ( int i = 0; i < 2; i++ ) {
			makeSourceMain( i, output[i] );
		}
	}

	private static HashMap<Integer, PrePage>	hash_map = new HashMap<Integer, PrePage>();
	private int id = 0;
	private String path = "";
	// package_nameにParam.PACKAGE + view|logic を付加する。
	private String package_name;
	private String java_path;
	private String org_file_name;
	private String class_name;
	private String enum_name;
	private long	html_last_time = 0;

	private HashMap<String,String>	name_class = new HashMap<String,String>();
	private HashMap<String,ArrayList<String>>	group_name = new HashMap<String,ArrayList<String>>();
	private ArrayList<String>	note = new ArrayList<String>();

	private ArrayList<String>	imple = new ArrayList<String>();

	void addNote( String n ) {
		note.add( n );
	}

	String getNote() {
		StringBuilder	buf = new StringBuilder();
		int	cnt = note.size();
		for ( int i = 0; i < cnt; i++ ) {
			buf = buf.append( note.get( i ) );
			if ( i != ( cnt - 1 ) )	buf = buf.append( "\n" );
		}
		return buf.toString();
	}

	private static final String	NULL_TITLE = "(タイトルなし)";
	private static final String	SKIP_TITLE = "(解析skip) ";

	String getTitle() {
		Tag[]	tag = getAllTagByType( "title" );
		if ( tag == null )	return SKIP_TITLE;
		if ( tag.length == 0 )	return NULL_TITLE;
		String	str = tag[0].getValueString();
		if ( str == null )	return NULL_TITLE;
		if ( str.length() == 0 )	return NULL_TITLE;
		return str;
	}

	private static HashMap<String, String>	name_check = new HashMap<String, String>();

	public void init() {
		path = path.substring( Param.HTML_ROOT.get().length() );
		if ( path.charAt( 0 ) == File.separatorChar ) {
			path = path.substring( 1 );
		}

		String[]	check = path.split( "[/\\\\]" );
		for ( int i = 0; i < check.length; i++ ) {
			boolean	ret;
			if ( i == (check.length - 1) ) {
				ret = JavaDefine.isDefine( check[i], false );
			}
			else {
				ret = JavaDefine.isDefine( check[i], true );
			}
			if ( ret ) {
				System.err.println( "ディレクトリ名またはファイル名に使用できない文字が含まれています" );
				System.err.println( path );
				System.exit( 1 );
			}
		}

		File	tmp = new File( path );
		java_path = tmp.getParent();
		if ( java_path == null ) {
			package_name = "";
			java_path = "";
		}
		else {
			char[]	ch = java_path.toCharArray();
			for ( int i = 0; i < ch.length; i++ ) {
				if ( ch[i] == '.' ) {
					ch[i] = '_';
				}
				if ( ch[i] == File.separatorChar ) {
					ch[i] = '.';
				}
			}
			package_name = "." + new String( ch );
		}
		char[]	ch = java_path.toCharArray();
		for ( int i = 0; i < ch.length; i++ ) {
			if ( ch[i] == File.separatorChar ) {
				ch[i] = '_';
			}
			else {
				ch[i] = Character.toUpperCase( ch[i] );
			}
		}
		if ( ch.length > 0 ) {
			enum_name = new String( ch ) + "_";
		}
		else {
			enum_name = "";
		}

		org_file_name = tmp.getName();
		class_name = name2CamelCase( org_file_name );

		StringBuilder	buf = new StringBuilder( enum_name );
		enum_name = org_file_name.toUpperCase();
		String[]	part = enum_name.split( "[\\p{Punct}\\p{Space}]+" );
		for ( int i = 0; i < part.length; i++ ) {
			if ( i > 0 )	buf = buf.append( "_" );
			buf = buf.append( part[i] );
		}
		enum_name = buf.toString();

		java_path = java_path + File.separatorChar + class_name + ".java";
		String	cf = name_check.get( java_path );
		if ( cf != null ) {
			System.err.println( "似たファイル名が存在するため、生成するクラス名が衝突します" );
			System.err.println( cf );
			System.err.println( path );
			System.exit( 1 );
		}
		name_check.put( java_path, path );

		id = (Param.PACKAGE.get() + path).hashCode();
		if ( id < 0 )	id *= -1;
		while( true ) {
			PrePage	page = hash_map.get( id );
			if ( page == null )	break;
			id++;
			if ( id < 10 )	id = 10;
		}
		hash_map.put( id, this );
	}

	static String name2CamelCase( String str ) {
		String	name = str.trim().toLowerCase( Locale.ENGLISH );
		String[]	part = name.split( "[\\p{Punct}\\p{Space}]+" );
		StringBuilder	buf = new StringBuilder();
		for ( int i = 0; i < part.length; i++ ) {
			char[]	ch = part[i].toCharArray();
			ch[0] = Character.toUpperCase( ch[0] );
			buf = buf.append( new String( ch ) );
		}
		return buf.toString();
	}

	protected DynamicPage makePage() {
		return new PrePage( this );
	}

	private PrePage( Page p ) {
		super( null, p, Param.provider );
	}

	PrePage( File p )
	throws FileNotFoundException, UnsupportedEncodingException, IOException,
	DynamicPageException {
		this( p, false );
	}

	PrePage( File p, boolean dev_f )
	throws FileNotFoundException, UnsupportedEncodingException, IOException,
	DynamicPageException {
		super( null, Param.provider );
		path = p.getPath();
		html_last_time = p.lastModified();
		if ( !dev_f )	init();
		if ( this instanceof Download )	return;

		StringBuilder	out_path = new StringBuilder( Make.SRC_PATH );
		out_path = out_path.append( File.separator );
		out_path = out_path.append( out_dir[0] );
		out_path = out_path.append( File.separator );
		out_path = out_path.append( java_path );
		if ( new File( out_path.toString() ).lastModified() > html_last_time )	return;

		Linker.readme.setCurrentPage( this );
		create( p, Param.IN_ENCODE.get() );
		if ( !isXML() && getParsingCharset() == null ) {
			addNote( "警告: 解析時にHTMLの文字コードを決定できませんでした。" );
		}
		Tag	frameset = getFirstTagByType( "frameset" );
		if ( frameset != null ) {
			imple.add( "FramesetPage" );
			addNote( "FramesetPage を実装しました。" );
		}
	}

	public PageID getID() { return new PrePageID( id ); }
	protected URI getMyselfURI() {
		try {
			return new URI( Output.toHtmlPath( path ) );
		}
		catch( Exception e ) {
			return null;
		}
	}

	public String getContentType() {
		return "text/html; charset=" + getCharset();
	}

	public String getCharset() {
		return Param.OUT_ENCODE.get();
	}

	public static void main( String[] argc ) throws Exception {
		File	inp = new File( argc[0] );
		SourceHead.init( new File( argc[2] ) );
		PrePage	pp = new PrePage( inp, true );
		paraselene.mockup.output.source.view.readme	readme = new paraselene.mockup.output.source.view.readme();
		readme.write( new File( argc[1] ), pp, Long.MAX_VALUE );
	}

	protected Tag makeTag( String name, ArrayList<Attribute> attr ) throws Exception {
		TagSource	nt = null;
		if ( name.equalsIgnoreCase( "table" ) ) {
			nt = new TableSource( org_file_name );
			table.add( (TableSource)nt );
		}
		else {
			nt = new TagSource( org_file_name, name, isSimpleTag( name ) );
		}
		int	cnt = attr.size();
		for ( int i = 0; i < cnt; i++ ) {
			nt.setAttribute( attr.get( i ) );
		}
		return nt;
	}

	public void clear() {
		super.clear();
		table.clear();
	}

	protected Text makeText( String str ) {
		return new TextSource( str );
	}

	public boolean isRuntime() {
		return false;
	}
}


