/* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2.0,
   as published by the Free Software Foundation.

   This program is also distributed with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have included with MySQL.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License, version 2.0, for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */

#define LOG_COMPONENT_TAG "pfs_example_plugin_employee"

#include <mysql/plugin.h>
#include <mysql_version.h>

#include <mysql/components/my_service.h>
#include <mysql/components/services/log_builtins.h>
#include <mysqld_error.h>

#include "mysql/psi/mysql_mutex.h"

#include "plugin/pfs_table_plugin/pfs_example_employee_name.h"
#include "plugin/pfs_table_plugin/pfs_example_employee_salary.h"
#include "plugin/pfs_table_plugin/pfs_example_machine.h"
#include "plugin/pfs_table_plugin/pfs_example_machines_by_emp_by_mtype.h"

static SERVICE_TYPE(registry) *reg_srv = nullptr;
SERVICE_TYPE(log_builtins) *log_bi = nullptr;
SERVICE_TYPE(log_builtins_string) *log_bs = nullptr;

/**
  @page EXAMPLE_PLUGIN An example plugin

  Plugin Name     : pfs_example_plugin_employee \n
  Source location : plugin/pfs_table_plugin

  This file contains a definition of the pfs_example_plugin_employee.
*/

/* clang-format off */
/* Records to be inserted into pfs_example_employee_name table from plugin code */
Ename_Record ename_array[] =
{
  {{1, false}, "foo1", 4, "bar1", 4, true},
  {{2, false}, "foo2", 4, "bar2", 4, true},
  {{3, false}, "foo3", 4, "bar3", 4, true}
};

/* Records to be inserted into pfs_example_employee_salary table from plugin code */
Esalary_Record esalary_array[] =
{
  {{1, false}, {1000, false}, "2013-11-12", 10, "12:02:34", 8, true},
  {{2, false}, {2000, false}, "2016-02-29", 10, "12:12:30", 8, true},
  {{3, false}, {3000, false}, "2017-03-24", 10, "11:12:50", 8, true}
};

/* Records to be inserted into pfs_example_machine table from plugin code */
Machine_Record machine_array[] =
{
  {{1, false}, {DESKTOP, false}, "Lenovo", 6, {1, false}, true},
  {{2, false}, {LAPTOP, false}, "Dell", 4, {2, false}, true},
  {{3, false}, {MOBILE, false}, "Apple", 5, {1, false}, true},
  {{4, false}, {MOBILE, false}, "Samsung", 7, {1, false}, true},
  {{5, false}, {LAPTOP, false}, "Lenovo", 6, {2, false}, true},
  {{6, false}, {MOBILE, false}, "Nokia", 5, {2, false}, true},
  {{7, false}, {LAPTOP, false}, "Apple", 5, {1, false}, true},
  {{8, false}, {LAPTOP, false}, "HP", 2, {3, false}, true},
  {{9, false}, {DESKTOP, false}, "Apple", 5, {3, false}, true},
};
/* clang-format off */

/* Global handles */
SERVICE_TYPE(registry) *r = nullptr;
my_h_service h_ret_table_svc = nullptr;
SERVICE_TYPE(pfs_plugin_table) *table_svc = nullptr;

/* Collection of table shares to be added to performance schema */
PFS_engine_table_share_proxy* share_list[4]= {nullptr, nullptr, nullptr, nullptr};
unsigned int share_list_count= 4;

/* Mutex info definitions for the table mutexes */
static PSI_mutex_key key_mutex_name;
static PSI_mutex_key key_mutex_salary;
static PSI_mutex_key key_mutex_machine;

static PSI_mutex_info mutex_info[] = {
  {&key_mutex_name, "LOCK_ename_records_array",
   PSI_FLAG_SINGLETON, PSI_VOLATILITY_PERMANENT,
   "Mutex for the pfs_example_employee_name table."},

  {&key_mutex_salary, "LOCK_esalary_records_array",
   0, PSI_VOLATILITY_PERMANENT,
   "Mutex for the pfs_example_employee_salary table."},

  {&key_mutex_machine, "LOCK_machine_records_array",
   0, PSI_VOLATILITY_PERMANENT,
   "Mutex for the pfs_example_machine table."}
};

/**
* acquire_service_handles does following:
*   - Acquire the registry service for mysql_server.
*   - Acquire pfs_plugin_table service implementation.
*/
bool
acquire_service_handles(MYSQL_PLUGIN p MY_ATTRIBUTE((unused)))
{
  bool result = false;

  /* Acquire mysql_server's registry service */
  r = mysql_plugin_registry_acquire();
  if (!r)
  {
    LogPluginErr(ERROR_LEVEL, ER_LOG_PRINTF_MSG,
                 "mysql_plugin_registry_acquire() returns empty");
    result = true;
    goto error;
  }

  /* Acquire pfs_plugin_table service */
  if (r->acquire("pfs_plugin_table", &h_ret_table_svc))
  {
    LogPluginErr(ERROR_LEVEL, ER_LOG_PRINTF_MSG,
                 "can't find pfs_plugin_table service");
    result = true;
    goto error;
  }

  /* Type cast this handler to proper service handle */
  table_svc =
    reinterpret_cast<SERVICE_TYPE(pfs_plugin_table) *>(h_ret_table_svc);

error:
  return result;
}

