package com.one;

import com.vmx.AuxClass;
import com.vmx.InputStreamDecoder;
import com.vmx.Locale;
import com.vmx.ProgressCallback;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import javax.microedition.lcdui.Image;
import javax.microedition.rms.InvalidRecordIDException;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
import javax.microedition.rms.RecordStoreNotOpenException;

public class ModuleRegistry
{
	public static final int ACTION_UNKNOWN = 0;
	public static final int ACTION_EXPLORE = 1;
	public static final int ACTION_OPEN = 2;

	protected static Hashtable modules = new Hashtable();		// файловые модули по расширениям
	protected static Hashtable actions = new Hashtable();		// действия по расширениям и модулям
	//protected static Hashtable instances = new Hashtable();	// загруженные объекты по расширениям
	protected static Hashtable icons = new Hashtable();			// значки по модулям
	protected static Hashtable locnames = new Hashtable();		// локализованные имена по модулям
	protected static Hashtable similars = new Hashtable();		// списки похожих модулей по группам
	protected static Vector operations = new Vector();			// операции
	protected static Vector filesources = new Vector();			// источники файлов
	protected static Vector contsources = new Vector();			// источники контейнеров
	protected static Hashtable extensions = new Hashtable();	// расширения файлов по модулям
	protected static Vector optstores = new Vector();			// хранилища настроек
	protected static Hashtable errcodes = new Hashtable();		// базы кодов ошибок

	protected static Vector modlist = new Vector();				// упорядоченный список для modules
	protected static Vector simlist = new Vector();				// упорядоченный список для similars

	protected static Hashtable rmsmap;							// номера записей по модулям

	/**
	 * Очистить все списки модулей (например, перед повторной загрузкой).
	 */
	public static void reset()
	{
		modules.clear();
		actions.clear();
		icons.clear();
		locnames.clear();
		similars.clear();
		operations.removeAllElements();
		filesources.removeAllElements();
		contsources.removeAllElements();
		extensions.clear();
		optstores.removeAllElements();
		errcodes.clear();
		modlist.removeAllElements();
		simlist.removeAllElements();

		rmsmap = null;
	}

	/**
	 * Чтение абстрактного списка модулей (файл modules.ini).
	 */
	public static void readModuleList() throws IOException
	{
		IniFile ini = new IniFile("/config/modules.ini", false);

		Enumeration sections = ini.listSections();
		String section;
		Integer action;
		Vector target;

		Enumeration keys;
		String key, value;
		int index;
		String locname;
		String iconspec;
		Integer errcode;

		while(sections.hasMoreElements())
		{
			section = (String)sections.nextElement();

			if(section.equalsIgnoreCase("Container"))
			{
				action = new Integer(ACTION_EXPLORE);
				target = null;
			}
			else if(section.equalsIgnoreCase("Application"))
			{
				action = new Integer(ACTION_OPEN);
				target = null;
			}
			else if(section.equalsIgnoreCase("Operation"))
			{
				action = null;
				target = operations;
			}
			else if(section.equalsIgnoreCase("FileSource"))
			{
				action = null;
				target = filesources;
			}
			else if(section.equalsIgnoreCase("ContainerSource"))
			{
				action = null;
				target = contsources;
			}
			else
			{
				continue;
			}

			keys = ini.listKeys(section);

			while(keys.hasMoreElements())
			{
				key = (String)keys.nextElement();

				if(!AuxClass.classExists(key))
				{
					continue;
				}

				value = ini.getRecord(section, key);

				index = value.indexOf(',');

				if(index >= 0)
				{
					locname = value.substring(0, index).trim();
					iconspec = value.substring(index + 1);
				}
				else
				{
					locname = value;
					iconspec = null;
				}

				errcode = null;
				
				if(locname.startsWith("#"))
				{
					try
					{
						errcode = new Integer(Integer.parseInt(key.substring(1).trim()) + Locale.getOffset(key));
					}
					catch(NumberFormatException nfe)
					{
					}
				}
				
				if(errcode == null)
				{
					errcode = new Integer(locname.hashCode() % 1000);
				}
				
				errcodes.put(key, errcode);

				if(action != null)
				{
					actions.put(key.toLowerCase(), action);
					modlist.addElement(key);
				}

				if(target != null)
				{
					target.addElement(key);
				}

				locnames.put(key, locname);

				if(iconspec != null)
				{
					icons.put(key.toLowerCase(), iconspec);
				}
			}
		}
	}

