//---------------------------------------------------------------------------
#ifndef TIPTOPBD_H_
#define TIPTOPBD_H_

//---------------------------------------------------------------------------
#include <math.h>
#include <wx/string.h>
#include <wx/wfstream.h>
#include <wx/mstream.h> //Поток в память
#include <wx/stream.h> //Поток буф
#include "structs.h"
#include "tcDebug.h"
//---------------------------------------------------------------------------
typedef unsigned char uint1;
typedef unsigned short uint2;
typedef unsigned int uint4;
typedef unsigned __int64 uint8;
typedef char int1;
typedef short int2;
typedef int int4;
typedef __int64 int8;
//---------------------------------------------------------------------------
const uint2 file_id=65500;//*((uint2*)"TB");//16980 id файла базы данных и таблицы 2 байта
const uint2 file_version=1; //текущая версия файла базы данных 2 байта
//---------------------------------------------------------------------------
//типы данных которые можно хранить в базе под них 1 байт
#define BD_UINT1 0 //1 байт без знаковый
#define BD_UINT2 1 //2 байт без знаковый
//#define BD_UINT3 2 //3 байт без знаковый
#define BD_UINT4 3 //4 байт без знаковый
//#define BD_UINT5 4 //5 байт без знаковый
//#define BD_UINT6 5 //6 байт без знаковый
//#define BD_UINT7 6 //7 байт без знаковый
#define BD_UINT8 7 //8 байт без знаковый
#define BD_INT1 10 //1 байт со знаковый
#define BD_INT2 11 //2 байт со знаковый
//#define BD_INT3 12 //3 байт со знаковый
#define BD_INT4 13 //4 байт со знаковый
//#define BD_INT5 14 //5 байт со знаковый
//#define BD_INT6 15 //6 байт со знаковый
//#define BD_INT7 16 //7 байт со знаковый
#define BD_INT8 17 //8 байт со знаковый
#define BD_FLOAT4 20 //17 - float 4_байта
//#define BD_FLOAT6 21 //18 - real 6_байт
#define BD_FLOAT8 22 //19 - double 8_байт
#define BD_BOOL 30 //1 байт 0-false 1-true
#define BD_UTF8_1 100 //100 - utf8_1 string 1й байт размер строки в байтах
#define BD_UTF8_2 101 //101 - utf8_2 string 1х 2 байта размер строки в байтах
//#define BD_UTF8_4 102 //101 - utf8_2 string 1х 4 байта размер строки в байтах
//#define BD_ASCII_1 110 //102 - asci_1 строка 1й байт размер строки в байтах
//#define BD_ASCII_2 112 //103 - asci_2 строка 1х 2 байта размер строки в байтах
//-#define BD_ASCII_0 120 //Строка с 0 на конце 
//для этих двоичных объектов в начале передаются не данные а только размер потом обновляют запись по id записи
#define BD_GPS_4 130 //250 - GPS коордионаты в формате: uint4 количество байт,float,float (широта lat,долгота lon в WGS-84)
#define BD_GPS_8 131 //251 - GPS коордионаты в формате: uint4 количество байт, double,double (широта lat,долгота lon в WGS-84 )
#define BD_GPSS_8 132 //252 - GPS координаты в формате: uint4 количество байт, uint4 количество сегментов, uint4 количество точек в первом сегменте,(double,double точки первого сегмента)
//#define BD_BLOB_1 140 //254 - двоичные данные uint1 количество байт
//#define BD_BLOB_2 141 //254 - двоичные данные uint2 количество байт
//#define BD_BLOB_3 142 //254 - двоичные данные uint3 количество байт
#define BD_BLOB_4 143 //254 - двоичные данные uint4 количество байт
//#define BD_BLOB_8 144 //255 - двоичные данные uint8 количество байт


