#include <iostream.h>
#include <fstream.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include "datacheck.h"
#include "erf_util.h"

#define  NS    10

/*
static void MandatoryCategoryChecking(CifFileObj *in, DICFileObj *ref, ofstream &log);
*/
static int  ItemsChecking(CifFileObj *in, DICFileObj *ref, ofstream &log, const int is_nmr,
                int& wrong_date, CifString &date_error);
static void GetKeywordFromItemName(char *keyword, const char *itemName);
/*
static void GetCategoryNameFromItemName(char *categoryName, const char *itemName) ;
*/
static void ConvertEscapedString(CifString& inString, CifString& outString);
static int is_wrong_date(const char *date);

int DictionaryDataChecking(CifFileObj *in, DICFileObj *ref, FILE *fp, const int is_nmr,
            int& wrong_date, CifString &date_error)
{
       int ret = 0;
       char c;
       FILE *f = NULL;
       ofstream log;

       log.open("Diagnostics.log", ios::out/*|ios::app*/);
/*
       MandatoryCategoryChecking(in, ref, log);
*/
       ret = ItemsChecking(in, ref, log, is_nmr, wrong_date, date_error);
       log.close();

       f = fopen("Diagnostics.log", "r");
       while (!feof(f)) {
            c = fgetc(f);
            if (feof(f)) break;
            fputc(c, fp);
            // ret = 1;
       }
       fclose (f);

       return ret;
}
/*
static void MandatoryCategoryChecking(CifFileObj *in, DICFileObj *ref, ofstream &log)
{
       int i, j, numBlocks, mandatoryIndex, errCode, refnumBlocks;
       char **BlockNames = NULL, **refBlockNames = NULL;
       CifString cell;
       ISTable *Category = NULL, *Table = NULL;;
       ReVarCifArray<CifString> list, target;
       ReVarPCifArray<int> * OutList;

       refBlockNames = ref->GetBlockNames(refnumBlocks);
       if (refnumBlocks > 1) 
            log << "WARNING - dictionary has " << refnumBlocks << " datablocks" << endl;
       Category = ref->GetTablePtr(refBlockNames[0],  "category");
       list.Clear();
       list.Add("mandatory_code");
       Category->CreateIndex("index0", list);
       target.Clear();
       target.Add("yes");
       OutList = Category->Search(target, list, errCode);
       if (errCode >= 0) {
            mandatoryIndex = Category->GetColumnIndex("id");
            BlockNames = in->GetBlockNames(numBlocks);
            for (i = 0; i < OutList->Length(); i++) {
                 Category->GetCell(cell, mandatoryIndex, (*OutList)[i]);
                 for (j = 0; j < numBlocks; j++) {
                      Table = in->GetTablePtr(BlockNames[j], cell.Text());
                      // if (in->IsTablePresent(BlockNames[j], cell.Text()) == 0) {
                      if (is_empty_table(Table)) {
                           log << "ERROR - table " << cell.Text()
                               << " is mandatory, but is missing" << endl;
                      }
                 }
            }
            for (i = 0; i < numBlocks; i++) if (BlockNames[i]) delete[] BlockNames[i];
            if (BlockNames) delete[] BlockNames;
       }
       if (OutList) delete (OutList);
       for (i = 0; i < refnumBlocks; i++) if (refBlockNames[i]) delete[] refBlockNames[i];
       if (refBlockNames) delete[] refBlockNames;
}
*/
static int ItemsChecking(CifFileObj *in, DICFileObj *ref, ofstream &log, const int is_nmr,
             int& wrong_date, CifString &date_error)
{
       int ret = 1, i, j, l, m, numBlocks, numTables, numRows /* , ParentIndex */ ;
       int iOut, errCode, icmp = 0, errCodeRegex /* , errCodeP_CH, refItemIndex */ ;
       int ItemTypeIndex, ItemTypeLIndex, ItemRangeIndex1, ItemRangeIndex2;
       int errCodeRange, irange = 0, matched, ival, imin, imax, ienum = 0, ie;
       int ItemEnumerationIndex, errCodeEnumeration, KeyIndex, entry_id, rowNo;
       int error_code = 0, missing_key = 0;
       char **BlockNames = NULL, **TableNames = NULL, *p = NULL, *pm = NULL;
       char /* ParentTblName[MAX_CIF_NAME_LENGTH], */ keyword[MAX_CIF_NAME_LENGTH];
       char **maxlist = NULL, **minlist = NULL, **enumlist = NULL;
       double dval, dmin, dmax, de;
       CifString ParentName, cell, typeCode, patternString;
       ISTable *Category = NULL /* , *ParentTbl = NULL, *ParentChildTbl = NULL */ ;
       ISTable /* *refItem = NULL, */ *ItemType = NULL, *ItemTypeL = NULL, *ItemRange = NULL;
       ISTable *ItemEnumeration = NULL, *CategoryKey = NULL;
       ReVarCifArray<CifString> *ItemNames = NULL, ParentList, ParentTarget;
       ReVarCifArray<CifString> list, target, nameList, refItemList, refItemTarget;
       ReVarCifArray<CifString> inItemList, inItemTarget, ItemTypeLList, ItemTypeLTarget;
       ReVarCifArray<CifString> keyTarget, keyList, colNamesKey;
       ReVarPCifArray<int> *OutList = NULL;
       regex_t preg;
       regmatch_t pmatch[NS];
       unsigned int k, len;

       // wrong_date = 0;
       nameList.Clear();
       nameList.Add("name");
/*
       // PARENT-CHILD
       ParentChildTbl = ref->GetTablePtr("item_linked");
       list.Clear();
       list.Add("child_name");
       ParentChildTbl->CreateIndex("index0",list);
       ParentIndex = ParentChildTbl->GetColumnIndex("parent_name");

       // MANDATORY
       refItem = ref->GetTablePtr("item");
       refItemList.Clear();
       refItemList.Add("category_id");
       refItemList.Add("mandatory_code");
       refItem->CreateIndex("index0",refItemList);
       refItemIndex = refItem->GetColumnIndex("name");
*/
       // REGEX
       ItemType = ref->GetTablePtr("item_type");
       ItemTypeIndex = ItemType->GetColumnIndex("code");
       ItemTypeL = ref->GetTablePtr("item_type_list");
       ItemTypeLList.Clear();
       ItemTypeLList.Add("code");
       ItemTypeLIndex = ItemTypeL->GetColumnIndex("construct");

       // RANGE
       ItemRange = ref->GetTablePtr("item_range");
       ItemRangeIndex1 = ItemRange->GetColumnIndex("maximum");
       ItemRangeIndex2 = ItemRange->GetColumnIndex("minimum");

       // ENUMERATION
       ItemEnumeration = ref->GetTablePtr("item_enumeration");
       ItemEnumerationIndex = ItemEnumeration->GetColumnIndex("value");

       // REDUNDANT ROWS - KEY
       keyList.Clear();
       keyList.Add("id");
       CategoryKey = ref->GetTablePtr("category_key");
       KeyIndex = CategoryKey->GetColumnIndex("name");

       BlockNames = in->GetBlockNames(numBlocks);
       for (i=0; i<numBlocks; i++) { //do for all datablocks
            TableNames = in->GetTableNames(BlockNames[i],numTables);
            for (j = 0; j < numTables; j++) {
                 Category = in->GetTablePtr(BlockNames[i], TableNames[j]);
                 if (is_empty_table(Category)) continue;
		 if (!strcasecmp(TableNames[j], "ndb_database_PDB_master")) continue;
                 if (is_nmr && !strcasecmp(TableNames[j], "atom_site")) continue;

                 // REDUNDANT ROWS - KEY
                 entry_id = Category->GetColumnIndex("entry_id");
                 rowNo = Category->GetNumRows();
                 if (entry_id >= 0  && rowNo > 1 &&
                     strcasecmp(TableNames[j], "ndb_database_proc")) {
                      log << "ERROR - multiple row in table " << TableNames[j] << "." << endl; 
                 }
                 if (strcasecmp(TableNames[j], "atom_site") &&
                     strcasecmp(TableNames[j], "ndb_nonstandard_list") &&
                     strcasecmp(TableNames[j], "ndb_pdb_compnd") &&
                     strcasecmp(TableNames[j], "ndb_pdb_source") &&
                     strcasecmp(TableNames[j], "ndb_poly_seq_scheme") &&
                     strcasecmp(TableNames[j], "rcsb_missing_residue_list") &&
                     strcasecmp(TableNames[j], "rcsb_struct_link") &&
                     strcasecmp(TableNames[j], "software") &&
                     strcasecmp(TableNames[j], "struct_mon_prot_cis")) {
                      keyTarget.Clear();
                      keyTarget.Add(TableNames[j]);
                      OutList = CategoryKey->Search(keyTarget, keyList, errCode);
                      if (errCode >= 0) {
                           colNamesKey.Clear();
                           for (k = 0; k < OutList->Length(); k++) {
                                CategoryKey->GetCell(cell, KeyIndex, (*OutList)[k]);
                                GetKeywordFromItemName(keyword, cell.Text());
                                colNamesKey.Add(keyword);
                           }
                           missing_key = Category->FindRedundantRows(colNamesKey, log, 1);
                           if (missing_key == 1)
                                error_code |= 1;
                           else if (missing_key == DUPLICATE_ROW)
                                error_code |= 2;
                      }
                      if (OutList) delete OutList; OutList = NULL;
                 }
/*
                 // MANDATORY
                 refItemTarget.Clear();
                 refItemTarget.Add(TableNames[j]);
                 refItemTarget.Add("yes");
                 OutList = refItem->Search(refItemTarget, refItemList, errCode);
                 if (errCode >= 0) {
                      for (k = 0; k < OutList->Length(); k++) {
                           refItem->GetCell(cell, refItemIndex, (*OutList)[k]);
                           GetKeywordFromItemName(keyword,cell.Text());
                           errCode = Category->GetColumnIndex(keyword);
                           if (errCode < 0) {
                                log << "ERROR - Mandatory item " << cell.Text() <<
                                  " is not in datablock " << BlockNames[i] << endl;
                           }
                      }
                 }
                 if (OutList) delete OutList; OutList = NULL;
*/
                 ItemNames = Category->GetItemNames();
                 for (k = 0; k < ItemNames->Length(); k++) {
                      target.Clear();
                      target.Add((*ItemNames)[k]);
/*           
                      // PARENT-CHILD --- Checking if item has a parent
                      iOut = ParentChildTbl->FindFirst(target, list, errCodeP_CH);
                      if (errCodeP_CH >= 0) {
                           // if YES checking for "parent" table & value in "child" item
                           ParentChildTbl->GetCell(ParentName, ParentIndex, iOut);
                           GetCategoryNameFromItemName(ParentTblName, ParentName.Text());
                           if ((errCodeP_CH = in->IsTablePresent(BlockNames[i], ParentTblName)) == 0) {
                                //table for parent item does not exist
                                log << "WARNING - Item " << (*ItemNames)[k].Text() <<
                                       " has parent item " << ParentName.Text() << " but table " <<
                                       ParentTblName << " is NOT present in the file" << endl;
                           } else {
                                ParentTbl = in->GetTablePtr(BlockNames[i], ParentTblName);
                                GetKeywordFromItemName(keyword, ParentName.Text());
                                ParentList.Clear();
                                ParentList.Add(keyword);
                                if (ParentTbl->FindIndex(keyword) == -1) { 
                                     ParentTbl->CreateIndex(keyword, ParentList);
                                }
                           }
                      }
*/
                      // REGEX step 1 --- compile a regex
                      iOut = ItemType->FindFirst(target, nameList, errCodeRegex);
                      if (errCodeRegex >= 0) {
                           ItemType->GetCell(typeCode, ItemTypeIndex, iOut);
                           ItemTypeLTarget.Clear();
                           ItemTypeLTarget.Add(typeCode);
                           iOut = ItemTypeL->FindFirst(ItemTypeLTarget, ItemTypeLList, errCode);
                           ItemTypeL->GetCell(cell, ItemTypeLIndex, iOut);
                           ConvertEscapedString(cell, patternString); 
                           icmp = regcomp(&preg, patternString.Text(), REG_EXTENDED);
                           regfree(&preg);
                      }

                      // RANGE step 1 --- finding min & max
                      OutList = ItemRange->Search(target, nameList, errCodeRange);
                      if (errCodeRange >= 0) {
                           maxlist = (char**) malloc(sizeof(char*)*(OutList->Length()+1));
                           minlist = (char**) malloc(sizeof(char*)*(OutList->Length()+1));
                           irange = OutList->Length();
                           for (l = 0; l < irange; l++) {
                                ItemRange->GetCell(cell, ItemRangeIndex1, (*OutList)[l]);
                                maxlist[l] = (char*) malloc(strlen(cell.Text())+1);
                                strcpy(maxlist[l], cell.Text());
                                if (!strcmp(maxlist[l], "?") || !strcmp(maxlist[l], "."))
                                     strcpy(maxlist[l], "");
                                ItemRange->GetCell(cell, ItemRangeIndex2, (*OutList)[l]);
                                minlist[l] = (char*) malloc(strlen(cell.Text())+1);
                                strcpy(minlist[l], cell.Text());
                                if (!strcmp(minlist[l], "?") || !strcmp(minlist[l], "."))
                                     strcpy(minlist[l], "");
                           }
                      }
                      if (OutList) delete (OutList); OutList = NULL;

                      // ENUMERATION step 1
                      OutList = ItemEnumeration->Search(target, nameList, errCodeEnumeration);
                      if (errCodeEnumeration >= 0) {
                           enumlist = (char**) malloc(sizeof(char*)*(OutList->Length()+1));
                           ienum = OutList->Length();
                           for (l = 0; l < ienum; l++) {
                                ItemEnumeration->GetCell(cell, ItemEnumerationIndex, (*OutList)[l]);
                                enumlist[l] = (char*) malloc(strlen(cell.Text())+1);
                                strcpy(enumlist[l], cell.Text());
                           }
                      }
                      if (OutList) delete (OutList); OutList = NULL;

                      // loop for all rows
                      numRows = Category->GetNumRows();
                      for (l = 0; l < numRows; l++){
                           Category->GetCell(cell, k, l);
/*
                           // PARENT-CHILD step 2
                           if (errCodeP_CH == 0) {
                                if ((cell != ".") && (cell != "?") && (cell != "")) {
                                     ParentTarget.Clear();
                                     ParentTarget.Add(cell);
                                     ParentTbl->CheckValue(ParentTarget, ParentList, errCode);
                                     if (errCode < 0) {
                                          log << "ERROR - Value " << cell.Text() << " for " <<
                                              (*ItemNames)[k].Text() << " is NOT defined in parent item "
                                              << ParentName.Text() << endl;
                                     }
                                }
                           }
*/
                           // REGEX step 2
                           if (errCodeRegex >= 0) {
                                if (icmp == 0 && cell != "." && cell != "?" && cell != "") {
                                     regcomp(&preg, patternString.Text(), REG_EXTENDED);
                                     ret = regexec(&preg, cell.Text(), NS, pmatch, 0);
                                     regfree(&preg);
                                     if (ret != 0) {
                                          log << "ERROR - value " << cell.Text() << " for " <<
                                              (*ItemNames)[k].Text() << " does not match the pattern (row "
                                              << l << ")" << endl;
                                          error_code |= 4;
                                          ret = 0;
                                     } else { 
                                          len = pmatch[0].rm_eo - pmatch[0].rm_so;
                                          if (len != strlen(cell.Text())) {
                                               if (strcmp(typeCode.Text(), "text")) { 
                                                    p = (char*)malloc(pmatch[0].rm_eo+1);
                                                    strncpy(p,  cell.Text(),  pmatch[0].rm_eo); 
                                                    p[pmatch[0].rm_eo]='\0';
                                                    pm=p+pmatch[0].rm_so;
                                                    log << "ERROR - value " << cell.Text() << " for "
                                                        << (*ItemNames)[k].Text() <<
                                                           " does not match the pattern (row "
                                                        << l << ")" << endl;
                                                    log << "ERROR - for " << (*ItemNames)[k].Text()
                                                        << " only " << pm << " is matched (row "
                                                        << l << ")" << endl;
                                                    error_code |= 4;
                                                    ret = 0;
                                                    free (p);
                                               }
                                          } else {
                                               if (!strcmp(typeCode.Text(), "yyyy-mm-dd")) {
                                                    if (is_wrong_date(cell.Text())) {
                                                         log << "ERROR - wrong date "
                                                             << cell.Text() << " for "
                                                             << (*ItemNames)[k].Text()
                                                             << " (row " << l << ")" << endl;
                                                         date_error += " incorrect date ";
                                                         date_error += cell.Text();
                                                         date_error += ",";
                                                         wrong_date++; //  = 1;
                                                    }
                                               }
                                          }
                                     }
                                }

                                // RANGE step 2 --- Range(cell.Text(),maxlist,minlist,
                                if (errCodeRange >= 0 && cell != "." && cell != "?" && cell != "") {
                                     m = 0;
                                     matched = 0;
                                     if (strcmp(typeCode.Text(), "float") == 0) {
                                          dval = atof(cell.Text());
                                          while (m < irange && !matched) {
                                               if (strcmp(minlist[m], "") && strcmp(maxlist[m], "")) {
                                                    dmin = atof(minlist[m]);
                                                    dmax = atof(maxlist[m]);
                                                    if (dmin == dmax) {
                                                         if (dval == dmax) matched = 1;
                                                    } else {
                                                         if (dval > dmin && dval < dmax) matched = 1;
                                                    }
                                               } else if (strcmp(minlist[m], "")) {
                                                    dmin = atof(minlist[m]);
                                                    if (dval > dmin) matched = 1;
                                               } else if (strcmp(maxlist[m], "")) {
                                                    dmax = atof(maxlist[m]);
                                                    if (dval < dmax) matched = 1;
                                               } else matched = 1;
                                               m++;
                                          }
                                          if (matched == 0) {
                                               log << "ERROR - for " << (*ItemNames)[k].Text() << " "
                                                   << cell.Text() << " is NOT in a range (row " << l
                                                   << ")" << endl;
                                               error_code |= 8;
                                               ret = 0;
                                          }
                                     } else {
                                          m = 0;
                                          matched = 0;
                                          if (strcmp(typeCode.Text(), "int") == 0) {
                                               ival = atoi(cell.Text());
                                               while (m < irange && !matched) {
                                                    if (strcmp(minlist[m], "") && strcmp(maxlist[m], "")) {
                                                         imin = atoi(minlist[m]);
                                                         imax = atoi(maxlist[m]);
                                                         if (imin == imax) {
                                                              if (ival == imin) matched = 1;
                                                         } else {
                                                              if (ival > imin && ival < imax) matched = 1;
                                                         }
                                                    } else if (strcmp(minlist[m], "")) {
                                                         imin = atoi(minlist[m]);
                                                         if (ival > imin) matched = 1;
                                                    } else if (strcmp(maxlist[m], "")) {
                                                         imax = atoi(maxlist[m]);
                                                         if (ival < imax) matched = 1;
                                                    } else matched = 1;
                                                    m++;
                                               }
                                               if (matched == 0) {
                                                    log << "ERROR - for " << (*ItemNames)[k].Text() << " "
                                                        << cell.Text() << " is NOT in a range (row " << l
                                                        << ")" << endl;
                                                    error_code |= 8;
                                                    ret = 0;
                                               }
                                          }
                                     }
                                }

                                // ENUMERATION step 2
                                if (strcasecmp(TableNames[j], "entity_src_gen") &&
                                    strcasecmp(TableNames[j], "entity_src_nat") &&
                                    strcasecmp(TableNames[j], "rcsb_nmr_ensemble") &&
                                    strcasecmp(TableNames[j], "rcsb_nmr_representative") &&
                                    errCodeEnumeration >= 0 && cell != "." && cell != "?" && cell != "") {
                                     m = 0;
                                     matched = 0;
                                     if (strcmp(typeCode.Text(), "float")==0) {
                                          dval = atof(cell.Text());
                                          while (m < ienum && !matched) {
                                               de = atof(enumlist[m]);
                                               if (dval == de) matched = 1;
                                               m++;
                                          }
                                     } else if (strcmp(typeCode.Text(), "int")==0) {
                                          ival = atoi(cell.Text());
                                          while (m < ienum && !matched) {
                                               ie = atoi(enumlist[m]);
                                               if (ival == ie) matched = 1;
                                               m++;
                                          }
                                     } else {
                                          while (m<ienum && !matched) {
                                               if (strcasecmp(cell.Text(), enumlist[m])==0) matched = 1;
                                               m++;
                                          }
                                     }
                                     if (matched == 0) {
                                          log << "ERROR - for " << (*ItemNames)[k].Text() << " "
                                              << cell.Text() << " is NOT defined in the enumeration list (row "
                                              << l << ")" << endl;
                                          error_code |= 16;
                                          ret = 0;
                                     }
                                }
                           }
                      }

                      if (errCodeRange >= 0) {
                           for (l = 0; l < irange; l++) {
                                if (maxlist[l]) free (maxlist[l]);
                                if (minlist[l]) free (minlist[l]);
                           }
                           if (maxlist) free (maxlist);
                           if (minlist) free (minlist);
                      }
                      if (errCodeEnumeration >= 0) {
                           for (l = 0; l < ienum; l++) {
                                if (enumlist[l]) free (enumlist[l]);
                           }
                           if (enumlist) free (enumlist);
                      }
                 }
                 if (ItemNames) delete ItemNames;
            }
            for (j = 0; j < numTables; j++)
                 if (TableNames[j]) delete[] TableNames[j];
            if (TableNames) delete[] TableNames;
       }

       for (i = 0; i < numBlocks; i++)
            if (BlockNames[i]) delete[] BlockNames[i];
       if (BlockNames) delete[] BlockNames;

       return error_code;
}

