using System;
using System.Diagnostics;
using System.Threading;

/* Raima.Rdm is the system module that contains the ADO.NET
 * data provider for RDM */
using Raima.Rdm;

/* EXAMPLE - Perf10 for C# using ADO.NET */
/* This simple performance example creates 50,000 rows in a simple table
 * in an on-disk database.  It then spawns three threads.  The first one
 * updates rows in the table inside transactions with 1000 updates per
 * transaction.  The second thread attempts to read all the records to 
 * sum their contents.  The third thread just periodically reports the
 * results from the other two threads.  The reading thread uses read only
 * transactions.  This can be compared against the performance in Perf09
 * where the reading thread uses standard locking.
 * 
 * The following methods are illustrated:
 *     Raima.Rdm.RdmConnection:
 *         ChangeDatabase Method
 *         Close() Method
 *         CreateCommand() Method
 *         Open() Method
 *         RdmConnection() Constructor
 *     Raima.Rdm.RdmCommand:
 *         CommandText Property
 *         ExecuteNonQuery() Method
 *         ExecuteScalar() Method
 * 
 * They are fully document in http://docs.raima.com/rdm/.
 * 
 * For simplicity this example does not catch any thrown execptions
 * but good programming practices would dictate that this occur in
 * a full application.
 */

namespace Perf10_CS_ADO
{
    class Program
    {
        const int NumRows = 50000;

        static bool shutdown = false;
        static int rowsUpdated = 0;
        static int queriesExecuted = 0;

        private const int transactionSize = 1000;

        static void DropDatabase(
            RdmConnection conn,
            string name)
        {
            RdmCommand cmd = conn.CreateCommand();

            /* Attempt to drop this database.  If the database does not
             * already exist this will fail and we simply ignore the
             * failure for this sample. */
            try
            {
                cmd.CommandText = "DROP DATABASE " + name;
                cmd.ExecuteNonQuery();

                cmd.CommandText = "COMMIT";
                cmd.ExecuteNonQuery();
            }
            catch (Exception)
            {
            }
        }

        static void CreateThisDatabase(
            RdmConnection conn,
            string name,
            string[] cmds)
        {
            RdmCommand cmd = conn.CreateCommand();

            /* Create the specified database.  Make sure the database is
             * closed when we are done so other databases can be created
             * after this if necessary. */
            cmd.CommandText = "CREATE DATABASE " + name;
            cmd.ExecuteNonQuery();

            foreach (string c in cmds)
            {
                cmd.CommandText = c;
                cmd.ExecuteNonQuery();
            }

            cmd.CommandText = "COMMIT";
            cmd.ExecuteNonQuery();

            cmd.CommandText = "CLOSE " + name;
            cmd.ExecuteNonQuery();
        }

        static void CreateDatabases(
            RdmConnection conn)
        {
            string[] cmds =
            {
                "CREATE TABLE Simple (id INTEGER PRIMARY KEY, int_col INTEGER)"
            };

            /* Drop any existing copy of the database */
            DropDatabase(conn, "Perf10");

            /* Attempt to create the database */
            CreateThisDatabase(conn, "Perf10", cmds);
        }

        /* Add 50,000 rows to the table */
        static void AddRows(RdmConnection conn)
        {
            RdmCommand cmd = conn.CreateCommand();
            cmd.CommandText = "INSERT INTO Simple VALUES :id, 0";
            RdmParameter parm = cmd.Parameters.Add("id", RdmType.Int32);

            RdmTransaction trans = conn.BeginTransaction();

            for (int ii = 0; ii < NumRows; ii++)
            {
                parm.Value = ii;
                cmd.ExecuteNonQuery();
            }

            trans.Commit();
        }

        /* Initialization - Open the connection and create the database */
        static void Initialize(RdmConnection conn)
        {
            conn.Open();
            CreateDatabases(conn);
            conn.ChangeDatabase("Perf10");
            AddRows(conn);

            Console.WriteLine("Initializing with DIRECT-LINK configuration");
            Console.WriteLine("\tOne, simple row type");
            Console.WriteLine("\tOn disk");
            Console.WriteLine("\tOpened shared");
            Console.WriteLine();
            Console.WriteLine("\t\t\tHit <Enter> to exit the example");
        }

        /* Thread to add rows in chunks of 1000. */
        static void UpdateRowsThread()
        {
            RdmConnection conn = new RdmConnection("host=local;database=Perf10;openmode=s;tfstype=tfst;autocommit=off");
            conn.Open();

            RdmCommand cmd = conn.CreateCommand();
            cmd.CommandText = "UPDATE Simple SET int_col = int_col + 1 WHERE id >= :minid AND id < :minid + 1000";
            RdmParameter minparm = cmd.Parameters.Add("minid", RdmType.Int32);

            Console.WriteLine("Updating rows in transactions of size {0}", transactionSize);
            Console.WriteLine();

            int minid = 0;

            while (!shutdown)
            {
                if (minid >= NumRows)
                    minid = 0;

                RdmTransaction trans = conn.BeginTransaction();

                minparm.Value = minid;
                cmd.ExecuteNonQuery();

                trans.Commit();

                rowsUpdated += transactionSize;
                minid += transactionSize;
            }

            Console.WriteLine("Ending updates");

            conn.Close();
        }

        /* Thread to query the database (read) */
        static void ReaderThread()
        {
            RdmConnection conn = new RdmConnection("host=local;database=Perf10;openmode=s;tfstype=tfst;autocommit=off;readonlytransactions=on");
            conn.Open();

            RdmCommand cmd = conn.CreateCommand();
            cmd.CommandText = "SELECT SUM(int_col) FROM Simple";

            Console.WriteLine("Reading the contents of the table");

            while (!shutdown)
            {
                cmd.ExecuteScalar();
                queriesExecuted++;
            }

            Console.WriteLine("Ending reading");

            conn.Close();
        }

        /* Thread to report results periodically */
        static void ReporterThread()
        {
            for (int secs = 1; ; secs++)
            {
                Thread.Sleep(1000);

                if (shutdown)
                    break;

                Console.WriteLine("Updated {0} rows ({1}/sec) and executed {2} queries ({3:F2}/sec) in {4} secs",
                        rowsUpdated, (rowsUpdated / secs), queriesExecuted, ((double) queriesExecuted / secs), secs);
            }
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Perf10 Example");
            Console.WriteLine();

            /* Create the connection with a simple connection string */
            RdmConnection conn = new RdmConnection("host=local;openmode=s;tfstype=tfst;autocommit=off");

            /* Open the connection, delete any existing database, and create
             * the database anew */
            Initialize(conn);

            Console.WriteLine();

            /* Create three new threads - one to update records in the table
             * as fast as it can and another to read the contents of the table
             * and the third to periodically report the results */
            Thread updater = new Thread(new ThreadStart(UpdateRowsThread));
            Thread reader = new Thread(new ThreadStart(ReaderThread));
            Thread reporter = new Thread(new ThreadStart(ReporterThread));

            /* Start all threads */
            updater.Start();
            reader.Start();
            reporter.Start();

            /* Wait for user to hit <Enter> */
            Console.ReadLine();

            /* Indicate to the threads that they should shut down */
            shutdown = true;

            /* Wait for each thread to finish */
            updater.Join();
            reader.Join();
            reporter.Join();

            Console.WriteLine();

            conn.Close();

            Console.WriteLine();
            Console.WriteLine("Hit <Enter> to continue");
            Console.ReadLine();
        }
    }
}