//---------------------------------------------------------------------------
//"Типа" свой поток для записи в память либо в файл
class TiptopStream
{
	private:
		uint4 m_posRead; //Текущая позиция при записи
		uint4 m_posWrite; //Текущая позиция при чтении
		uint4 m_size; //Размер данных
		//список страниц базы данных
		unsigned int m_pageSize; //Размер страницы данных
		TSimpleList<unsigned char*>* m_pages; //список страниц данных
	public:
		TiptopStream(){ m_pageSize=1024;/*4096;*/ m_pages=new TSimpleList<unsigned char*>(10,true); m_posRead=0; m_posWrite=0; m_size=0; };
		~TiptopStream(){ delete m_pages; };
		void Clear()
		{	m_pages->clear();
		}
		uint4 GetSize(){ return m_size; };
		void Read(const void* buf, unsigned int size) //Прочитать данные в buffer размером
		{
			unsigned char* buffer=(unsigned char*)buf;
			while(size>0)
			{
				unsigned int b=(m_posRead / (float)m_pageSize); //Блок
				unsigned int p=m_posRead - b * m_pageSize; //Позиция в блоке
				unsigned char* ch=m_pages->get(b) + p; //Буфер на нужной позиции
				unsigned int s = m_pageSize - p; //сколько можно и нужно прочитать из блока
				if(s > size) s=size;
				memcpy(buffer, ch, s);
				buffer+=s;
				size-=s;
				m_posRead+=s;
			}
		};
		void Read(wxOutputStream* os) //Записать все данные в os
		{
			uint4 s=m_size;
			for(uint4 i=0;i<m_pages->count();i++)
			{
				unsigned char* ch=m_pages->get(i);
				if(s>m_pageSize)
				{	s-=m_pageSize;
					os->Write(ch,m_pageSize);
				}
				else os->Write(ch,s);
			}
		}

		void Write(const void* buf, unsigned int size)
		{
			unsigned char* buffer=(unsigned char*)buf;
			if(m_posWrite+size>m_pages->count()*m_pageSize) //Расширить память
			{
				unsigned int s=ceil((m_posWrite+size) / (float)m_pageSize);
				if(s>m_pages->count())
				{	s-=m_pages->count();
					for(unsigned int i=0;i<s;i++) m_pages->add( new unsigned char[m_pageSize] );
				}
			}
			while(size>0)
			{
				unsigned int b=floor(m_posWrite / (float)m_pageSize); //Находим нужный блок памяти
				unsigned int p=m_posWrite - b * m_pageSize; //Позиция с начала блока памяти
				unsigned char* ch=m_pages->get(b) + p;
				unsigned int s = m_pageSize - p;
				if(s > size) s=size;
				memcpy(ch, buffer, s);
				buffer+=s;
				size-=s;
				m_posWrite+=s;
			}
			if(m_size<m_posWrite+size) m_size=m_posWrite+size;
		};
		void Write(wxInputStream* s)
		{
			unsigned char* buf=new unsigned char[1024];
			while(true)
			{
				s->Read(buf,1024);
				unsigned int r=s->LastRead();
				if(r==0) break;
				Write(buf,r);
			}
			delete buf;
		};

