#include <conio.h>
#include <ctype.h>
#include <malloc.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define      DEBUG

#define    EOS   ('\0')
#define    bool  char

#define     In(x,y,z)      ((x)<=(y) && (y)<=(z))
#define     Min(x,y)       ((x)<(y) ? (x) : (y))
#define     Max(x,y)       ((x)>(y) ? (x) : (y))
#define     streq          !strcmp

enum {weekly,monthly,yearly};

void create_calendar(void);
void exit_here(bool opt,const char *format,...);
int  get_day_of_week(int year,int month,int day);
void get_second_field_parameters(char *string);
void get_third_field_parameters(char *string);
void get_today(void);
bool leap_year(int year);
int  n_rows_in_monthly_calendar(int year,int month,int dw);
void preprocess(int argc,char *argv[]);
void show_usage(void);
int  str2num(char *str);
void write_calendar_page(int n,FILE *fp_out);


int  CalendarType; /* weekly=0, monthly=1 or yearly=2 */
int  Npages;       /* -n */
int  MperPage=1;     /* m in -n,m.  For "monthly" only. */
int  StartYear,StartMonth,StartDay;  /* [year/month/day] */
/* Above are determined in preprocess(),
   from the command line. */

int  Days_of_month[12]={31,28,31,30,31,30,31,31,30,31,30,31};
char *MonthName[12]={"January","February","March","April",
                     "May","June","July","August",
                     "September","October","November","December"},
     *Month_name[12]={"Jan","Feb","Mar","Apr",
                     "May","Jun","Jul","Aug",
                     "Sep","Oct","Nov","Dec"};
char *DayName[7]={"Sunday","Monday","Tuesday","Wednesday",
                  "Thursday","Friday","Saturday"},
     *Day_name[7]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
int  ThisYear,ThisMonth,Today; /* Counts from one. */

void main(int argc,char *argv[])
{
  char *calendartype[3]={"weekly","monthly","yearly"};

  if(!In(2,argc,4)) show_usage();

  get_today();

  preprocess(argc,argv);
  if(MperPage>=4) exit_here(0,"This option not available yet.");

  printf("\nWe'll create %d page",Npages);
  if(Npages>1) printf("s");
  printf(" of %s calendar starting from %d",
         calendartype[CalendarType],StartYear);
  if(CalendarType==weekly) printf("/%d/%d.\n",StartMonth,StartDay);
  else if(CalendarType==monthly){
    printf("/%d.\nMonth per page = %d.\n",StartMonth,MperPage);
  }
  else printf(".\n");

  cprintf("\r\nContinue ? (y/n): ");
  if(getche()!='y') exit_here(0,"\nAborted by user request.");

  create_calendar();

  printf("\nExecute \"latex calendar\".\n");
}

              /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

void preprocess(int argc,char *argv[])
{
  char *p;

  /* first field, CalendarType */
  if(argv[1][0]!='-') show_usage();
  if(streq((p=&(argv[1][1])),"w") || streq(p,"weekly"))
                                              CalendarType = weekly;
  else if(streq(p,"m") || streq(p,"monthly")) CalendarType = monthly;
  else if(streq(p,"y") || streq(p,"yearly"))  CalendarType = yearly;
  else show_usage();

  /* give default values, tentatively */
  Npages = 1;
  StartYear = ThisYear;
  StartMonth = ThisMonth;
  StartDay = Today; /* year day */

  if(argv[2][0]=='-'){
    get_second_field_parameters(&(argv[2][1]));
    if(argc==4) get_third_field_parameters(argv[3]);
    else if(argc!=3) show_usage();
  }
  else if(argc==3) get_third_field_parameters(argv[2]);
  else if(argc!=2) show_usage();
}

              /* -------------------------------------- */

void show_usage(void)
{
  printf("\nUsage: nemocal -options [-n[,m]] [year/month/day]\n\n\
       where -options = -w[eekly]|-m[onthly]|-y[early]\n\
             -n = number of pages\n\
              m = number of months in a page (for -monthly only)\n\
              year/month/day = start of the calendar\n\n");
  exit(0);
}

              /* -------------------------------------- */

