package org.madore.android.unicodeMap;

import java.util.Collections;
import java.util.Iterator;
import java.util.Comparator;
import java.util.List;
import java.util.ArrayList;
import java.util.NoSuchElementException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.sql.SQLException;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.content.Context;
import android.content.res.AssetManager;
import android.app.Activity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.database.sqlite.SQLiteOpenHelper;

public class UnicodeDatabase {

    protected static final String DATABASE_NAME = "unicode.db";
    protected static final int DATABASE_VERSION = 3;
    protected static final String UNICODE_TABLE_NAME = "unicode";

    protected boolean needPopulate;

    protected class DatabaseInit extends SQLiteOpenHelper {

	final Context context;

	DatabaseInit(Context context) {
	    super(context, DATABASE_NAME, null, DATABASE_VERSION);
	    this.context = context;
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
	    db.execSQL("CREATE TABLE "+UNICODE_TABLE_NAME+" ( "
		       +"id INTEGER PRIMARY KEY , "
		       +"name TEXT , "
		       +"category TEXT )");
	    UnicodeDatabase.this.needPopulate = true;
	}

	@Override
        public void onUpgrade(SQLiteDatabase db,
			      int oldVersion, int newVersion) {
            db.execSQL("DROP TABLE IF EXISTS "+UNICODE_TABLE_NAME);
            onCreate(db);
        }

    }

    Activity activity;
    DatabaseInit dbinit;
    SQLiteDatabase db;

    public UnicodeDatabase(Activity activity) {
	this.activity = activity;
	this.dbinit = new DatabaseInit(activity);
	db = dbinit.getReadableDatabase();
	if ( ! this.needPopulate ) {
	    Cursor c = db.rawQuery("SELECT count(*) FROM "+UNICODE_TABLE_NAME, null);
	    c.moveToFirst();
	    int lineCnt = c.getInt(0);
	    c.close();
	    if ( lineCnt == 0 )
		this.needPopulate = true;
	}
    }

    public boolean needPopulate() {
	return this.needPopulate;
    }

    protected static void reportProgress(Handler progressHandler,
					 int done, int total) {
	Message msg = progressHandler.obtainMessage();
	Bundle b = new Bundle();
	b.putInt("done", done);
	b.putInt("total", total);
	msg.setData(b);
	progressHandler.sendMessage(msg);
    }

    public void populate(Handler progressHandler) {
	SQLiteDatabase db = dbinit.getWritableDatabase();
	db.beginTransaction();
	db.execSQL("DELETE FROM "+UNICODE_TABLE_NAME);
	AssetManager amgr = activity.getAssets();
	SQLiteStatement cmd
	    = db.compileStatement("INSERT INTO "+UNICODE_TABLE_NAME
				  +" VALUES ( ? , ? , ? )");
	final String[] pieces = {
	    "UnicodeData-00.txt", "UnicodeData-01.txt",
	    "UnicodeData-02.txt", "UnicodeData-03.txt",
	    "UnicodeData-04.txt", "UnicodeData-05.txt",
	    "UnicodeData-06.txt", "UnicodeData-07.txt",
	    "UnicodeData-08.txt", "UnicodeData-09.txt",
	    "UnicodeData-10.txt", "UnicodeData-11.txt",
	    "UnicodeData-12.txt", "UnicodeData-13.txt",
	    "UnicodeData-14.txt", "UnicodeData-15.txt",
	    "UnicodeData-16.txt", "UnicodeData-16b.txt", "UnicodeData-17.txt",
	    "UnicodeData-18.txt", "UnicodeData-18b.txt", "UnicodeData-19.txt"
	};
	try {
	    for ( int cnt=0 ; cnt<pieces.length ; cnt++ ) {
		reportProgress(progressHandler, cnt, pieces.length);
		String file = pieces[cnt];
		BufferedReader rd
		    = new BufferedReader(new InputStreamReader(amgr.open(file),
							       "US-ASCII"));
		String line;
		while ( ( line = rd.readLine() ) != null ) {
		    String[] fields = line.split(";");
		    int codePoint = Integer.parseInt(fields[0], 16);
		    String name = fields[1];
		    String category = fields[2];
		    if ( name.charAt(0) == '<' )
			continue;
		    cmd.bindLong(1, codePoint);
		    cmd.bindString(2, name);
		    cmd.bindString(3, category);
		    cmd.execute();
		}
		rd.close();
	    }
	    db.setTransactionSuccessful();
	    this.needPopulate = false;
	} catch (UnsupportedEncodingException e) {
	    throw new AssertionError("US-ASCII encoding unsupported");
	} catch (IOException e) {
	    throw new RuntimeException(e);
	} finally {
	    db.endTransaction();
	    db.close();
	    reportProgress(progressHandler, pieces.length, pieces.length);
	}
    }

    protected final static String[] queryColumns = { "id", "name", "category" };

    public static UnicodeCharacter getSingleSpecial(UnicodeCharacter.SpecialRange rng,
						    int codePoint) {
	return new UnicodeCharacter(codePoint,
				    rng.getName(codePoint),
				    rng.getCategory());
    }