		void SeekRead(uint4 pos){ m_posRead=pos; };
		uint4 TellRead(){ return m_posRead; };
		void SeekWrite(uint4 pos) { m_posWrite=pos; }
		uint4 TellWrite(){ return m_posWrite; };
};
//---------------------------------------------------------------------------
//Преобразовать значние в соответствии с типом если int и пустая строка то NULL
wxString getSQLValue(wxString t,wxString v);
//---------------------------------------------------------------------------
class TiptopTable;
//---------------------------------------------------------------------------
/*
//http://slady.net/java/bt/view.php?w=1024&h=768
//узел двоичного дерева фиксированной длины (высчитать размер надо чтоб с диска читалось примерно по 512 байта)
//По определению, двоичное дерево называется АВЛ деревом в том и только в том случае, когда высоты двух поддеревьев каждой из вершин дерева отличаются не более, чем на единицу
class BTreeNode
{
	private:
	public:
		int1 balance; //Баланс для АВЛ дерева -1 - левое длиней, 0 - сбалансированно, 1 - правое длиней
		uint4 val; //размеры свободного пространства
		uint4 pos; //позиции в файле для размера
		uint4 fllink,frlink; //ссылки на дочерние узлы в файле

		BTreeNode *llink,*rlink; //ссылки на дочерние узлы в памяти
		BTreeNode *parent; //родительмкий узел дла нормализации
		BTreeNode(){llink=NULL; rlink=NULL;};
		~BTreeNode(){};
};
//Индекс пустых мест в таблице создаётся для каждой таблицы (потом наверно и для всего файла надо будет сделать)
//отсортированно по возрастанию свободного места "кол-во места->позиция в файле" 
/*class TiptopIndexFreeSpase
{
	private:
//		TiptopTable* m_table;
		uint4 m_count; //количество элементов
		wxFFile* m_file; //чтение и запись 
		bool LLRotation(BTreeNode* p0,BTreeNode* p1,BTreeNode* p2); //Одинарный LL-поворот.
		bool RRRotation(BTreeNode* p0,BTreeNode* p1,BTreeNode* p2); //Одинарный RR-поворот.
		bool LRRotation(BTreeNode* p0,BTreeNode* p1,BTreeNode* p2); //Двойной LR-поворот.
		bool RLRotation(BTreeNode* p0,BTreeNode* p1,BTreeNode* p2); //Двойной RL-поворот.
	public:
		TiptopIndexFreeSpase(TiptopTable* table);
		~TiptopIndexFreeSpase();
		uint4 findFree(uint4 size); //найти позицию в файле с минимальным подходящим размером для записи
		bool setUseSpace(uint4 pos,uint4 size); //сообщить что данная позиция занята
		bool setFreeSpase(uint4 pos,uint4 size); //сообщить что данные освободились
		void updateLast(); //обновить размер по данным поледнего вызова findFree
};

//Есть 4 варианта нарушения балансировки АВЛ-дерева

//Одинарный LL-поворот. Выполняется, когда <перевес> идет по пути L-L от узла с нарушенной балансировкой.
bool TiptopIndexFreeSpase::LLRotation(BTreeNode* p0,BTreeNode* p1,BTreeNode* p2) 
{
	p1 := p^.llink;
	p^.llink := p1^.rlink;
	p1^.rlink := p;
	p := p1;
}
//Одинарный RR-поворот. Выполняется, когда <перевес> идет по пути R-R от узла с нарушенной балансировкой.
bool TiptopIndexFreeSpase::RRRotation(BTreeNode* p0,BTreeNode* p1,BTreeNode* p2) 
{
	p1 := p^.rlink;
	p^.rlink := p1^.llink;
	p1^.llink := p;
	p := p1;
}
//Двойной LR-поворот. Выполняется, когда <перевес> идет по пути L-R от узла с нарушенной балансировкой.
bool TiptopIndexFreeSpase::LRRotation(BTreeNode* p0,BTreeNode* p1,BTreeNode* p2) 
{
	p1 := p^.llink;
	p2 := p1^.rlink;
	p1^.rlink := p2^.llink;
	p2^.llink := p1;
	p^.llink := p2^.rlink;
	p2^.rlink := p;
	p := p2;
}
//Двойной RL-поворот. Выполняется, когда <перевес> идет по пути R-L от узла с нарушенной балансировкой.
bool TiptopIndexFreeSpase::RLRotation(BTreeNode* p0,BTreeNode* p1,BTreeNode* p2) 
{
	p1 := p^.rlink;
	p2 := p1^.llink;
	p1^.llink := p2^.rlink;
	p2^.rlink := p1;
	p^.rlink := p2^.llink;
	p2^.llink := p;
	p := p2;
}
*/
//---------------------------------------------------------------------------
class TiptopField;
//---------------------------------------------------------------------------
//числовой индекс базы данных (для id поля он обязателен)
//Создаётся файл вида "id таблицы_id поля.i" (если id поля = 0 то это индекс свободных мест) 
class TiptopIndex
{
	private:
		uint4 m_type; //Тип индекса (1 - самый простой)
		uint4 m_count; //количество элементов
		wxFFile* m_file; //чтение и запись
	public:
		TiptopField* m_fl; //Поле таблицы для которого строится индекс

