// rddbcheck.cpp
//
// A Database Check/Repair Tool for Rivendell.
//
//   (C) Copyright 2002-2006 Fred Gleason <fredg@paravelsystems.com>
//
//      $Id: rddbcheck.cpp,v 1.11 2008/06/24 22:17:48 fredg Exp $
//
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License version 2 as
//   published by the Free Software Foundation.
//
//   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 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., 675 Mass Ave, Cambridge, MA 02139, USA.
//

#include <limits.h>
#include <glob.h>
#include <signal.h>
#include <math.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

#include <qapplication.h>
#include <qdir.h>

#include <rddb.h>
#include <rd.h>
#include <rddbcheck.h>
#include <rdcart.h>
#include <rdlog.h>
#include <rdcreate_log.h>
#include <rdescape_string.h>

//
// MAINTAINER'S NOTE
// Be sure to use QSqlQuery here, *not* RDSqlQuery, otherwise the
// DB connection will be reset when we detect an error!
//

MainObject::MainObject(QObject *parent,const char *name)
  :QObject(parent,name)
{
  check_yes=false;
  check_no=false;

  //
  // Read Command Options
  //
  RDCmdSwitch *cmd=
    new RDCmdSwitch(qApp->argc(),qApp->argv(),"rddbcheck",RDDBCHECK_USAGE);
  for(unsigned i=0;i<cmd->keys();i++) {
    if(cmd->key(i)=="--yes") {
      check_yes=true;
    }
    if(cmd->key(i)=="--no") {
      check_no=true;
    }
  }
  if(check_yes&&check_no) {
    fprintf(stderr,"rddbcheck: '--yes' and '--no' are mutually exclusive\n");
    exit(256);
  }

  //
  // Check for Root Perms
  //
  if(geteuid()!=0) {
    fprintf(stderr,"rddbcheck: must be user \"root\"\n");
    exit(256);
  }

  //
  // Read Configuration
  //
  rdconfig=new RDConfig();
  rdconfig->load();

  //
  // Open Database
  //
  QString err (tr("rddbcheck: "));
  QSqlDatabase *db=RDInitDb (&err);
  if(!db) {
    fprintf(stderr,err.ascii());
    delete cmd;
    exit(256);
  }

  //
  // Check for Orphaned Voice Tracks
  //
  printf("Checking voice tracks...\n");
  CheckOrphanedTracks();
  printf("done.\n\n");

  //
  // Check RDLogManager Consistency
  //
  printf("Checking RDLogManager events...\n");
  CheckEvents();
  printf("done.\n\n");
  printf("Checking RDLogManager clocks...\n");
  CheckClocks();
  printf("done.\n\n");

  //
  // Check for orphaned tables
  //
  printf("Checking for orphaned tables...\n");
  CheckOrphanedTables();
  printf("done.\n\n");

  exit(0);
}


void MainObject::CheckOrphanedTracks()
{
  QString logname;
  QString sql="select NUMBER,TITLE,OWNER from CART where OWNER!=\"\"";
  QSqlQuery *q=new QSqlQuery(sql);
  QSqlQuery *q1;

  while(q->next()) {
    logname=q->value(2).toString();
    logname.replace(" ","_");
    sql=QString().sprintf("select ID from %s where CART_NUMBER=%u",
			  (const char *)logname,q->value(0).toUInt());
    q1=new QSqlQuery(sql);
    if(!q1->first()) {
      printf("  Found orphaned track %u - \"%s\".  Delete? (y/N) ",
	     q->value(0).toUInt(),(const char *)q->value(1).toString());
      fflush(NULL);
      if(UserResponse()) {
	RDCart *cart=new RDCart(q->value(0).toUInt());
	cart->remove();
	delete cart;
	RDLog *log=new RDLog(q->value(2).toString());
	log->updateTracks();
	delete log;
      }
    }
    delete q1;
  }
  delete q;
}


void MainObject::CheckClocks()
{
  QString sql;
  QSqlQuery *q;
  QSqlQuery *q1;
  QSqlQuery *q2;
  QSqlQuery *q3;
  QString clockname;
  QString eventname;

  sql="select NAME from CLOCKS";
  q=new QSqlQuery(sql);
  while(q->next()) {
    clockname=q->value(0).toString();
    clockname.replace(" ","_");

    //
    // Check the CLK Table
    //
    sql=QString().sprintf ("select EVENT_NAME,ID from %s_CLK",
			   (const char *)clockname);
    q1=new QSqlQuery(sql);
    if(q1->isActive()) {
      //
      // Check the clock -> event linkage
      //
      while(q1->next()) {
	sql=QString().sprintf("select NAME from EVENTS where NAME=\"%s\"",
			      (const char *)q1->value(0).toString());
	q2=new QSqlQuery(sql);
	if(q2->first()) {
	  if(q1->value(0)!=q2->value(0)) {  // Make sure the cases match!
	    printf("  Clock %s's linkage to event %s is broken -- fix (y/N)? ",
		   (const char *)clockname,
		   (const char *)q2->value(0).toString());
	    fflush(NULL);
	    if(UserResponse()) {
	      sql=QString().sprintf("update %s_CLK set EVENT_NAME=\"%s\"\
                                     where ID=%d",
				    (const char *)clockname,
				    (const char *)q2->value(0).toString(),
				    q1->value(1).toInt());
	      q3=new QSqlQuery(sql);
	      delete q3;
	    }
	  }
	}
	delete q2;
      }
    }
    else {
      printf("  Clock %s is missing the CLK table -- fix (y/N)? ",
	     (const char *)q->value(0).toString());
      fflush(NULL);
      if(UserResponse()) {
	sql=RDCreateClockTableSql(clockname);
	q2=new QSqlQuery(sql);
	delete q2;
      }
    }
    delete q1;
  }
  delete q;

}