void print_rows(int rows,int dw,int dmon,bool center,
                int width,int height6,int height5,
                int depth6,int depth5,FILE *fp_out)
{ /* for monthly calendars only */
  int x,row,i;
  char xstr[21];

  x = 0;
  for(row=0;row<rows;row++){
    for(i=0;i<7;i++){
      if(i==0){
        if(rows<6)
             fprintf(fp_out,"\\rule[-%dpt]{0pt}{%dpt}  ",depth5,height5);
        else fprintf(fp_out,"\\rule[-%dpt]{0pt}{%dpt}  ",depth6,height6);
      }
      else fprintf(fp_out,"                         ");
      if(row==0){
        if(i<dw) x = 0;
        else x++;
      }
      else{
        if(x!=0) x++;
        if(x>dmon) x = 0;
      }
      if(x==0) strcpy(xstr," ");
      else sprintf(xstr,"%d",x);
      if(center){
        if(i<6) fprintf(fp_out,"\\makebox[%dpt]{%s}   &\n",width,xstr);
        else fprintf(fp_out,"\\makebox[%dpt]{%s} \\\\ \\hline\n",
                      width,xstr);
      }
      else{
        if(i<6) fprintf(fp_out,"\\makebox[%dpt][r]{%s}   &\n",width,xstr);
        else fprintf(fp_out,"\\makebox[%dpt][r]{%s} \\\\ \\hline\n",
                      width,xstr);
      }
    }
  }
}

              /* -------------------------------------- */

void create_calendar(void)
{
  FILE *fp_out;
  int n;

  fp_out = fopen("calendar.tex","wt");
  if(fp_out==NULL) exit_here(1,"Can't create calendar.tex.");

  cprintf("\r\n\nCreating calendar.tex");
  fprintf(fp_out,"\\documentstyle{article}\n\n");
  fprintf(fp_out,"\\oddsidemargin 0cm\n");
  fprintf(fp_out,"\\evensidemargin 0cm\n");
  if(CalendarType==weekly) fprintf(fp_out,"\\topmargin -1cm\n");
  else fprintf(fp_out,"\\topmargin -1.6cm\n");

  fprintf(fp_out,"\\parindent 0cm\n");

  if(CalendarType!=monthly || (MperPage!=1 && MperPage!=4)){
    /* Portrait mode */
    fprintf(fp_out,"\\textheight 11in\n");
    fprintf(fp_out,"\\textwidth 16.44cm\n");
  }
  else{ /* Landscape mode */
    fprintf(fp_out,"\\textheight 7.0in\n");
    fprintf(fp_out,"\\textwidth 9.0in\n");
  }

  fprintf(fp_out,"\\pagestyle{empty}\n\n");
  fprintf(fp_out,"\\begin{document}\n\n");

  for(n=1;n<=Npages;n++){
    write_calendar_page(n,fp_out);
    cprintf(" [%d]",n);
  }

  fprintf(fp_out,"\\end{document}\n\n");
  fclose(fp_out);

  printf("  Done.\n");
  if(CalendarType==monthly && (MperPage==1 || MperPage==4))
    printf("\nRemember the landscape mode!\n");
}

              /* -------------------------------------- */