		TiptopIndex(TiptopField* fl);
		~TiptopIndex();
		void add(uint4 pos,uint4 id);
		bool getPos(uint4 val,uint4& pos,int4& num); //Поиск значения среди упорядоченного списка
};
//---------------------------------------------------------------------------
struct RuiPointPS
{	unsigned int pos; //позиция в файле
	unsigned int size; //Размер
};
//---------------------------------------------------------------------------
//Индекс пустых мест пока как таблица (потом переделать в двоичный список)
class TiptopIndexFreeSize
{
private:
	TSimpleList<RuiPointPS*>* m_Pos; ///<Массив позиции
public:
	TiptopIndexFreeSize()
	{	m_Pos=new TSimpleList<RuiPointPS*>(10,true);
	};
	~TiptopIndexFreeSize(){ delete m_Pos; };
	void add(unsigned int pos, unsigned int size) //Добавить "пустое место" в список
	{	RuiPointPS* p=new RuiPointPS;
		p->pos=pos; p->size=size;
		m_Pos->add(p);
	}; 
	bool del(unsigned int pos) //Удалить из списка свободных мест
	{	for(uint4 i=0;i<m_Pos->count();i++)
		if(m_Pos->get(i)->pos==pos) { return m_Pos->del(i); }
		return false;
	}; 
	bool upd(unsigned int pos, unsigned int size) //Сдвинуть позицию на заданый размер и уменьшить размер
	{
		for(uint4 i=0;i<m_Pos->count();i++)
		{
			if(m_Pos->get(i)->pos==pos) 
			{
				unsigned int s=m_Pos->get(i)->size;
				if(s==size) 
				{	m_Pos->del(i);
					break;
				}
				if(s<size) return false;
				else 
				{	m_Pos->get(i)->pos+=size;
					m_Pos->get(i)->size-=size;
				}
				break;
			}
		}
		return false;
	}; 
	bool get(unsigned int size,unsigned int &pos) //Получить позицию подходящую под заданный размер
	{	bool b=false;
		unsigned int min=0xffffffff;
		for(unsigned int i=0;i<m_Pos->count();i++)
		{
			if(m_Pos->get(i)->size>=size && m_Pos->get(i)->size<min) 
			{	pos=m_Pos->get(i)->pos;
				min=m_Pos->get(i)->size;
				b=true;
			}
		}
		return b;
	}; 
};
//---------------------------------------------------------------------------
class TiptopTable;
//---------------------------------------------------------------------------
//поле базы данных
class TiptopField
{
	private:
		//Для 1 го поля 1н индекс
	public:
		TiptopTable* m_tb; //Если NULL то поле не присоединено к таблице

		void* m_value; //ссылка на данные (может быть NULL)		
		uint4 m_size; //размер данных (Если 0 то в базе  NULL)

		wxString m_name; //название поля
		uint1 m_type_id; //id типа данных
		bool m_NULL; //может ли быть пустым
		bool m_index; //Нужен ли индекс для этого поля (не используется, может быть: первичным, вторичным, уникальным, просто индексом для поиска)
		TiptopIndex* m_idx; //Индекс поля (пока только для поля id)

		TiptopField(TiptopTable* tb); //Конструктор задаётся какой таблице принадлежит
		~TiptopField();
		void setValue(void* value,uint4 size); //Данные копируются
		void setValue(TiptopStream* is,uint4 size); //Записать данные
		void setValue(int value);
		void setValue(wxString value); //Прочитать данные из строки в соответствии с типом
		void setValue(bool value); //Присвоить bool значение
		void setValue(double value); //Присвоить double значение

		TiptopField* cloneNode(); ///< Клонировать узел