static void GetKeywordFromItemName(char *keyword, const char *itemName)
{
       int i, j, k, ilen;

       strcpy(keyword, "");
       if (!itemName || (ilen = strlen(itemName)) < 3) return;
     
       for (k = 1; k < ilen; k++) {
            if (itemName[k] == '.') break;
       }
       if (k == 1 || k == ilen) return;
       j = 0;
       for (i = k + 1; i < ilen; i++) {
            keyword[j] = itemName[i];
            j++;
       }
       keyword[j] = '\0';
}
/*
static void GetCategoryNameFromItemName(char *categoryName, const char *itemName) 
{
       int i, j;

       strcpy(categoryName, "");
       if (!itemName) return;

       for (i = 1, j = 0; i < strlen(itemName); i++) {
            if (itemName[i] != '.') {
                 categoryName[j] = itemName[i];
                 j++;
            } else break;
       }
       categoryName[j] = '\0';
}
*/
static void ConvertEscapedString(CifString& inString, CifString& outString)
{
       int tmpPos = 0;
       unsigned int posI;
       char* tmpStringP = NULL;
       tmpStringP = (char*) malloc(inString.Length() + 1);

       for (posI = 0; posI < inString.Length(); tmpPos++) {
            if ((inString.Text())[posI] == '\\') {
                 switch ((inString.Text())[posI + 1]) {
                      case '\\':
                           tmpStringP[tmpPos] = '\\';
                           posI += 2;
                           break;
                      case 't':
                           tmpStringP[tmpPos] = '\t';
                           posI += 2;
                           break;
                      case 'n':
                           tmpStringP[tmpPos] = '\n';
                           posI += 2;
                           break;
                      default:
                           tmpStringP[tmpPos] = (inString.Text())[posI];
                           posI++;
                           break;
                 }
            } else {
                 tmpStringP[tmpPos] = (inString.Text())[posI];
                 posI++;
            }
       }
       tmpStringP[tmpPos] = '\0';
       outString.Copy(tmpStringP);
       free (tmpStringP);
}