void write_calendar_page(int n,FILE *fp_out)
{
  int startmonth,endmonth,startyear,endyear,
      d_Sun,dw,i,j,k,days_remaining,day,dmon,rows,month;
  char month_str[21],year_str[21];

  if(n>1) fprintf(fp_out,"\\newpage\n");

  if(CalendarType==weekly){
    /* for weekly calendars, one page may have two months/years */
    dw = get_day_of_week(StartYear,StartMonth,StartDay);
    if(StartDay>=dw){
      d_Sun = StartDay - dw + 1;
      startyear = endyear = StartYear;
      startmonth = endmonth = StartMonth;
      days_remaining = Days_of_month[StartMonth-1] - StartDay;
      if(StartMonth==2 && leap_year(StartYear)) days_remaining++;
      if(days_remaining<7-dw){
        if(StartMonth<12) endmonth = startmonth+1;
        else{
          endmonth = 1;
          endyear = StartYear+1;
        }
      }
    }
    else{
      if(StartMonth>1){
        startyear = endyear = StartYear;
        startmonth = StartMonth-1;
        endmonth = StartMonth;
      }
      else{
        startyear = StartYear - 1;
        endyear = StartYear;
        startmonth = 12;
        endmonth = 1;
      }
      d_Sun = Days_of_month[startmonth-1] - dw + 2;
      if(startmonth==2 && leap_year(startyear)) d_Sun++;
    }
    if(endmonth==startmonth)
      sprintf(month_str,"%s",Month_name[startmonth-1]);
    else sprintf(month_str,"%s/%s",
                 Month_name[startmonth-1],Month_name[endmonth-1]);
    if(endyear==startyear) sprintf(year_str,"%d",startyear);
    else sprintf(year_str,"%d/%d",startyear,endyear);

    if(n%2==1) fprintf(fp_out,
            "{\\large\\sf\n\\hfill %s \\ %s\n}\n\\vspace{1cm}\n\n",
            month_str,year_str);
    else fprintf(fp_out,"{\\large\\sf\n %s \\ %s\n}\n\\vspace{1cm}\n\n",
                 month_str,year_str);

    fprintf(fp_out,"\\begin{tabular}{|c|c|} \\hline\n");
    for(i=1;i<=7;i++){
      day = d_Sun + i - 1;
      if(day>(dmon = Days_of_month[startmonth-1] +
                     (startmonth==2 && leap_year(startyear))))
        day -= dmon;
      fprintf(fp_out,"\\rule{0cm}{1.5cm}{\\Huge %d}\
 & \\rule{13.68cm}{0cm} \\\\ \n",day);
      fprintf(fp_out,"\\rule{0cm}{1cm}\\raisebox{.1cm}{\\sf %s}\
 & \\\\ \\hline \n",DayName[i-1]);
    }
    fprintf(fp_out,"\\end{tabular}\n\n");

    StartDay += 7;
    if(StartDay > (dmon = Days_of_month[StartMonth-1] +
                          (StartMonth==2 && leap_year(StartYear)))){
      StartDay -= dmon;
      StartMonth++;
      if(StartMonth>12){ StartMonth = 1; StartYear++; }
    }
  }
  else if(CalendarType==monthly){
    switch(MperPage){
    case 1:
      dw = get_day_of_week(StartYear,StartMonth,1) - 1;
           /* Sunday=0,Monday=1 etc. for no good reason */
      rows = n_rows_in_monthly_calendar(StartYear,StartMonth,dw);
      if(rows<6) fprintf(fp_out,"\\rule{0cm}{0.6cm}\n");
      fprintf(fp_out,"\\begin{center}\n\n");
      fprintf(fp_out,"{\\huge\\sf    %s \\rule{1.5em}{0em} %d}\n\n",
                   MonthName[StartMonth-1],StartYear);
      fprintf(fp_out,"\\rule{0em}{3em}\n\n");
      fprintf(fp_out,"{\\Large\\sf\n");
      fprintf(fp_out,"\\begin{tabular}{|c|c|c|c|c|c|c|} \\hline\n");
      fprintf(fp_out,"\\rule[-3mm]{0mm}{10mm}");
      for(i=0;i<7;i++){
        fprintf(fp_out,"\\makebox[71pt]{%s}",Day_name[i]);
        if(i<6) fprintf(fp_out," &\n");
      }
      fprintf(fp_out," \\\\ \\hline\n");
      dmon = Days_of_month[StartMonth-1] +
             (StartMonth==2 && leap_year(StartYear));
      print_rows(rows,dw,dmon,0,71,65,71,48,54,fp_out);
      fprintf(fp_out,"\\end{tabular}\n");
      fprintf(fp_out,"}\n\n");
      fprintf(fp_out,"\\end{center}\n\n");
      StartMonth++;
      if(StartMonth>12){ StartMonth = 1;  StartYear++; }
      break;
    case 2:
      for(j=0;j<2;j++){
        if(j==0) fprintf(fp_out,"\\rule{0cm}{0.5cm}\n");
        else     fprintf(fp_out,"\\rule{0cm}{1.5cm}\n");
        dw = get_day_of_week(StartYear,StartMonth,1) - 1;
             /* Sunday=0,Monday=1 etc. for no good reason */
        rows = n_rows_in_monthly_calendar(StartYear,StartMonth,dw);
        fprintf(fp_out,"\\begin{center}\n\n");
        fprintf(fp_out,"{\\Large\\sf %s \\rule{1.5em}{0em} %d}\n\n",
                       MonthName[StartMonth-1],StartYear);
        fprintf(fp_out,"\\rule{0em}{2em}\n\n");
        fprintf(fp_out,"{\\large\\sf\n");
        fprintf(fp_out,"\\begin{tabular}{|c|c|c|c|c|c|c|} \\hline\n");
        fprintf(fp_out,"\\rule[-7pt]{0pt}{22pt}");
        for(i=0;i<7;i++){
          fprintf(fp_out,"\\makebox[43pt]{%s}",Day_name[i]);
          if(i<6) fprintf(fp_out," &\n");
        }
        fprintf(fp_out," \\\\ \\hline\n");
        dmon = Days_of_month[StartMonth-1] +
               (StartMonth==2 && leap_year(StartYear));
        print_rows(rows,dw,dmon,0,43,37,43,25,31,fp_out);
        fprintf(fp_out,"\\end{tabular}\n");
        fprintf(fp_out,"}\n\n");
        fprintf(fp_out,"\\end{center}\n\n");
        StartMonth++;
        if(StartMonth>12){ StartMonth = 1;  StartYear++; }
      }
      break;
      /* case 4 and case 6 not available yet */
    }
  }
  else{ /* yearly */
    fprintf(fp_out,"\\begin{center}\n");
    fprintf(fp_out,"{\\huge\\sf %d}\n",StartYear);
    fprintf(fp_out,"\\end{center}\n\n");
    for(j=0;j<4;j++){ /* each j corrsponds to three months */
      if(j==0) fprintf(fp_out,"\\rule{0cm}{3.5cm}\n");
      else     fprintf(fp_out,"\\rule{0cm}{3.0cm}\n");
      for(k=0;k<3;k++){
        fprintf(fp_out,"\\parbox{4.8cm}{\n");
        /* Write a monthly calendar here. */
        month = j*3 + k + 1;

        dw = get_day_of_week(StartYear,month,1) - 1;
             /* Sunday=0,Monday=1 etc. for no good reason */
        rows = n_rows_in_monthly_calendar(StartYear,month,dw);
        fprintf(fp_out,"\\begin{center}\n\n");
        fprintf(fp_out,"{\\sf %s}\n\n",MonthName[month-1]);
        fprintf(fp_out,"\\rule{0em}{1em}\n\n");
        fprintf(fp_out,"{\\sf\n");
        fprintf(fp_out,"\\begin{tabular}{|c|c|c|c|c|c|c|} \\hline\n");
        fprintf(fp_out,"\\rule[-3pt]{0pt}{11pt}");
        for(i=0;i<7;i++){
          fprintf(fp_out,"\\makebox[7pt]{\\footnotesize %s}",Day_name[i]);
          if(i<6) fprintf(fp_out," &\n");
        }
        fprintf(fp_out," \\\\ \\hline\n");
        dmon = Days_of_month[month-1] +
               (month==2 && leap_year(StartYear));
        print_rows(rows,dw,dmon,1,7,8,11,-2,0,fp_out);
        fprintf(fp_out,"\\end{tabular}\n");
        fprintf(fp_out,"}\n\n");
        fprintf(fp_out,"\\end{center}\n\n");

        if(k<2) fprintf(fp_out,"}\\rule{0.9cm}{0cm}\n");
        else    fprintf(fp_out,"} \\\\ \n");
      }
    }
    StartYear++;
  }
}