		uint4 getSize()
		{	if(m_value==NULL) return 0;
			return m_size;
		}; ///<Размер без учёта размера под данные
		uint4 getAllSize(); ///<Размер с учётом типа поля

		//Функции для взятия значений
		bool getBoolVal();
		uint4 getUint4Val();
		int4 getIntVal();
		double getDblVal();
		wxString getStrVal();
		wxString getStrSQLVal();
};
//---------------------------------------------------------------------------
class TiptopBD;
//---------------------------------------------------------------------------
//таблица данных может быть присоединённой к данным а может и отдельно волятся...
//Если отдельно то для неё не предназначенны вторичные ключи только 1 первичный и индексы...
class TiptopTable
{
	private:
		uint1 m_Type; //Тип таблицы 0-Плотная(Индекс не обязателен последовательный доступ) 1-Жидкая (Индекс обязателен)

		//wxOutputStream* m_os; ///<Для записи заголовка и данных данных
		//wxMemoryOutputStream* m_os; ///<Для записи заголовка и данных
		//bool m_osOwner; ///<Владелец ли m_os смотри деструктор

		//bool m_ok; //выполнилась ли последняя операция успешно

		//wxString m_path; //Путь к файлу таблицы
		int4 getNumFieldOnName(wxString name); //вернуть позицию поля по имени нумерация с 0, если -1 то нет такого
		TSimpleList2<uint8> m_Pos; ///<Массив позиции полей от начала файла либо потока

		TiptopIndexFreeSize* m_IndexFreeSize; //Индекс свободного пространства
		bool buildPosIndex(); ///<Построить индекс позиций прочитав таблацу он начала до конца
	public:
		//wxFileOutputStream* m_fos; //для записи в файл таблицы
		//wxFileInputStream* m_fis; //для чтения из файла таблицы
		//wxInputStream* m_is; ///<Для чтения заголовка таблицы и данных

		wxFFile* m_file; //чтение и запись
		TiptopStream* m_stream; ///<С этим потоком работает таблица для чтения и записи


		uint1* m_Null;//Массив байт для чтения и записи NULL значений (равно количеству столбцов не зависимо возможно ли NULL)
		uint1 m_NullSize;//количество байт для NULL, подсчитываются = cell(count fields/8)
		uint4 m_RecSize; ///<Размер последней прочитанной записи в байтах с учётом null и типов
		uint4 m_RecPos; ///<Позиция последней прочитаной записи
		TSimpleList<TiptopField*>* fields; //список столбцов владелец (идут попорядку id поля нет есть позиция)

		TiptopBD* m_bd; //Если NULL то таблица не присоединена к базе данных
		uint4 m_id; //id таблицы в нутри базы (или запроса)

		wxString m_name; //Название таблицы (читается из файла)
		int m_count; //Количество записей (заранее неизвестно если нет индекса)
		//uint4 rCount; //Количество записей в таблице (Информационное поле)

		TiptopTable(TiptopBD* bd,uint4 id);
		~TiptopTable();
		//bool isOk();
		bool AddField(TiptopField* fl);  ///<Добавить поле к таблице становится владельцем (без записи в поток)
		TiptopField* AddField(wxString name,uint1 type_id); ///<Добавить поле к таблице (без записи в поток)
		TiptopField* AddField(wxString name,wxString type); ///<Добавить поле к таблице (без записи в поток)
		bool OpenTable(); //открыть таблицу (если обьект TiptopTable не в базе если таблица не открылась то результат false а путь к файлу сохраниться)
		//bool OpenTable(wxInputStream* is); //Подгрузить таблицу из потока (Читает только заголовок)
		bool OpenTable(wxString path); //Прочитать из файла
//		bool SetOutputStream(wxMemoryOutputStream* os, bool owner=false);
//		const wxMemoryOutputStream* GetOutputStream(){return m_os;};

		bool ReadTableToMem(wxInputStream* is); ///<Прочитать таблицу из потока в оперативную память