	/**
	 * Чтение ассоциаций расширений файлов с модулями (файл types.ini).
	 */
	public static void readTypeList() throws IOException
	{
		if(readCustomTypeList())
		{
			return;
		}

		IniFile ini = new IniFile("/config/types.ini", false);

		Enumeration sections = ini.listSections();
		String section;

		Enumeration keys;
		String key, value;

		Integer action;
		Vector tokens;
		String ext;

		while(sections.hasMoreElements())
		{
			section = (String)sections.nextElement();
			keys = ini.listKeys(section);

			if(section.equalsIgnoreCase("Open"))
			{
				while(keys.hasMoreElements())
				{
					key = (String)keys.nextElement();
					value = ini.getRecord(section, key);

					if(!AuxClass.classExists(value))
					{
						continue;
					}

					action = (Integer)actions.get(value.toLowerCase());

					tokens = StringPattern.tokenizeString(key, ' ');

					for(int i = 0; i < tokens.size(); i++)
					{
						ext = ((String)tokens.elementAt(i)).toLowerCase();

						modules.put(ext, value);

						if(action != null)
						{
							actions.put(ext, action);
						}
					}
				}
			}
			else if(section.equalsIgnoreCase("Create") ||
				    section.equalsIgnoreCase("Special"))
			{
				while(keys.hasMoreElements())
				{
					key = (String)keys.nextElement();
					value = ini.getRecord(section, key);

					extensions.put(key, value);
				}
			}
		}

		saveCustomTypeList();
	}

	/**
	 * Чтение списка групп схожих модулей (файл groups.ini).
	 */
	public static void readGroupList() throws IOException
	{
		InputStreamDecoder ini = InputStreamDecoder.getResourceDecoder("/config/groups.ini");
		IniRecord record = null;

		Vector tokens;

		while((record = IniRecord.getNextRecord(ini)) != null)
		{
			tokens = StringPattern.tokenizeString(record.value, ',');

			for(int i = 0; i < tokens.size(); i++)
			{
				tokens.setElementAt(((String)tokens.elementAt(i)).trim(), i);
			}

//			if(record.key.startsWith("#"))
//			{
//				record.key = Locale.getString(this, Integer.parseInt(record.key.substring(1).trim()));
//			}

			simlist.addElement(record.key);
			similars.put(record.key, tokens);
		}

		ini.close();
	}

	/**
	 * Чтение списка хранилищ настроек (файл options.ini).
	 */
	public static void readOptionStoreList() throws IOException
	{
		InputStreamDecoder ini = InputStreamDecoder.getResourceDecoder("/config/options.ini");
		IniRecord record = null;

		while((record = IniRecord.getNextRecord(ini)) != null)
		{
			if(!AuxClass.classExists(record.key))
			{
				continue;
			}
			
			optstores.addElement(record.key);
			locnames.put(record.key, record.value);
		}

		ini.close();
	}

	/**
	 * Восстановить настройки для всех зарегистрированных хранилищ.
	 */
	public static void restoreModuleOptions()
	{
		Enumeration e = optstores.elements();
		String s;

		while(e.hasMoreElements())
		{
			((OptionStorage)ModuleRegistry.getModuleInstance((String)e.nextElement())).restoreOptions();
		}
	}

	/**
	 * Сохранить настройки для всех зарегистрированных хранилищ.
	 */
	public static void saveModuleOptions()
	{
		Enumeration e = optstores.elements();
		String s;

		while(e.hasMoreElements())
		{
			((OptionStorage)ModuleRegistry.getModuleInstance((String)e.nextElement())).saveOptions();
		}
	}

	/**
	 * Достать экземпляр данного модуля.
	 *
	 * @param module имя класса модуля
	 * @return экземпляр модуля, или null
	 */
	public static Object getModuleInstance(String module)
	{
		Object instance = null; // instances.get(module);

		if(instance == null)
		{
			try
			{
				instance = Class.forName(module).newInstance();
				//instances.put(module, instance);
			}
			catch(Throwable t)
			{
				throw new RuntimeException("Unable to instantiate module '" + module + "': " + t.toString());
			}
		}

		return instance;
	}

	/**
	 * Получить имя класса модуля, связанного с данным расширением.
	 *
	 * @param key расширение файла (без точки)
	 * @return имя класса модуля, или null
	 */
	public static String getModule(String ext)
	{
		return (String)modules.get(ext.toLowerCase());
	}

	/**
	 * Получить имя класса модуля, который в списке модулей идет под данным индексом.
	 *
	 * @param index индекс модуля в списке
	 * @return имя класса модуля
	 */
	public static String getModule(int index)
	{
		return (String)modlist.elementAt(index);
	}

	/**
	 * Получить локализованное имя модуля с данным именем класса.
	 *
	 * @param module имя класса модуля
	 * @return локализованное имя модуля
	 */
	public static String getModuleName(String module)
	{
		return TextProcessor.getString(module, (String)locnames.get(module));
	}

