/*---------------------------------------------------------------------
The select library performs the SQL select command on a simple 
        file-based database.

Specification:
	Input:  the name of a relational table (name);
	        a schema file for the table (name.schema);
		a data file for the table (name.dat);
		a field name and value to restrict the selection;
	Output: a string of records restricted by the user input.
------------------------------------------------------------------------*/

#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstdio>
#include <cassert>
using namespace std;

#include "select.h"

/*---------------------------------------------------------------------
Some simple type conversion routines

Specification:
       int2string:    converts integers to their string representation
       double2string: converts doubles to their string representation
       string2int:    converts strings to their integer representation
       string2double: converts strings to their double representation
------------------------------------------------------------------------*/

string int2string(int x)
{
  char buff[20];
  sprintf(buff, "%d", x);
  return buff;
}

int string2int(string s)
{
  int x;
  const char* buff = s.c_str();
  sscanf(buff, "%d", &x);
  return x;
}

string double2string(double x)
{
  char buff[20];
  sprintf(buff, "%.2f", x);
  return buff;
}

double string2double(string s)
{
  double x;
  const char* buff = s.c_str();
  sscanf(buff, "%lf", &x);
  return x;
}


/*---------------------------------------------------------------------
openTableFiles opens the files associated with a table

Specification:
	Input:  the name of a relational table;
	Effect: The schema and data file parameters are opened if possible.
------------------------------------------------------------------------*/

void openTableFiles(string tableName, ifstream& schemaFile, ifstream& dataFile);

void openTableFiles(string tableName, ifstream& schemaFile, ifstream& dataFile)
{
  string
    schemaTableName = tableName + ".schema",   // the schema filename
    dataTableName = tableName + ".data";       // the data filename

  schemaFile.open(schemaTableName.data()); 
  dataFile.open(dataTableName.data()); 

  if (schemaFile.fail() || dataFile.fail())
    {
      cerr << "\nError: Unable to open this table's schema and data files.\n" 
	   << endl;
      exit(-1);
    }
}

/*---------------------------------------------------------------------
recordSelected determines if the current field and field value have been
        selected by the user.  It does the appropriate type conversions
	before checking.

Specification:
	Input:  the current field name and value;
	        the selected field name and value;
		the current field type;
	Return: whether or not the current field and value have been selected
------------------------------------------------------------------------*/

bool recordSelected(const string& selectedFieldName, 
		    const string& currentFieldName, 
		    const string& selectedFieldValue, 
		    const string& currentFieldValue,
		    const string& currentFieldType);

bool recordSelected(const string& selectedFieldName, 
		    const string& currentFieldName, 
		    const string& selectedFieldValue, 
		    const string& currentFieldValue,
		    const string& currentFieldType)
{
  if (selectedFieldName == currentFieldName)
    if (currentFieldType == "int")
      return string2int(selectedFieldValue) == string2int(currentFieldValue);
    else if (currentFieldType == "double")
      return string2double(selectedFieldValue) == string2double(currentFieldValue);
    else if (currentFieldType == "string")
      return selectedFieldValue == currentFieldValue;
  return false;
}


/*---------------------------------------------------------------------
select performs the relational select operator

Specification:
	Input:  the name of a relational table;
	        the name of a field to select on;
		the value that the field should have;
	Effect: The output of the select operation is printed on cout.
------------------------------------------------------------------------*/

void select(const string& projectedFieldName,
	    const string& tableName, 
	    const string& selectedFieldName, 
	    const string& selectedOperator,   // We'll assume = for now.
	    const string& selectedFieldValue)
{
  ifstream schemaFile, dataFile;   // the associated table files

  openTableFiles(tableName, schemaFile, dataFile);   // Open these files.

  string 
    currentFieldName,  // the field we are currently reading from the file
    currentFieldType,  // the type of the current field
    currentFieldValue, // the value of the current field
    outputLine;        // a place to keep the record before printing it
  bool selectLine;     // whether or not the records is selected

  cout << endl;

  for (;;)   // for each record in the data file
    {
      outputLine = "";    // We'll add to this one field at a time.
      selectLine = false; // Assume that the record is not selected.

      for (;;)  // for each field in the schema file
	{
	  // Get the next field specification from the schema file.
	  schemaFile >> currentFieldName >> currentFieldType;

	  if ( schemaFile.eof() ) break;
//	  if ( schemaFile.fail() ) break;

	  dataFile >> currentFieldValue;
	  
	  if ( dataFile.eof() )
//	  if ( dataFile.fail() )
	    {
	      // Close the table files and return.
	      schemaFile.close();
	      dataFile.close();
	      return;
	    }

	  // Check if the current record satisfies the selection criterion.
	  if (recordSelected(selectedFieldName, currentFieldName, 
			     selectedFieldValue, currentFieldValue,
			     currentFieldType))
	    selectLine = true;

          // Add the new field to the output string if it is projected (with * or by name).
	  if ((projectedFieldName == "*") ||
	      (projectedFieldName.find(currentFieldName,0)) < projectedFieldName.length())
	    outputLine += currentFieldValue + "\t ";
	}

      // Output the current record if it has been selected.
      if (selectLine)
	     cout << outputLine << endl;

      // Prepare to do a second pass through the schema file.
      schemaFile.clear();
      schemaFile.seekg(0, ios::beg);
    }
  
}