    public UnicodeCharacter getSingle(int codePoint) {
	for ( UnicodeCharacter.SpecialRange rng : UnicodeCharacter.SpecialRange.values() )
	    if ( rng.belongs(codePoint) )
		return getSingleSpecial(rng, codePoint);
	final Cursor c
	    = db.query(UNICODE_TABLE_NAME, queryColumns,
		       "id=?",
		       new String[] { Integer.toString(codePoint) },
		       null, null, null, null);
	UnicodeCharacter ch;
	if ( c.getCount() > 0 ) {
	    c.moveToFirst();
	    ch = new UnicodeCharacter(c.getInt(0),
				      c.getString(1),
				      UnicodeCharacter.Category.fromCode(c.getString(2)));
	} else
	    ch = null;
	c.close();
	return ch;
    }

    public int countRange(int from, int to) {
	int extra = 0;
	for ( UnicodeCharacter.SpecialRange rng : UnicodeCharacter.SpecialRange.values() )
	    extra += rng.interCount(from, to);
	final Cursor c
	    = db.rawQuery("SELECT count(*) FROM "+UNICODE_TABLE_NAME
			  +" WHERE id >= ? AND id < ?",
			  new String[] { Integer.toString(from),
					 Integer.toString(to) });
	if ( c.getCount() != 1 )
	    throw new AssertionError("\"SELECT count(*)\" returned no result");
	c.moveToFirst();
	int cnt = c.getInt(0);
	c.close();
	return cnt + extra;
    }

    private static class CursorIterable
	implements Iterable<UnicodeCharacter> {
	protected final Cursor c;
	public CursorIterable(Cursor c) {
	    this.c = c;
	}
	public Iterator<UnicodeCharacter> iterator() {
	    final boolean hasData = c.getCount() > 0;
	    if ( hasData )
		c.moveToFirst();
	    else
		c.close();
	    return (new Iterator<UnicodeCharacter>() {
		boolean hasNext = hasData;
		public boolean hasNext() { return hasNext; }
		public UnicodeCharacter next() {
		    if ( hasNext ) {
			UnicodeCharacter ch
			    = new UnicodeCharacter(c.getInt(0), c.getString(1),
						   UnicodeCharacter.Category.fromCode(c.getString(2)));
			if ( c.isLast() ) {
			    hasNext = false;
			    c.close();
			} else
			    c.moveToNext();
			return ch;
		    } else
			throw new NoSuchElementException();
		}
		public void remove() {
		    throw new UnsupportedOperationException();
		}
	    });
	}
    }

    private static class SpecialRangeIterable
	implements Iterable<UnicodeCharacter> {
	protected final UnicodeCharacter.SpecialRange rng;
	protected final int from;  protected final int to;
	public SpecialRangeIterable(UnicodeCharacter.SpecialRange rng,
				    int from, int to) {
	    this.rng = rng;
	    this.from = from;
	    this.to = to;
	}
	public Iterator<UnicodeCharacter> iterator() {
	    return (new Iterator<UnicodeCharacter>() {
		int i = from;
		public boolean hasNext() { return i < to; }
		public UnicodeCharacter next() {
		    if ( i < to )
			return getSingleSpecial(rng, i++);
		    else
			throw new NoSuchElementException();
		}
		public void remove() {
		    throw new UnsupportedOperationException();
		}
	    });
	}
    }

    public Iterable<UnicodeCharacter> getRange(int from, int to) {
	List<UnicodeCharacter> annoyance = null;
	boolean muchAnnoyance = false;
	for ( UnicodeCharacter.SpecialRange rng : UnicodeCharacter.SpecialRange.values() ) {
	    if ( rng.inside(from, to) ) {
		return new SpecialRangeIterable(rng, from, to);
	    } else if ( rng.interCount(from, to) > 0 ) {
		if ( annoyance == null )
		    annoyance
			= new ArrayList<UnicodeCharacter>(rng.interCount(from, to));
		else
		    muchAnnoyance = true;
		int from0 = Math.max(rng.getFrom(), from);
		int to0 = Math.min(rng.getTo(), to);
		Iterable<UnicodeCharacter> rangeIt
		    = new SpecialRangeIterable(rng, from0, to0);
		for ( UnicodeCharacter ch : rangeIt )
		    annoyance.add(ch);
	    }
	}
	final Cursor c
	    = db.query(UNICODE_TABLE_NAME, queryColumns,
		       "id >= ? AND id < ?",
		       new String[] { Integer.toString(from),
				      Integer.toString(to) },
		       null, null, "id", null);
	if ( annoyance != null ) {
	    Iterable<UnicodeCharacter> cursorIt
		= new CursorIterable(c);
	    for ( UnicodeCharacter ch : cursorIt ) {
		annoyance.add(ch);
		muchAnnoyance = true;
	    }
	    if ( muchAnnoyance )
		Collections.sort(annoyance, new Comparator<UnicodeCharacter>() {
			public int compare(UnicodeCharacter ch1,
					   UnicodeCharacter ch2) {
			    int cp1 = ch1.getCodePoint();
			    int cp2 = ch2.getCodePoint();
			    return (cp1<cp2)?-1:(cp1>cp2)?1:0;
			}
		    });
	    return annoyance;
	}
	return new CursorIterable(c);
    }

    public Iterable<UnicodeCharacter> searchNames(String like, int limit) {
	final Cursor c
	    = db.query(UNICODE_TABLE_NAME, queryColumns,
		       "name LIKE ?",
		       new String[] { like },
		       null, null, "id",
		       (limit>0)?Integer.toString(limit):null);
	return new CursorIterable(c);
    }

    public void close() {
	db.close();
	db = null;
    }

}