	/**
	 * Получить действие для данного ключа.
	 * Ключом может быть как расширение файла,
	 * так и имя класса модуля.
	 *
	 * @param key ключ
	 * @return номер действия
	 */
	public static int getAction(String key)
	{
		Integer action = (Integer)actions.get(key.toLowerCase());

		if(action == null)
		{
			return ACTION_UNKNOWN;
		}

		return action.intValue();
	}

	/**
	 * Получить значок для данного ключа.
	 * Ключом может быть как расширение файла,
	 * так и имя класса модуля.
	 *
	 * @param key ключ
	 * @return значок, или null
	 */
	public static Image getIcon(String key)
	{
		key = key.toLowerCase();
		
		Object object = icons.get(key);

		if(object instanceof Image)
		{
			return (Image)object;
		}
		else
		{
			object = modules.get(key);

			if(object != null)
			{
				return getIcon((String)object);
			}
		}

		return null;
	}

	/**
	 * Получить расширение файла для данного модуля.
	 *
	 * @param module имя класса модуля
	 * @return расширение файла, или null
	 */
	public static String getExtension(String module)
	{
		return (String)extensions.get(module);
	}

	/**
	 * Получить список расширений,
	 * для которых есть модуль.
	 *
	 * @return Enumeration
	 */
	public static Enumeration listExtensions()
	{
		return modules.keys();
	}

	/**
	 * Получить базовый номер для ошибок в данном модуле.
	 *
	 * Пример использования:
	 * ErrScreen.showErrMsg(getErrCode() * 100 + LocalErrCode, exception);
	 * где LocalErrCode - местный код ошибки, в данном случае
	 * под местные коды отводится 100 номеров (* 100).
	 *
	 * @param module имя класса модуля
	 * @return базовый код ошибки
	 */
	public static int getErrCode(String module)
	{
		Integer errcode = (Integer)errcodes.get(module);

		if(errcode != null)
		{
			return errcode.intValue();
		}

		return 0;
	}

	/**
	 * Получить список модулей.
	 * @return Enumeration
	 */
	public static Enumeration listModules()
	{
		return modlist.elements();
	}

	/**
	 * Получить список файловых источников.
	 * @return Enumeration
	 */
	public static Enumeration listFileSources()
	{
		return filesources.elements();
	}

	/**
	 * Получить список источников контейнеров.
	 * @return Enumeration
	 */
	public static Enumeration listContainerSources()
	{
		return contsources.elements();
	}

	/**
	 * Получить список операций.
	 * @return Enumeration
	 */
	public static Enumeration listOperations()
	{
		return operations.elements();
	}

	/**
	 * Получить имя класса операции, которая в списке операций идет под данным индексом.
	 *
	 * @param index индекс операции в списке
	 * @return имя класса операции
	 */
	public static String getOperation(int index)
	{
		return (String)operations.elementAt(index);
	}

	/**
	 * Получить список хранилищ настроек.
	 * @return Enumeration
	 */
	public static Enumeration listOptionStores()
	{
		return optstores.elements();
	}

	/**
	 * Получить список имен групп схожести.
	 * @return Enumeration
	 */
	public static Enumeration listSimalarityGroups()
	{
		return simlist.elements();
	}

	/**
	 * Получить список модулей в группе схожести.
	 *
	 * @param group группа схожести
	 * @return Enumeration
	 */
	public static Enumeration listSimilars(String group)
	{
		Vector tokens = (Vector)similars.get(group);

		if(tokens == null)
		{
			return null;
		}

		return tokens.elements();
	}

	/**
	 * Связать расширение файла с заданным модулем.
	 *
	 * @param key расширение файла (без точки)
	 * @param module имя класса модуля
	 */
	public static String associate(String ext, String module, boolean save)
	{
		ext = ext.toLowerCase();

		String prev;

		if(module != null)
		{
			prev = (String)modules.put(ext, module);

			actions.put(ext, actions.get(module.toLowerCase()));
		}
		else
		{
			prev = (String)modules.remove(ext);

			actions.remove(ext);
		}

		//instances.remove(ext);

		if(save)
		{
			saveCustomTypeList();
		}

		return prev;
	}