/**
* release_service_handles does following:
*   - Release the handle to the pfs_plugin_table service.
*   - Release the handle to registry service.
*/
void
release_service_handles()
{
  if (r != nullptr)
  {
    if (h_ret_table_svc != nullptr)
    {
      /* Release pfs_plugin_table and pfs_plugin_table services */
      r->release(h_ret_table_svc);
      h_ret_table_svc = nullptr;
      table_svc = nullptr;
    }
    /* Release registry service */
    mysql_plugin_registry_release(r);
    r = nullptr;
  }
}

/* Prepare and insert rows in pfs_example_employee_name table */
int
ename_prepare_insert_row()
{
  int result = 0;
  Ename_Table_Handle handle;
  int array_size= sizeof(ename_array) / sizeof(ename_array[0]);

  for (int i = 0; i < array_size; i++)
  {
    strncpy(handle.current_row.f_name, ename_array[i].f_name,
        ename_array[i].f_name_length);
    handle.current_row.f_name_length = ename_array[i].f_name_length;
    strncpy(handle.current_row.l_name, ename_array[i].l_name,
        ename_array[i].l_name_length);
    handle.current_row.l_name_length = ename_array[i].l_name_length;
    handle.current_row.e_number = ename_array[i].e_number;
    handle.current_row.m_exist = ename_array[i].m_exist;

    /* Insert a row in the table to be added */
    result = ename_write_row_values((PSI_table_handle *)&handle);

    if (result)
      break;
  }

  return result;
}

/* Prepare and insert rows in pfs_example_employee_salary table */
int
esalary_prepare_insert_row()
{
  int result = 0;
  Esalary_Table_Handle handle;
  int array_size= sizeof(esalary_array) / sizeof(esalary_array[0]);

  for (int i = 0; i < array_size; i++)
  {
    strncpy(handle.current_row.e_dob, esalary_array[i].e_dob,
        esalary_array[i].e_dob_length);
    handle.current_row.e_dob_length= esalary_array[i].e_dob_length;

    strncpy(handle.current_row.e_tob, esalary_array[i].e_tob,
        esalary_array[i].e_tob_length);
    handle.current_row.e_tob_length= esalary_array[i].e_tob_length;

    handle.current_row.e_number = esalary_array[i].e_number;
    handle.current_row.e_salary = esalary_array[i].e_salary;

    handle.current_row.m_exist = esalary_array[i].m_exist;

    /* Insert a row in the table to be added */
    result = esalary_write_row_values((PSI_table_handle *)&handle);
    if (result)
      break;
  }

  return result;
}

/* Prepare and insert rows in pfs_example_machine table */
int
machine_prepare_insert_row()
{
  int result = 0;
  Machine_Table_Handle handle;
  int array_size= sizeof(machine_array) / sizeof(machine_array[0]);

  for (int i = 0; i < array_size; i++)
  {
    handle.current_row.machine_number = machine_array[i].machine_number;
    strncpy(handle.current_row.machine_made, machine_array[i].machine_made,
      machine_array[i].machine_made_length);
    handle.current_row.machine_made_length = machine_array[i].machine_made_length;
    handle.current_row.machine_type = machine_array[i].machine_type;
    handle.current_row.employee_number = machine_array[i].employee_number;

    handle.current_row.m_exist = machine_array[i].m_exist;

    /* Insert a row in the table to be added */
    result = machine_write_row_values((PSI_table_handle *)&handle);
    if (result)
      break;
  }

  return result;
}

/**
*  pfs_example_func does following :
*    - Instantiate PFS_engine_table_share_proxy(s).
*    - Prepare and insert rows in tables from here.
*    - Acquire pfs_plugin_table service handle.
*    - Call add_table method of pfs_plugin_table service.

*  Error messages are written to the server's error log.
*  In case of success writes a single information message to the server's log.

*  @retval false  success
*  @retval true   failure
*/
static bool
pfs_example_func(MYSQL_PLUGIN p)
{
  bool result = false;

  /* Instantiate and initialize PFS_engine_table_share_proxy */
  init_ename_share(&ename_st_share);
  init_esalary_share(&esalary_st_share);
  init_machine_share(&machine_st_share);
  init_m_by_emp_by_mtype_share(&m_by_emp_by_mtype_st_share);

  /* From here, prepare rows for tables and insert */
  if (ename_prepare_insert_row() ||
      esalary_prepare_insert_row() ||
      machine_prepare_insert_row())
  {
    LogPluginErr(ERROR_LEVEL, ER_LOG_PRINTF_MSG,
                 "Error returned during prepare and insert row.");
    result = true;
    goto error;
  }

  /* Get pfs_plugin_table service handle. */
  result = acquire_service_handles(p);
  if (result)
    goto error;

  /* Prepare the shares list to be passed to the service call */
  share_list[0]= &ename_st_share;
  share_list[1]= &esalary_st_share;
  share_list[2]= &machine_st_share;
  share_list[3]= &m_by_emp_by_mtype_st_share;

  /**
   * Call add_tables function of pfs_plugin_table service to
   * add plugin tables in performance schema.
   */
  if (table_svc->add_tables(&share_list[0], share_list_count))
  {
    LogPluginErr(ERROR_LEVEL, ER_LOG_PRINTF_MSG,
                 "Error returned from add_tables()");
    result = true;
    goto error;
  }

  return result;

error:
  /* Release service handles. */
  release_service_handles();
  return result;
}

