#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;
}