// based on the SQLiteHelper poject
// Version 1.2
// Date: 2014-03-27
// http://sh.codeplex.com
// Dedicated to Public Domain
//
// modified by DL2ALF 2016

using System.Collections.Generic;
//
// SqliteDatabase.cs
//
// SQLite3 support simultaneous for Windows/Linux (Mono) without code change/recompilation
// Derived from Mono.Data.SqliteClient data access components for .Net
//
// Copyright (C) 2018  DL2ALF
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//using System.Data.Common;

using System.IO;
using System.Text;
using System;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using System.Reflection;
using System.Data;
using System.Runtime.InteropServices;
using System.Data.SQLite;
using System.Linq;

namespace System.Data.SQLite
{

    public class SQLiteDatabase
    {
        // contains real filename on disk if InMemory database
        public string DiskFileName = "";

        string dblocation;

        /// <summary>
        /// local database location
        /// ":memory:", if in-memory database
        /// </summary>
        public string DBLocation
        {
            get
            {
                if (isinmemory)
                    return ":memory:";
                else
                    return dblocation;
            }
        }

        bool isinmemory = false;

        /// <summary>
        /// indicates a in-memory database
        /// </summary>
        public bool IsInMemory
        {
            get
            {
                return isinmemory;
            }
        }


        /// <summary>
        /// local database connection string
        /// </summary>
        public string DBConnectionString
        {
            get
            {
                SQLiteConnectionStringBuilder b = new SQLiteConnectionStringBuilder();
                b.Version = 3;
                b.PageSize = 4096;
                b.UseUTF16Encoding = true;
                b.DataSource = DBLocation;
                return b.ConnectionString;
            }
        }

        /// <summary>
        /// local database logical file size in MB
        /// </summary>
        public double DBSize
        {
            get
            {
                if (isinmemory)
                    return 0;
                else
                {
                    try
                    {
                        FileInfo fi = new FileInfo(dblocation);
                        return fi.Length / (long)1024.0 / (long)1024.0;
                    }
                    catch
                    {
                        return -1;
                    }
                }
            }
        }

        /// <summary>
        /// Holds the database status
        /// Is set to UNDEFINED by default
        /// // Can be used by external procedure to manage database update process 
        /// </summary>
        public DATABASESTATUS Status { get; set; }

        public SQLiteConnection DBConnection = null;
        public SQLiteCommand DBCommand = null;

        public SQLiteDatabase() : this(null) { }
        public SQLiteDatabase(string filename) 
        {
            if (String.IsNullOrEmpty(filename))
            {
                // create a default database file name
                filename = "db.db3";
            }
            // handle special filenames
            if (filename == ":memory:")
            {
                isinmemory = true;
                dblocation = filename;
                return;
            }
            // check if filename contains path
            string directory = Path.GetDirectoryName(filename);
            if (String.IsNullOrEmpty(directory))
            {
                // create default database directory name
                // collect entry assembly info
                Assembly ass = Assembly.GetCallingAssembly();
                string name = ass.GetName().Name;
                string product = "";
                string company = "";
                object[] attribs;
                attribs = ass.GetCustomAttributes(typeof(AssemblyCompanyAttribute), true);
                if (attribs.Length > 0)
                {
                    company = ((AssemblyCompanyAttribute)attribs[0]).Company;
                }
                attribs = ass.GetCustomAttributes(typeof(AssemblyProductAttribute), true);
                if (attribs.Length > 0)
                {
                    product = ((AssemblyProductAttribute)attribs[0]).Product;
                }
                // create database path
                string dir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
                if (!String.IsNullOrEmpty(company))
                    dir = Path.Combine(dir, company);
                if (!String.IsNullOrEmpty(product))
                    dir = Path.Combine(dir, product);
                directory = Path.Combine(dir, "Database");
                filename = Path.Combine(directory, filename);
            }
            dblocation = filename;
            // create an empty database if not file found
            if (!File.Exists(dblocation))
            {
                // check for existing directory first
                string dir = Path.GetDirectoryName(dblocation);
                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }
            }
            // set status to UNDEFINED after initialization
            Status = DATABASESTATUS.UNDEFINED;
        }

        public void Open()
        {
            if (DBConnection != null)
                throw new InvalidOperationException("Cannot open database twice.");
            // check if database is initailly created
            DBConnection = new SQLiteConnection(DBConnectionString);
            DBCommand = new SQLiteCommand();
            DBCommand.Connection = DBConnection;
            Console.WriteLine("SQLiteDatabase.Open: connectionstring=" + DBConnection.ConnectionString);
            DBConnection.Open();
        }