	public static void loadIcons(ProgressCallback callback, boolean noEffects)
	{
		int ih = AbstractFont.getDefaultFont().getHeight();

		loadScaledIcons(ih);
		boolean updatecache = false;

		Enumeration keys = icons.keys();
		Vector vector = new Vector(icons.size());

		while(keys.hasMoreElements())
		{
			vector.addElement(keys.nextElement());
		}

		keys = vector.elements();

		String key;
		Object value;
		Image icon;

		while(keys.hasMoreElements())
		{
			key = (String)keys.nextElement();
			value = icons.get(key);

			if(value instanceof String)
			{
				icons.remove(key);
				
				try
				{
					icon = ImageProcessor.getImage("/img/", (String)value);

					if(true) // !noEffects)
					{
						icon = ImageProcessor.scaleImage(icon, -1, ih, !noEffects, true);
					}

					if(icon != null)
					{
						icons.put(key, icon);
						updatecache = true;

						callback.setProgress(0);
						callback.setMax(icons.size());
					}
				}
				catch(Exception e)
				{
				}
			}
		}

		if(updatecache)
		{
			saveScaledIcons(ih, callback);
		}
	}

	public static void loadIcons(InputStreamDecoder ini, ProgressCallback callback, boolean noEffects) throws IOException
	{
		icons.clear();
		clearScaledIcons();

		operations.removeAllElements();
		filesources.removeAllElements();
		contsources.removeAllElements();
		modlist.removeAllElements();
		
		IniRecord record = null;

		while((record = IniRecord.getNextRecord(ini)) != null)
		{
			try
			{
				icons.put(record.key.toLowerCase(), record.value);
			}
			catch(Exception e)
			{
			}
		}

		readModuleList();
		loadIcons(callback, noEffects);
	}