/*
 *
 * void exit_here(bool opt,const char *format,...)
 *   Prints formatted error messages on stdout and exits.
 *
 * int get_day_of_week(int year,int month,int day)
 *   Sun=1,Mon=2 etc.
 *
 * void get_second_field_parameters(char *string)
 *   Computes the global variable(s) Npages (and MperPage) from the
 *   input string -n[,m].
 *
 * void get_third_field_parameters(char *string)
 *   Computes the global variables StartYear,StartMonth and StartDay
 *   from the input string year/month/day.  The CalendarType strongly
 *   affects the way this routine calculates those values.
 *
 * void get_today(void)
 *
 * bool leap_year(int year)
 *
 * int n_rows_in_monthly_calendar(int year,int month,int dw)
 *   year is to determine leap year.
 *   dw is day of the week counting from zero.
 *
 * int str2num(char *str)
 *
 */

void exit_here(bool opt,const char *format,...)
{ /* opt==0 iff doesn't show !*** ERROR ***! */
  char buffer[401];
  va_list argp;

  /* write to {\tt buffer[]} */
  va_start(argp,format);
  vsprintf(buffer,format,argp);
	va_end(argp);
  /* print the message and exit */
  printf("\n");
  if(opt) printf("!*** ERROR ***! ");
  printf("%s\n\n",buffer);
  exit(1);
}

              /* -------------------------------------- */