		bool CreateTable(wxString SQL); //Создать файл таблицы через SQL запрос
		bool InsertInto(wxString SQL); //Добавить запись в таблицу и в индексы через SQL запрос
		bool UpdateTable(wxString SQL); //"простое" обновление записей через SQL запрос

		wxFFile* getStream(uint4 id,wxString field); //TODO надо сделать свой защищённый "поток"
		//wxMemoryInputStream* GetInputStream(); ///<Создаёт новый поток в который помещяет таблицу с данными для чтения (TiptopTable не остаётся владельцем)

		TiptopField* getField(wxString name); //Получить поле по имени (Для того чтобы получить по индексу используй TSimpleList)
		uint4 findFreeSize(uint4 size = 0); //Найти позицию с свободным местом для записи данных (из индекса свободных мест)

		bool FindFirstRecord(TiptopField* fl,uint4 &pos,uint4 &num); ///<Найти позицию первой попавшейся записи с заданными значениями поля, результат позиция записи в памяти.

		bool SeekToStart(); //Переместить курсор чтения на первую запись (чтоб потом можно было последовательно прочитать всю таблицу)
		bool ReadRecord(wxString field,uint4 val); //найти и прочитать запись
		bool ReadRecord(TiptopStream* is); //Позиция на нужном месте читаем запись из потока
		bool ReadRecord(int4 pos); //Перемещяем курсор в заданную позицию и читаем запись
		bool ReadNextRecord(bool savePos = false); //Прочитать следующую запись из таблицы
		bool ReadRecordByNum(uint4 num); //Читать запись по её порядковому номеру (должны заранее знать позиции записей)

		bool WriteHeader(); ///<Записать заголовок таблицы в поток m_os (Используется при создании таблицы)
		bool WriteRecord(bool savePos , bool findPos); ///<Записать 1 запись в конец m_os из полей (TODO как сделаю findFreeSize то в пустую позицию)
		bool WriteRecord(); //Записать запись в текущее положение потока m_os

		//bool WriteTableToStream(wxOutputStream* os); ///<Записать заголовок и тело таблицы в предоставленный поток

		void addRecPos(uint8 pos) //Добавить позицию записи в памяти
		{	m_Pos.add(pos);
		};
		bool delRecPos(uint8 pos); //Удалить позицию записи в памяти

		//uint4 getNumByRecPos(uint8 pos)//Получить порядковый номер записи по позиции

		/*uint4 GetRecPos(uint4 num) //Получить адрес записи по позиции
		{	if(m_Pos.count()==0) rerturn 0; //TODO исправить добавить bool к параметрам либо возвращять -1
			return m_Pos.get(num);
		};*/

		bool AppendTable(TiptopTable* tb, bool spw, bool spr); //Добавить записи из заданной в текущую (поля должны совпадать по имени и по типу)
		bool UpdateTable(TiptopTable* tb); //Обновить записи из заданной в текущую (поля должны совпадать по имени и по типу)
		bool UpdateRecord(); ///<Обновить 1 запись удаляем потом пишем
};
//---------------------------------------------------------------------------
//база данных (набор таблиц)
class TiptopBD
{
	private:
		wxFileOutputStream* m_fos; //поток чтения из файла
		wxFileInputStream* m_fis; //поток записи в файл
		TSimpleList<TiptopTable*>* list;//список таблиц
		uint4 m_maxid; //макс id таблиц (нумерация с 1)
		bool m_ok; //Нормально ли открылась база
	public:
		wxString m_path; //путь к папке с файлами базы
		TiptopBD();
		~TiptopBD();
		bool Open(wxString path); //задаётся путь к папке где лежит bd.bd файл

		bool TableExist(wxString name); //существует ли таблица
		//bool AddTable(wxString file); //добавить существующую таблицу в базу данных
		TiptopTable* getTable(wxString name); //Получить объект таблицы по её имени
		bool ExecSQL(wxString SQL); //выполнить SQL запрос без результата

		bool addTable(uint4 id); //Добавить таблицу в базу под заданным id
};
//---------------------------------------------------------------------------

#endif