        public void Close()
        {
            if (DBConnection == null)
                throw new InvalidOperationException("Database is closed already.");
            DBConnection.Close();
            DBConnection = null;
            DBCommand = null;
        }

        #region DB Info

        public DataTable GetTableStatus()
        {
            return Select("SELECT * FROM sqlite_master;");
        }

        public DataTable GetTableList()
        {
            DataTable dt = GetTableStatus();
            DataTable dt2 = new DataTable();
            dt2.Columns.Add("Tables");
            for (int i = 0; i < dt.Rows.Count; i++)
            {
                string t = dt.Rows[i]["name"] + "";
                if (t != "sqlite_sequence")
                    dt2.Rows.Add(t);
            }
            return dt2;
        }

        public bool TableExists (string tablename)
        {
            DataTable dt = Select("SELECT * FROM sqlite_master WHERE NAME = '" + tablename + "'");
            return ((dt != null) && (dt.Rows.Count > 0));
        }

        public int TableRowCount(string tablename)
        {
            try
            {
                DataTable dt = Select("SELECT Count(*) FROM " + tablename);
                return System.Convert.ToInt32(dt.Rows[0][0]);
            }
            catch
            {
            }
            return 0;
        }

        public DataTable GetColumnStatus(string tableName)
        {
            return Select(string.Format("PRAGMA table_info(`{0}`);", tableName));
        }

        public DataTable ShowDatabase()
        {
            return Select("PRAGMA database_list;");
        }

        public int GetUserVersion()
        {
            DataTable dt = Select("PRAGMA user_version;");
            if ((dt != null) && (dt.Rows.Count > 0))
                return System.Convert.ToInt32(dt.Rows[0][0]);
            return 0;
        }

        public void SetUserVerion(int version)
        {
            Execute("PRAGMA user_version = " + version.ToString());
        }

        public int GetSchemaVersion()
        {
            DataTable dt = Select("PRAGMA schema_version;");
            if ((dt != null) && (dt.Rows.Count > 0))
                return System.Convert.ToInt32(dt.Rows[0][0]);
            return 0;
        }

        public void SetSchemaVerion(int version)
        {
            Execute("PRAGMA schema_version = " + version.ToString());
        }

        public string GetTextEncoding()
        {
            DataTable dt = Select("PRAGMA encoding;");
            if ((dt != null) && (dt.Rows.Count > 0))
                return (dt.Rows[0][0].ToString());
            else return String.Empty;
        }

        public void SetTextEncoding(string encoding)
        {
            Execute("PRAGMA encoding = '" + encoding + "'");
        }

        public AUTOVACUUMMODE GetAutoVacuum()
        {
            DataTable dt = Select("PRAGMA auto_vacuum;");
            if ((dt != null) && (dt.Rows.Count > 0))
                return (AUTOVACUUMMODE)System.Convert.ToInt32(dt.Rows[0][0]);
            return 0;
        }