/**
*  Initialize the pfs_example_plugin_employee at server start or plugin
*  installation.
*
*   - Call pfs_example_func.
*/

static int
pfs_example_plugin_employee_init(void *p)
{
  DBUG_TRACE;
  int result = 0;

  if (init_logging_service_for_plugin(&reg_srv, &log_bi, &log_bs))
    return 1;

  /* Register the mutex classes */
  mysql_mutex_register("pfs_example2", mutex_info, 3);

  /* Initialize mutexes to be used for table records */
  mysql_mutex_init(key_mutex_name, &LOCK_ename_records_array,
                   MY_MUTEX_INIT_FAST);
  mysql_mutex_init(key_mutex_salary, &LOCK_esalary_records_array,
                   MY_MUTEX_INIT_FAST);
  mysql_mutex_init(key_mutex_machine, &LOCK_machine_records_array,
                   MY_MUTEX_INIT_FAST);

  /* In case the plugin has been unloaded, and reloaded */
  ename_delete_all_rows();
  esalary_delete_all_rows();
  machine_delete_all_rows();

  result = pfs_example_func(reinterpret_cast<MYSQL_PLUGIN>(p)) ? 1 : 0;

  if (result)
  {
    /* Destroy mutexes for table records */
    mysql_mutex_destroy(&LOCK_ename_records_array);
    mysql_mutex_destroy(&LOCK_esalary_records_array);
    mysql_mutex_destroy(&LOCK_machine_records_array);
  }

  return result;
}

static int
pfs_example_plugin_employee_check(void *)
{
  DBUG_TRACE;

  if (table_svc != nullptr)
  {
    if (table_svc->delete_tables(&share_list[0], share_list_count))
    {
      /* Block execution of UNINSTALL PLUGIN. */
      return 1;
    }
  }

  return 0;
}

/**
*  Terminate the pfs_example_plugin_employee at server shutdown or plugin
*  deinstallation.
*
*   - Delete/Drop plugin tables from Performance Schema.
*   - Release pfs_plugin_table service handle.
*/
static int
pfs_example_plugin_employee_deinit(void *p  MY_ATTRIBUTE((unused)))
{
  DBUG_TRACE;

  /**
   * Call delete_tables function of pfs_plugin_table service to
   * delete plugin tables from performance schema
   */
  if (table_svc != nullptr)
  {
    if (table_svc->delete_tables(&share_list[0], share_list_count))
    {
      LogPluginErr(ERROR_LEVEL, ER_LOG_PRINTF_MSG,
                   "Error returned from delete_tables()");
      deinit_logging_service_for_plugin(&reg_srv, &log_bi, &log_bs);
      return 1;
    }
  }
  else /* Service not found or released */
  {
    deinit_logging_service_for_plugin(&reg_srv, &log_bi, &log_bs);
    return 1;
  }

  /* Destroy mutexes for table records */
  mysql_mutex_destroy(&LOCK_ename_records_array);
  mysql_mutex_destroy(&LOCK_esalary_records_array);
  mysql_mutex_destroy(&LOCK_machine_records_array);

  deinit_logging_service_for_plugin(&reg_srv, &log_bi, &log_bs);

  /* Release service handles. */
  release_service_handles();

  return 0;
}

static struct st_mysql_daemon pfs_example_plugin_employee = {
  MYSQL_DAEMON_INTERFACE_VERSION};

/**
  pfs_example_plugin_employee plugin descriptor
*/

/* clang-format off */
mysql_declare_plugin(pfs_example_plugin_employee)
{
  MYSQL_DAEMON_PLUGIN,
  &pfs_example_plugin_employee,
  "pfs_example_plugin_employee",
  PLUGIN_AUTHOR_ORACLE,
  "pfs_example_plugin_employee",
  PLUGIN_LICENSE_GPL,
  pfs_example_plugin_employee_init,   /* Plugin Init      */
  pfs_example_plugin_employee_check,  /* Plugin Check uninstall */
  pfs_example_plugin_employee_deinit, /* Plugin Deinit    */
  0x0100 /* 1.0 */, nullptr,             /* status variables */
  nullptr,                               /* system variables */
  nullptr,                               /* config options   */
  PLUGIN_OPT_ALLOW_EARLY,             /* flags            */
}
mysql_declare_plugin_end;
/* clang-format on */