int get_day_of_week(int year,int month,int day)
{ /* 1994/1/1 is Saturday */
  int day_1_1, /* day of the week of Jan 1 of the input year */
      day_m_1, /* day of the week of year/month/1 */
      delta,n;

  /* get day_1_1 */
  delta = 0;
  if(year>=1994){
    for(n=1994;n<year;n++){
      delta++;
      if(leap_year(n)) delta++;
    }
  }
  else{
    for(n=year;n<1994;n++){
      delta++;
      if(leap_year(n)) delta++;
    }
    delta = -delta;
  }
  day_1_1 = (delta + 6)%7 + 1;

  /* get day_m_1 */
  delta = 0;
  for(n=0;n<month-1;n++){
    delta += Days_of_month[n];
    if(n==1 && leap_year(year)) delta++;
  }
  day_m_1 = (delta+day_1_1-1)%7 + 1;
  return (day_m_1 + day - 2)%7 + 1;
}

              /* -------------------------------------- */

void get_second_field_parameters(char *string)
{
  int x,pos,vpos;
  char c,str1[11],str2[11];

  if((x=str2num(string)) >= 0) Npages = x;
  else{
    if(CalendarType!=monthly) show_usage();
    pos = vpos = 0;
    while((c=string[pos++])!=',' && c!=EOS){
      if(vpos>10) show_usage();
      str1[vpos++] = c;
    }
    if(c!=',') show_usage();
    str1[vpos] = EOS;
    if((Npages=str2num(str1))<0) show_usage();
    vpos = 0;
    while((c=string[pos++])!=EOS){
      if(vpos>=10) show_usage();
      str2[vpos++] = c;
    }
    str2[vpos] = EOS;
    if((MperPage=str2num(str2))<0) show_usage();
    if(MperPage!=1 && MperPage!=2 && MperPage!=4 && MperPage!=6)
      exit_here(0,"You can have only 1,2,4 or 6 months per page.");
  }
}

              /* -------------------------------------- */

void get_third_field_parameters(char *string)
{
  int n=0;                /* number of subfields separated by / */
  char numstring[3][5];
  int pos=0,vpos=0,i;
  char c;

  while((c=string[pos++])!=EOS){
    if(c=='/'){
      if(n>=2) show_usage();
      numstring[n++][vpos] = EOS;
      vpos = 0;
    }
    else{
      if(vpos>=4) show_usage();
      numstring[n][vpos++] = c;
    }
  }
  numstring[n++][vpos] = EOS;
  for(i=0;i<n;i++){
    if(str2num(numstring[i])<=0) show_usage();
  }

  switch(CalendarType){ /* StartXxxx */

    case weekly:
      if(n==1) StartDay = str2num(numstring[0]);
      else if(n==2){
        StartMonth = str2num(numstring[0]);
        StartDay = str2num(numstring[1]);
      }
      else if(n==3){
        StartYear = str2num(numstring[0]);
        StartMonth = str2num(numstring[1]);
        StartDay = str2num(numstring[2]);
      }
    break;

    case monthly:
      if(n==1) StartMonth = str2num(numstring[0]);
      else if(n==2){
        StartYear = str2num(numstring[0]);
        StartMonth = str2num(numstring[1]);
      }
      else exit_here(0,"No need to specify the day.");
    break;

    case yearly:
      if(n!=1) exit_here(0,"No need to specify the month or day.");
      StartYear = str2num(numstring[0]);
    break;

  }
}

              /* -------------------------------------- */

void get_today(void)
{
  time_t time_of_day;
  auto struct tm *tmbuf;

  time_of_day = time(NULL);
  tmbuf = localtime(&time_of_day);
  Today = tmbuf->tm_mday;
  ThisMonth = tmbuf->tm_mon + 1;
  ThisYear = tmbuf->tm_year + 1900;
}

              /* -------------------------------------- */

bool leap_year(int year)
{
  if(year%4 == 0){
    if(year%100 == 0){
      if(year%400 == 0) return 1;
      else              return 0;
    }
    else return 1;
  }
  else return 0;
}

              /* -------------------------------------- */

int n_rows_in_monthly_calendar(int year,int month,int dw)
{
  int rows=1,i,dmon;

  dmon = Days_of_month[month-1] + (month==2 && leap_year(year));

  for(i=2;i<=dmon;i++){
    if((i+dw)%7 == 1) rows++;
  }

  return rows;
}

              /* -------------------------------------- */

int str2num(char *str)
{ /* Returns 0 <= x <= 29999 normally.
     Returns -1 if input is illegal. */
  char c;
  int n=0,pos=0;

  while((c=str[pos])!=EOS){
    if(!isdigit(c)) return -1;
    n = n*10 + c - '0';
    pos++;
  }
  if(pos>5 || (pos==5 && str[0]>'2'))  return -1;
  else                                 return n;
}