void MainObject::CheckEvents()
{
  QString sql;
  QSqlQuery *q;
  QSqlQuery *q1;
  QSqlQuery *q2;
  QString eventname;

  sql="select NAME from EVENTS";
  q=new QSqlQuery(sql);
  while(q->next()) {
    eventname=q->value(0).toString();
    eventname.replace(" ","_");

    //
    // Check the PRE Table
    //
    sql=QString().sprintf ("select ID from %s_PRE",(const char *)eventname);
    q1=new QSqlQuery(sql);
    if(!q1->isActive()) {
      printf("  Event %s is missing the PRE table -- fix (y/N)? ",
	     (const char *)q->value(0).toString());
      fflush(NULL);
      if(UserResponse()) {
	sql=RDCreateLogTableSql(eventname+"_PRE");
	q2=new QSqlQuery(sql);
	delete q2;
      }
    }
    delete q1;

    //
    // Check the POST Table
    //
    sql=QString().sprintf ("select ID from %s_POST",(const char *)eventname);
    q1=new QSqlQuery(sql);
    if(!q1->isActive()) {
      printf("  Event %s is missing the POST table -- fix (y/N)? ",
	     (const char *)q->value(0).toString());
      fflush(NULL);
      if(UserResponse()) {
	sql=RDCreateLogTableSql(eventname+"_POST");
	q2=new QSqlQuery(sql);
	delete q2;
      }
    }
    delete q1;
  }
  delete q;
}


void MainObject::CheckOrphanedTables()
{
  QSqlQuery *table_q;
  QSqlQuery *q;
  QString sql;

  //
  // Generate Table Query
  //
  sql="show tables";
  table_q=new QSqlQuery(sql);

  //
  // Look for orphaned clocks
  //
  sql="select NAME from CLOCKS";
  q=new QSqlQuery(sql);
  CleanTables("CLK",table_q,q);
  CleanTables("RULES",table_q,q);
  delete q;

  //
  // Look for orphaned events
  //
  sql="select NAME from EVENTS";
  q=new QSqlQuery(sql);
  CleanTables("PRE",table_q,q);
  CleanTables("POST",table_q,q);
  delete q;

  //
  // Look for orphaned logs
  //
  sql="select NAME from LOGS";
  q=new QSqlQuery(sql);
  CleanTables("LOG",table_q,q);
  CleanTables("STACK",table_q,q);
  delete q;

  //
  // Look for orphaned services
  //
  sql="select NAME from SERVICES";
  q=new QSqlQuery(sql);
  CleanTables("SRT",table_q,q);
  delete q;

  //
  // Look for orphaned feeds
  //
  sql="select KEY_NAME from FEEDS";
  q=new QSqlQuery(sql);
  CleanTables("FIELDS",table_q,q);
  CleanTables("FLG",table_q,q);
  delete q;

  //
  // Look for stale and obsolete tables
  //
  CleanTables("IMP",table_q);
  CleanTables("REC",table_q);


  delete table_q;
}


void MainObject::CleanTables(const QString &ext,QSqlQuery *table_q,
			     QSqlQuery *name_q)
{
  QString sql;
  QSqlQuery *q1;

  table_q->seek(-1);
  while(table_q->next()) {
    if(!IsTableLinked(name_q,ext,table_q->value(0).toString())) {
      printf("  Table %s is orphaned -- delete (y/N)? ",
	     (const char *)RDEscapeString(table_q->value(0).toString()));
      fflush(NULL);
      if(UserResponse()) {
	sql=QString().sprintf("drop table %s",
		  (const char *)RDEscapeString(table_q->value(0).toString()));
	q1=new QSqlQuery(sql);
	delete q1;
      }
    }
  }
}


void MainObject::CleanTables(const QString &ext,QSqlQuery *table_q)
{
  QString sql;
  QSqlQuery *q1;

  table_q->seek(-1);
  while(table_q->next()) {
    if(table_q->value(0).toString().right(ext.length())==ext) {
      printf("  Table %s is orphaned -- delete (y/N)? ",
	     (const char *)table_q->value(0).toString());
      fflush(NULL);
      if(UserResponse()) {
	sql=QString().sprintf("drop table %s",
		  (const char *)RDEscapeString(table_q->value(0).toString()));
	q1=new QSqlQuery(sql);
	delete q1;
      }
    }
  }
}


bool MainObject::IsTableLinked(QSqlQuery *name_q,const QString &ext,
			       const QString &table)
{
  QString tablename;

  if(table.right(ext.length())!=ext) {
    return true;
  }
  name_q->seek(-1);
  while(name_q->next()) {
    tablename=name_q->value(0).toString()+"_"+ext;
    tablename.replace(" ","_");
    if(tablename==table) {
      return true;
    }
  }
  return false;
}


bool MainObject::UserResponse()
{
  char c=0;

  if(check_yes) {
    printf("y\n");
    return true;
  }
  if(check_no) {
    printf("n\n");
    return false;
  }
  while((c!='y')&&(c!='Y')&&(c!='n')&&(c!='N')) {
    scanf("%c",&c);
    if((c=='y')||(c=='Y')) {
      return true;
    }
  }
  return false;
}


int main(int argc,char *argv[])
{
  QApplication a(argc,argv,false);
  new MainObject(NULL,"main");
  return a.exec();
}