static int is_wrong_date(const char *date)
{
       int end_year, i_year, i_month, i_day, is_leap_year = 0, days_per_month;
       char year[12], month[12], day[12], *p = NULL;
       time_t local_reading;
       struct tm *tmptr;

       time(&local_reading);
       tmptr = localtime(&local_reading);
       strftime(year, 5, "%Y", tmptr);
       end_year = atoi(year) + 5;

       strcpy(day, &date[8]);
       strcpy(month, &date[5]);
       p = strstr(month, "-");
       if (p) *p = '\0';
       strcpy(year, date);
       p = strstr(year, "-");
       if (p) *p = '\0';
       i_year = atoi(year);

       if (i_year < 1970 || i_year > end_year) return 1;

       i_month = atoi(month);
       if (i_month < 1 || i_month > 12) return 1;

       i_day = atoi(day);

       if (i_year % 4 == 0 && i_year % 100 != 0 || i_year % 400 == 0)
            is_leap_year = 1;

       switch (i_month) {
             case 1:
             case 3:
             case 5:
             case 7:
             case 8:
             case 10:
             case 12:
                  days_per_month = 31;
                  break;
             case 2:
                  days_per_month = 28;
                  if (is_leap_year) days_per_month = 29;
                  break;
             case 4:
             case 6:
             case 9:
             case 11:
                  days_per_month = 30;
                  break;
             default:
                  days_per_month = 30;
                  break;
       }

       if (i_day < 1 || i_day > days_per_month) return 1;

       return 0;
}
