package org.postgresforest.tool.lib;

public class SqlTokenizer {
	int curpos = 0;
	String sql = null;
	String token = null;

	public int ttype = TT_UNKNOWN;

	public static final int TT_UNKNOWN   = -1;
	public static final int TT_SPACE     = 0;
	public static final int TT_COMMENT   = 1;
	public static final int TT_WORD      = 2;
	public static final int TT_OPERATOR  = 3;
	public static final int TT_SEPARATOR = 4;
	public static final int TT_EOS       = 10;
	public static final int TT_EOL       = 11;

	public SqlTokenizer(String sql)
	{
		this.curpos = 0;
		this.sql = sql;
	}

	public String nextToken()
	{
		boolean isQuoted = false;
		ttype = TT_UNKNOWN;
		int i = curpos;

        /*
         * スペース（ホワイトスペースは読み飛ばす）
         */
		if ( isspace(sql.charAt(curpos)) )
		{
			for (i=curpos ; i<sql.length() ; i++)
			{
				if ( !isspace(sql.charAt(i)) )
					break;
			}
			ttype = TT_SPACE;
		}
		/*
		 * ラインコメント（行末まで読み飛ばす）
		 */
        else if ( sql.charAt(curpos)=='-' && sql.charAt(curpos+1)=='-' )
        {
            for (i=curpos ; i<sql.length() ; i++)
            {
                if ( sql.charAt(i)=='\n' )
				{
					i++;
                    break;
				}
            }

            ttype = TT_COMMENT;
        }
		/*
		 * ブロックコメント（ブロックが閉じられるまで読み飛ばす）
		 */
        else if ( sql.charAt(curpos)=='/' && sql.charAt(curpos+1)=='*' )
        {
            for (i=curpos ; i<sql.length() ; i++)
            {
                if ( sql.charAt(i-1)=='*' && sql.charAt(i)=='/' )
				{
					i++;
                    break;
				}
            }

            ttype = TT_COMMENT;
		}
		/*
		 * 演算子（演算子である限り読み続ける）
		 */
        else if ( isoper(sql.charAt(curpos)) )
        {
            for (i=curpos ; i<sql.length() ; i++)
            {
                if ( !isoper(sql.charAt(i)) )
                    break;
            }

			ttype = TT_OPERATOR;
        }
        /*
         * セパレータ3種 - '(' ')' ','
         */
        else if ( isseparator(sql.charAt(curpos)) )
        {
            i++;
            ttype = TT_SEPARATOR;
        }
        /*
         * 行末 - '\n'
         */
        else if ( sql.charAt(curpos)=='\n' )
        {
            i++;
            ttype = TT_EOL;
        }
		/*
		 * SQLステートメント終端 - ';'
		 */
        else if ( sql.charAt(curpos)==';' )
        {
			i++;
			ttype = TT_EOS;
        }
		else
		{
			for (i=curpos ; i<sql.length() ; i++)
			{
				if ( sql.charAt(i)=='\'' )
				{
					if ( (i+2)<=sql.length() && sql.charAt(i+1)=='\'' )
					{
						i++;
					}
					else
					{
						isQuoted = !isQuoted;
					}
				}
				else
				{
					if ( !isQuoted )
					{
						if ( isspace(sql.charAt(i)) || isseparator(sql.charAt(i)) || isoper(sql.charAt(i)) )
						{
							ttype = TT_WORD;
							break;
						}

						/*
						 * SQLステートメント終端
						 */
						if ( sql.charAt(i)==';' )
						{
							ttype = TT_WORD;
							break;
						}
						/*
						 * 行末
						 */
                        if ( sql.charAt(i)=='\n' )
                        {
                            ttype = TT_WORD;
                            break;
                        }
					}
				}
			}
		}

		//		System.out.println("curpos=" + curpos + ",i=" + i);
		token = sql.substring(curpos, i);

		curpos = i;

		return token;
	}

	public boolean hasMoreToken()
	{
		if ( curpos < sql.length() )
			return true;

		return false;
	}

	private boolean isspace(char c)
	{
		if ( c=='\t' || c==' ' )
			return true;

		return false;
	}

	private boolean isalpha(char c)
	{
		if ( 'A'<=c && c<='Z' )
			return true;

		if ( 'a'<=c && c<='z' )
			return true;

		return false;
	}

	private boolean isoper(char c)
	{
        if ( c=='!' || c=='@' || c=='%' || c=='^' || c=='&' ||
			 c=='*' || c=='-' || c=='+' || c=='=' || c=='|' ||
             c=='~' || c=='<' || c=='>' || c=='/' )
            return true;

        return false;
	}

    private boolean isseparator(char c)
    {
        if ( c=='(' || c==')' || c==',' )
            return true;

        return false;
    }

	private boolean isdigit(char c)
	{
		if ( c=='0' || c=='1' || c=='2' || c=='3' || c=='4' ||
			 c=='5' || c=='6' || c=='7' || c=='8' || c=='9' || c=='.' )
			return true;

		return false;
	}

	private boolean isalnum(char c)
	{
		return (isalpha(c) || isdigit(c));
	}

	public String toString()
	{
		return this.sql;
	}

	public static void test(String sql)
	{
        SqlTokenizer t = new SqlTokenizer(sql);

		System.out.println("\nSQL = " + t.toString());
        while ( t.hasMoreToken() )
        {
            System.out.println("token = [" + t.nextToken() + "]");
        }
	}

	public static void main(String[] args)
	{
		String sql = null;

		sql = "CREATE TABLE ( uid integer primary key,name text not       null);";
		test(sql);

		sql = "CREATE TABLE ( uid integer primary key, name text not /* hoge */      null );";
		test(sql);

		sql = "CREATE TABLE ( uid integer primary key, name text not -- /* hoge */\n      null );";
		test(sql);

		sql = "UPDATE hoge SET name = 'Satoshi Nagayasu';";
		test(sql);

		sql = "UPDATE hoge SET name = 'Satoshi ''Nagayasu';";
		test(sql);

		sql = "UPDATE hoge SET uid=30.4, name = '私の名前わ''-- 永安です、か？';";
		test(sql);

		sql = "SELECT * FROM t1 WHERE uid <> 100.0";
		test(sql);
	}
}