	protected static void clearScaledIcons()
	{
		try
		{
			RecordStore.deleteRecordStore(AuxClass.getStoreName(5));
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	protected static void loadScaledIcons(int iconHeight)
	{
		try
		{
			RecordStore rs = RecordStore.openRecordStore(AuxClass.getStoreName(5), true);

			if(rs.getNumRecords() == 0)
			{
				rs.closeRecordStore();
				return;
			}

			DataInputStream dis = new DataInputStream(new ByteArrayInputStream(rs.getRecord(1)));
			rs.closeRecordStore();

			if(dis.readInt() != iconHeight)
			{
				dis.close();
				return;
			}

			int count = dis.readInt();

			String module;
			Image icon;

			for(int i = 0; i < count; i++)
			{
				module = dis.readUTF();
				icon = ImageProcessor.readImage(dis);

				icons.put(module, icon);
			}

			dis.close();
		}
		catch(Throwable t)
		{
			ErrScreen.showErrMsg(129, t);
		}
	}

	protected static void saveScaledIcons(int iconHeight, ProgressCallback callback)
	{
		try
		{
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			DataOutputStream dos = new DataOutputStream(baos);

			dos.writeInt(iconHeight);

			dos.writeInt(modlist.size() + operations.size() + filesources.size() + contsources.size());

			CompoundEnumeration keys = new CompoundEnumeration();
			
			keys.addEnumeration(modlist.elements());
			keys.addEnumeration(operations.elements());
			keys.addEnumeration(filesources.elements());
			keys.addEnumeration(contsources.elements());

			String key;

			while(keys.hasMoreElements())
			{
				key = ((String)keys.nextElement()).toLowerCase();

				dos.writeUTF(key);
				ImageProcessor.writeImage(dos, (Image)icons.get(key), PNGFile.TRUECOLOR_ALPHA, PNGFile.FILTER_NONE, null);

				callback.progress(1);
			}

			byte[] data = baos.toByteArray();
			dos.close();

			RecordStore rs = RecordStore.openRecordStore(AuxClass.getStoreName(5), true);

			if(rs.getNumRecords() == 0)
			{
				rs.addRecord(data, 0, data.length);
			}
			else
			{
				rs.setRecord(1, data, 0, data.length);
			}

			rs.closeRecordStore();
		}
		catch(Throwable t)
		{
			ErrScreen.showErrMsg(130, t);
		}
	}

	public static void loadRMSMap()
	{
		try
		{
			RecordStore rs = RecordStore.openRecordStore(AuxClass.getStoreName(6), true);

			if(rs.getNumRecords() == 0)
			{
				rs.addRecord(null, 0, 0);
				rs.closeRecordStore();

				rmsmap = new Hashtable();

				return;
			}

			DataInputStream dis = new DataInputStream(new ByteArrayInputStream(rs.getRecord(1)));

			int count = dis.readInt();
			rmsmap = new Hashtable(count + count / 2);

			for(int i = 0; i < count; i++)
			{
				rmsmap.put(dis.readUTF(), new Integer(dis.readInt()));
			}

			dis.close();

			rs.closeRecordStore();
		}
		catch(Throwable t)
		{
			ErrScreen.showErrMsg(131, t);
		}
	}

	protected static void saveRMSMap(RecordStore rs) throws IOException, RecordStoreNotOpenException, InvalidRecordIDException, RecordStoreException
	{
		if(rmsmap == null)
		{
			return;
		}

		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		DataOutputStream dos = new DataOutputStream(baos);

		dos.writeInt(rmsmap.size());

		Enumeration keys = rmsmap.keys();
		String key;

		while(keys.hasMoreElements())
		{
			key = (String)keys.nextElement();

			dos.writeUTF(key);
			dos.writeInt(((Integer)rmsmap.get(key)).intValue());
		}

		byte[] data = baos.toByteArray();
		dos.close();

		rs.setRecord(1, data, 0, data.length);
	}

	public static byte[] getModuleData(String module)
	{
		if(rmsmap == null)
		{
			loadRMSMap();
		}

		try
		{
			RecordStore rs = RecordStore.openRecordStore(AuxClass.getStoreName(6), true);

			if(rs.getNumRecords() == 0)
			{
				rs.addRecord(null, 0, 0);
			}

			Integer id = (Integer)rmsmap.get(module);

			if(id == null)
			{
				id = new Integer(rs.addRecord(null, 0, 0));
				rmsmap.put(module, id);

				saveRMSMap(rs);
			}

			byte[] data = rs.getRecord(id.intValue());

			rs.closeRecordStore();

			return data;
		}
		catch(Throwable t)
		{
			ErrScreen.showErrMsg(132, t);
			return null;
		}
	}

	public static void setModuleData(String module, byte[] data)
	{
		if(rmsmap == null)
		{
			loadRMSMap();
		}
		
		try
		{
			RecordStore rs = RecordStore.openRecordStore(AuxClass.getStoreName(6), true);

			if(rs.getNumRecords() == 0)
			{
				rs.addRecord(null, 0, 0);
			}

			Integer id = (Integer)rmsmap.get(module);

			if(id == null)
			{
				id = new Integer(rs.addRecord(null, 0, 0));
				rmsmap.put(module, id);

				saveRMSMap(rs);
			}

			rs.setRecord(id.intValue(), data, 0, data.length);

			rs.closeRecordStore();
		}
		catch(Throwable t)
		{
			ErrScreen.showErrMsg(133, t);
		}
	}

	protected static boolean readCustomTypeList()
	{
		try
		{
			RecordStore rs = RecordStore.openRecordStore(AuxClass.getStoreName(7), true);

			if(rs.getNumRecords() == 0)
			{
				rs.closeRecordStore();
				return false;
			}

			DataInputStream dis = new DataInputStream(new ByteArrayInputStream(rs.getRecord(1)));

			int count = dis.readInt();
			String ext, module;

			Integer action;
			Object icon;

			for(int i = 0; i < count; i++)
			{
				ext = dis.readUTF();
				module = dis.readUTF();

				modules.put(ext, module);

				module = module.toLowerCase();

				action = (Integer)actions.get(module);
				icon = module; // (Object)icons.get(module);

				if(action != null)
				{
					actions.put(ext, action);
				}

				if(icon != null)
				{
					icons.put(ext, icon);
				}
			}

			count = dis.readInt();

			for(int i = 0; i < count; i++)
			{
				module = dis.readUTF();
				ext = dis.readUTF();

				extensions.put(module, ext);
			}

			dis.close();

			rs.closeRecordStore();

			return true;
		}
		catch(Throwable t)
		{
			ErrScreen.showErrMsg(134, t);
			return false;
		}
	}

	protected static void saveCustomTypeList()
	{
		try
		{
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			DataOutputStream dos = new DataOutputStream(baos);

			dos.writeInt(modules.size());

			Enumeration keys = modules.keys();
			String key;

			while(keys.hasMoreElements())
			{
				key = (String)keys.nextElement();

				dos.writeUTF(key);
				dos.writeUTF((String)modules.get(key));
			}

			dos.writeInt(extensions.size());

			keys = extensions.keys();

			while(keys.hasMoreElements())
			{
				key = (String)keys.nextElement();

				dos.writeUTF(key);
				dos.writeUTF((String)extensions.get(key));
			}

			byte[] data = baos.toByteArray();
			dos.close();

			RecordStore rs = RecordStore.openRecordStore(AuxClass.getStoreName(7), true);

			if(rs.getNumRecords() == 0)
			{
				rs.addRecord(data, 0, data.length);
			}
			else
			{
				rs.setRecord(1, data, 0, data.length);
			}

			rs.closeRecordStore();
		}
		catch(Throwable t)
		{
			ErrScreen.showErrMsg(135, t);
		}
	}
}