        public void SetAutoVacuum (AUTOVACUUMMODE mode)
        {
            // changes the auto vacuum pragma
            // command requires a VACUUM of the database to change layout
            // CAUTION: this requires time and disk space!
            try
            {
                Execute("PRAGMA auto_vacuum = " + ((int)mode).ToString() + "; VACUUM");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        #endregion

        #region Query

        public void BeginTransaction()
        {
            lock (DBCommand)
            {
                DBCommand.CommandText = "BEGIN TRANSACTION;";
                DBCommand.Parameters.Clear();
                DBCommand.ExecuteNonQuery();
            }
        }

        public void Commit()
        {
            lock (DBCommand)
            {
                DBCommand.CommandText = "COMMIT;";
                DBCommand.Parameters.Clear();
                DBCommand.ExecuteNonQuery();
            }
        }

        public void Rollback()
        {
            lock (DBCommand)
            {
                DBCommand.CommandText = "ROLLBACK";
                DBCommand.Parameters.Clear();
                DBCommand.ExecuteNonQuery();
            }
        }

        // simple sql select command returns a DataTable
        public DataTable Select (string sql)
        {
            DataTable dt = new DataTable();
            lock (DBCommand)
            {
                DBCommand.CommandText = sql;
                DBCommand.Parameters.Clear();
                SQLiteDataAdapter da = new SQLiteDataAdapter(DBCommand);
                da.Fill(dt);
            }
            // Linux/Mono hack --> cut column names to its right length
            for (int i = 0; i < dt.Columns.Count; i++)
            {
                int j = 0;
                while ((j < dt.Columns[i].ColumnName.Length) && (Char.IsLetter(dt.Columns[i].ColumnName[j]) || Char.IsDigit(dt.Columns[i].ColumnName[j])))
                    j++;
                dt.Columns[i].ColumnName = dt.Columns[i].ColumnName.Substring(0, j);
                Console.Write("'" + dt.Columns[i].ColumnName + "[" + dt.Columns[i].ColumnName.Length + "]', ");
            }
            Console.WriteLine();
            return dt;
        }

        public DataTable Select (SQLiteCommand cmd)
        {
            DataTable dt = new DataTable();
            lock (cmd)
            {
                SQLiteDataAdapter da = new SQLiteDataAdapter(cmd);
                da.Fill(dt);
            }
            return dt;
        }

        public void Execute(SQLiteCommand cmd)
        {
            cmd.ExecuteNonQuery();
        }

        public void Execute(string sql)
        {
            Execute(sql, new List<SQLiteParameter>());
        }

        public void Execute(string sql, Dictionary<string, object> dicParameters = null)
        {
            List<SQLiteParameter> lst = GetParametersList(dicParameters);
            Execute(sql, lst);
        }

        public void Execute(string sql, IEnumerable<SQLiteParameter> parameters = null)
        {
            lock (DBCommand)
            {
                DBCommand.CommandText = sql;
                if (parameters != null)
                {
                    DBCommand.Parameters.Clear();
                    foreach (var param in parameters)
                    {
                        DBCommand.Parameters.Add(param);
                    }
                }
                DBCommand.ExecuteNonQuery();
            }
        }

        public int ExecuteNonQuery (SQLiteCommand cmd)
        {
            return cmd.ExecuteNonQuery();
        }

        public object ExecuteScalar(string sql)
        {
            lock (DBCommand)
            {
                DBCommand.CommandText = sql;
                DBCommand.Parameters.Clear();
                return DBCommand.ExecuteScalar();
            }
        }

        public object ExecuteScalar (SQLiteCommand cmd)
        {
            List<SQLiteParameter> lst = new List<SQLiteParameter>();
            foreach (SQLiteParameter par in cmd.Parameters)
                lst.Add(par);
            return ExecuteScalar(cmd.CommandText, lst);
        }

        public object ExecuteScalar(string sql, Dictionary<string, object> dicParameters = null)
        {
            List<SQLiteParameter> lst = GetParametersList(dicParameters);
            return ExecuteScalar(sql, lst);
        }

        public object ExecuteScalar(string sql, IEnumerable<SQLiteParameter> parameters = null)
        {
            lock (DBCommand)
            { 
                DBCommand.CommandText = sql;
                if (parameters != null)
                {
                    DBCommand.Parameters.Clear();
                    foreach (var parameter in parameters)
                    {
                        DBCommand.Parameters.Add(parameter);
                    }
                }
                return DBCommand.ExecuteScalar();
            }
        }

        public dataType ExecuteScalar<dataType>(string sql, Dictionary<string, object> dicParameters = null)
        {
            List<SQLiteParameter> lst = null;
            if (dicParameters != null)
            {
                lst = new List<SQLiteParameter>();
                foreach (KeyValuePair<string, object> kv in dicParameters)
                {
                    lst.Add(new SQLiteParameter(kv.Key, kv.Value));
                }
            }
            return ExecuteScalar<dataType>(sql, lst);
        }

        public dataType ExecuteScalar<dataType>(string sql, IEnumerable<SQLiteParameter> parameters = null)
        {
            lock (DBCommand)
            {
                DBCommand.CommandText = sql;
                if (parameters != null)
                {
                    DBCommand.Parameters.Clear();
                    foreach (var parameter in parameters)
                    {
                        DBCommand.Parameters.Add(parameter);
                    }
                }
                return (dataType)Convert.ChangeType(DBCommand.ExecuteScalar(), typeof(dataType));
            }
        }

        public dataType ExecuteScalar<dataType>(string sql)
        {
            lock (DBCommand)
            {
                DBCommand.CommandText = sql;
                DBCommand.Parameters.Clear();
                return (dataType)Convert.ChangeType(DBCommand.ExecuteScalar(), typeof(dataType));
            }
        }

        private List<SQLiteParameter> GetParametersList(Dictionary<string, object> dicParameters)
        {
            List<SQLiteParameter> lst = new List<SQLiteParameter>();
            if (dicParameters != null)
            {
                foreach (KeyValuePair<string, object> kv in dicParameters)
                {
                    lst.Add(new SQLiteParameter(kv.Key, kv.Value));
                }
            }
            return lst;
        }

        public string Escape(string data)
        {
            data = data.Replace("'", "''");
            data = data.Replace("\\", "\\\\");
            return data;
        }

        public void InsertDataRow(string tableName, DataRow row)
        {
            Dictionary<string, object> dict = new Dictionary<string, object>();
            foreach (DataColumn col in row.Table.Columns)
            {
                dict.Add(col.ColumnName, row[col]);
            }
            Insert(tableName, dict);
        }

        public void InsertOrReplaceDataRow(string tableName, DataRow row)
        {
            Dictionary<string, object> dict = new Dictionary<string, object>();
            for (int i = 0; i < row.ItemArray.Length; i++)
            {
                dict.Add(row.Table.Columns[i].ColumnName, row[i]);
            }
            InsertOrReplace(tableName, dict);
        }

        public void Insert(string tableName, Dictionary<string, object> dic)
        {
            StringBuilder sbCol = new System.Text.StringBuilder();
            StringBuilder sbVal = new System.Text.StringBuilder();

            foreach (KeyValuePair<string, object> kv in dic)
            {
                if (sbCol.Length == 0)
                {
                    sbCol.Append("INSERT INTO ");
                    sbCol.Append(tableName);
                    sbCol.Append("(");
                }
                else
                {
                    sbCol.Append(",");
                }

                sbCol.Append("`");
                sbCol.Append(kv.Key);
                sbCol.Append("`");

                if (sbVal.Length == 0)
                {
                    sbVal.Append(" VALUES(");
                }
                else
                {
                    sbVal.Append(", ");
                }

                sbVal.Append("@v");
                sbVal.Append(kv.Key);
            }

            sbCol.Append(") ");
            sbVal.Append(");");

            lock (DBCommand)
            {
                DBCommand.CommandText = sbCol.ToString() + sbVal.ToString();
                DBCommand.Parameters.Clear();
                foreach (KeyValuePair<string, object> kv in dic)
                {
                    SQLiteParameter par = new SQLiteParameter("@v" + kv.Key, kv.Value);
                    DBCommand.Parameters.Add(par);
                }
                DBCommand.ExecuteNonQuery();
            }
        }

        public void InsertOrReplace(string tableName, Dictionary<string, object> dic)
        {
            StringBuilder sbCol = new System.Text.StringBuilder();
            StringBuilder sbVal = new System.Text.StringBuilder();

            foreach (KeyValuePair<string, object> kv in dic)
            {
                if (sbCol.Length == 0)
                {
                    sbCol.Append("INSERT OR REPLACE INTO ");
                    sbCol.Append(tableName);
                    sbCol.Append("(");
                }
                else
                {
                    sbCol.Append(",");
                }

                sbCol.Append("`");
                sbCol.Append(kv.Key);
                sbCol.Append("`");

                if (sbVal.Length == 0)
                {
                    sbVal.Append(" VALUES(");
                }
                else
                {
                    sbVal.Append(", ");
                }

                sbVal.Append("@v");
                sbVal.Append(kv.Key);
            }

            sbCol.Append(") ");
            sbVal.Append(");");
            lock (DBCommand)
            {
                DBCommand.CommandText = sbCol.ToString() + sbVal.ToString();
                DBCommand.Parameters.Clear();
                foreach (KeyValuePair<string, object> kv in dic)
                {
                    SQLiteParameter par = new SQLiteParameter("@v" + kv.Key, kv.Value);
                    DBCommand.Parameters.Add(par);
                }

                DBCommand.ExecuteNonQuery();
            }
        }

        public void Update(string tableName, Dictionary<string, object> dicData, string colCond, object varCond)
        {
            Dictionary<string, object> dic = new Dictionary<string, object>();
            dic[colCond] = varCond;
            Update(tableName, dicData, dic);
        }

        public void Update(string tableName, Dictionary<string, object> dicData, Dictionary<string, object> dicCond)
        {
            if (dicData.Count == 0)
                throw new Exception("dicData is empty.");

            StringBuilder sbData = new System.Text.StringBuilder();

            Dictionary<string, object> _dicTypeSource = new Dictionary<string, object>();

            foreach (KeyValuePair<string, object> kv1 in dicData)
            {
                _dicTypeSource[kv1.Key] = null;
            }

            foreach (KeyValuePair<string, object> kv2 in dicCond)
            {
                if (!_dicTypeSource.ContainsKey(kv2.Key))
                    _dicTypeSource[kv2.Key] = null;
            }

            sbData.Append("UPDATE `");
            sbData.Append(tableName);
            sbData.Append("` SET ");

            bool firstRecord = true;

            foreach (KeyValuePair<string, object> kv in dicData)
            {
                if (firstRecord)
                    firstRecord = false;
                else
                    sbData.Append(",");

                sbData.Append("`");
                sbData.Append(kv.Key);
                sbData.Append("` = ");

                sbData.Append("@v");
                sbData.Append(kv.Key);
            }

            sbData.Append(" WHERE ");

            firstRecord = true;

            foreach (KeyValuePair<string, object> kv in dicCond)
            {
                if (firstRecord)
                    firstRecord = false;
                else
                {
                    sbData.Append(" AND ");
                }

                sbData.Append("`");
                sbData.Append(kv.Key);
                sbData.Append("` = ");

                sbData.Append("@c");
                sbData.Append(kv.Key);
            }

            sbData.Append(";");
            lock (DBCommand)
            {
                DBCommand.CommandText = sbData.ToString();
                DBCommand.Parameters.Clear();
                foreach (KeyValuePair<string, object> kv in dicData)
                {
                    SQLiteParameter par = new SQLiteParameter("@v" + kv.Key, kv.Value);
                    DBCommand.Parameters.Add(par);
                }

                foreach (KeyValuePair<string, object> kv in dicCond)
                {
                    SQLiteParameter par = new SQLiteParameter("@v" + kv.Key, kv.Value);
                    DBCommand.Parameters.Add(par);
                }
                DBCommand.ExecuteNonQuery();
            }
        }

        public long LastInsertRowId()
        {
            return ExecuteScalar<long>("SELECT last_insert_rowid();");
        }

        #endregion

        #region Utilities

        public void CreateTable(DataTable dt)
        {
            if (String.IsNullOrEmpty(dt.TableName))
                throw new InvalidOperationException("Table name cannot be empty.");
            StringBuilder sb = new System.Text.StringBuilder();
            sb.Append("CREATE TABLE IF NOT EXISTS `");
            sb.Append(dt.TableName);
            sb.AppendLine("`(");

            bool firstRecord = true;

            foreach (DataColumn col in dt.Columns)
            {
                if (col.ColumnName.Trim().Length == 0)
                {
                    throw new Exception("Column name cannot be blank.");
                }

                if (firstRecord)
                    firstRecord = false;
                else
                    sb.AppendLine(",");

                sb.Append(col.ColumnName);
                sb.Append(" ");

                if (col.AutoIncrement)
                {

                    sb.Append("INTEGER PRIMARY KEY AUTOINCREMENT");
                    continue;
                }
                if ((col.DataType.IsPrimitive || col.DataType.IsValueType) || (col.DataType == typeof(string)))
                {
                    // append data type --> replace STRING with TEXT and DOUBLE with REAL
                    sb.Append(col.DataType.Name.ToUpper().Replace("STRING", "TEXT").Replace("DOUBLE", "REAL"));
                }
                else
                {
                    // complex type --> append as BLOB
                    sb.Append("BLOB");
                }
                if (col.Unique)
                    sb.Append(" UNIQUE");
                if (!col.AllowDBNull)
                    sb.Append(" NOT NULL");
                if (col.DefaultValue.GetType() != typeof(DBNull))
                {
                    sb.Append(" DEFAULT ");

                    if (col.DataType == typeof(string) || col.DataType == typeof(DateTime))
                    {
                        sb.Append("'");
                        sb.Append(col.DefaultValue.ToString());
                        sb.Append("'");
                    }
                    else
                    {
                        sb.Append(col.DefaultValue.ToString());
                    }
                }
            }

            //append primary keys if any
            if ((dt.PrimaryKey != null) && (dt.PrimaryKey.Length > 0))
            {
                sb.Append(", PRIMARY KEY (");
                firstRecord = true; 
                foreach (DataColumn col in dt.PrimaryKey)
                {
                    if (col.ColumnName.Trim().Length == 0)
                    {
                        throw new Exception("Column name cannot be blank.");
                    }

                    if (firstRecord)
                        firstRecord = false;
                    else
                        sb.AppendLine(",");

                    sb.Append(col.ColumnName);
                    sb.Append(" ");
                }
                sb.Append(")");
            }
            sb.AppendLine(");");
            lock (DBCommand)
            {
                DBCommand.CommandText = sb.ToString();
                DBCommand.Parameters.Clear();
                DBCommand.ExecuteNonQuery();
            }
        }

        public void ClearTable(string tablename)
        {
            BeginTransaction();
            Execute("DELETE FROM " + tablename);
            Commit();
        }

        public void InsertOrReplaceTable(DataTable dt)
        {
            // inserts all records from a data table or updates if exists
            // does not remove data rows if not in source table
            // will work with a unique key only
            if (String.IsNullOrEmpty(dt.TableName))
                throw new InvalidOperationException("Table name cannot be empty.");
            // check for primary key in database table
            DataTable info = GetColumnStatus(dt.TableName);
            bool hasprimarykey = false;
            foreach (DataRow row in info.Rows)
            {
                if (System.Convert.ToInt32(row["pk"]) > 0)
                {
                    hasprimarykey = true;
                    break;
                }
            }
            if (!hasprimarykey)
            if ((dt.PrimaryKey == null) || (dt.PrimaryKey.Length == 0))
                throw new InvalidOperationException("Data table must have a primary key.");
            BeginTransaction();
            foreach (DataRow row in dt.Rows)
                InsertOrReplaceDataRow(dt.TableName, row);
            Commit();
        }

        public void RenameTable(string tableFrom, string tableTo)
        {
            lock (DBCommand)
            {
                DBCommand.CommandText = string.Format("ALTER TABLE `{0}` RENAME TO `{1}`;", tableFrom, tableTo);
                DBCommand.Parameters.Clear();
                DBCommand.ExecuteNonQuery();
            }
        }


        public void CopyAllData(string tableFrom, string tableTo)
        {
            // get at least one row from tables
            DataTable src = Select(string.Format("SELECT * FROM `{0}` LIMIT 1;", tableFrom));
            // empty source table?? 
            if (src.Rows.Count == 0)
                return;
            DataTable dest;
            // empty dest table??
            if (TableRowCount(tableTo) == 0)
            {
                // add just one dummy row 
                Execute("INSERT INTO " + tableTo + " DEFAULT VALUES");
                // get the structure in a DataTable
                dest = Select(string.Format("SELECT * FROM `{0}` LIMIT 1;", tableTo));
                // remove the dummy row
                Execute("DELETE * FROM " + tableTo);
            }
            else
            {
                dest = Select(string.Format("SELECT * FROM `{0}` LIMIT 1;", tableTo));
            }
            Dictionary<string, bool> dic = new Dictionary<string, bool>();

            foreach (DataColumn dc in src.Columns)
            {
                if (dest.Columns.Contains(dc.ColumnName))
                {
                    if (!dic.ContainsKey(dc.ColumnName))
                    {
                        dic[dc.ColumnName] = true;
                    }
                }
            }

            foreach (DataColumn dc in dest.Columns)
            {
                if (src.Columns.Contains(dc.ColumnName))
                {
                    if (!dic.ContainsKey(dc.ColumnName))
                    {
                        dic[dc.ColumnName] = true;
                    }
                }
            }

            StringBuilder sb = new System.Text.StringBuilder();

            foreach (KeyValuePair<string, bool> kv in dic)
            {
                if (sb.Length > 0)
                    sb.Append(",");

                sb.Append("`");
                sb.Append(kv.Key);
                sb.Append("`");
            }

            StringBuilder sb2 = new System.Text.StringBuilder();
            sb2.Append("INSERT INTO `");
            sb2.Append(tableTo);
            sb2.Append("`(");
            sb2.Append(sb.ToString());
            sb2.Append(") SELECT ");
            sb2.Append(sb.ToString());
            sb2.Append(" FROM `");
            sb2.Append(tableFrom);
            sb2.Append("`;");
            lock (DBCommand)
            { 
                DBCommand.CommandText = sb2.ToString();
                DBCommand.Parameters.Clear();
                DBCommand.ExecuteNonQuery();
            }
        }

        public void DropTable(string table)
        {
            lock (DBCommand)
            {
                DBCommand.CommandText = string.Format("DROP TABLE IF EXISTS `{0}`", table);
                DBCommand.Parameters.Clear();
                DBCommand.ExecuteNonQuery();
            }
        }

        public void UpdateTableStructure(string targetTable, DataTable newStructure)
        {
            newStructure.TableName = targetTable + "_temp";

            CreateTable(newStructure);

            CopyAllData(targetTable, newStructure.TableName);

            DropTable(targetTable);

            RenameTable(newStructure.TableName, targetTable);
        }

        public void AttachDatabase(string database, string alias)
        {
            Execute(string.Format("ATTACH '{0}' AS {1};", database, alias));
        }

        public void DetachDatabase(string alias)
        {
            Execute(string.Format("DETACH {0};", alias));
        }

        public void BackupDatabase(string filename)
        {
            //backups a database to file
            // destination database must be open
            if (DBConnection == null)
                throw new InvalidOperationException("Database must be open for backup.");
            try
            {
                // return on empty filename
                if (String.IsNullOrEmpty(filename))
                    return;
                // check for existing directory first --> create on if not
                string dir = Path.GetDirectoryName(filename);
                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }
                // simply delete destination db if already exists
                if (File.Exists(filename))
                    File.Delete(filename);
                // get sql for creating structures
                string sql = "select sql from sqlite_master where name not like 'sqlite_%'";
                DataTable structure = this.Select(sql);
                // get user tables
                sql = "select name from sqlite_master where type = 'table' and name not like 'sqlite_%'";
                DataTable usertables = this.Select(sql);
                // crate identically structures in backup db
                SQLiteDatabase backupdb = new SQLiteDatabase(filename);
                backupdb.Open();
                foreach (DataRow row in structure.Rows)
                {
                    backupdb.DBCommand.CommandText = row[0].ToString();
                    backupdb.DBCommand.ExecuteNonQuery();
                }
                // close the backup db
                backupdb.Close();
                // attach backup db to source db 
                this.AttachDatabase(filename, "dest");
                this.BeginTransaction();
                // copy all user tables
                foreach (DataRow row in usertables.Rows)
                {
                    this.DBCommand.CommandText = "insert into dest." + row[0].ToString() + " select * from main." + row[0].ToString();
                    this.DBCommand.ExecuteNonQuery();
                }
                this.Commit();
                this.DetachDatabase("dest");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        public static SQLiteDatabase CreateInMemoryDB(string filename)
        {
            // returns a copy of a database in memory
            // create an empty memory db first
            SQLiteDatabase memdb = new SQLiteDatabase(":memory:");
            try
            {
                // open an existing on disk db
                SQLiteDatabase ondiskdb = new SQLiteDatabase(filename);
                ondiskdb.Open();
                // get user  & schema version
                int user_version = ondiskdb.GetUserVersion();
                int schema_version = ondiskdb.GetSchemaVersion();
                // get text encoding
                string text_encoding = ondiskdb.GetTextEncoding();
                // get sql for creating structures
                string sql = "select sql from sqlite_master where name not like 'sqlite_%'";
                DataTable structure = ondiskdb.Select(sql);
                // get user tables
                sql = "select name from sqlite_master where type = 'table' and name not like 'sqlite_%'";
                DataTable usertables = ondiskdb.Select(sql);
                // crate identically structures in memmory db
                memdb.Open();
                foreach (DataRow row in structure.Rows)
                {
                    memdb.DBCommand.CommandText = row[0].ToString();
                    memdb.DBCommand.ExecuteNonQuery();
                }
                // set user & schema version
                memdb.SetUserVerion(user_version);
                memdb.SetSchemaVerion(schema_version);
                // set text encoding
                memdb.SetTextEncoding(text_encoding);
                user_version = memdb.GetUserVersion();
                text_encoding = memdb.GetTextEncoding();
                // close on disk db
                ondiskdb.Close();
                // attach on disk db to memory db
                memdb.AttachDatabase(filename, "ondisk");
                memdb.BeginTransaction();
                foreach (DataRow row in usertables.Rows)
                {
                    memdb.DBCommand.CommandText = "insert into main." + row[0].ToString() + " select * from ondisk." + row[0].ToString();
                    memdb.DBCommand.ExecuteNonQuery();
                }
                memdb.Commit();
                memdb.DetachDatabase("ondisk");
                memdb.DiskFileName = filename;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return memdb;
        }

        #endregion

    }
}
