#define SNOWFLAKE                                   \
"                 _  8*       @                  \n"\
"    .  O    __ _| |_ __        '        *     ' \n"\
" *       /'v /\\_ + _/\\ v`\\ o                 \n"\
"         > x \\ _| |_ / x <                     \n"\
"   .0  __`./\\   _^_   /\\.'__ @       .:  '    \n"\
"      _\\ \\__/ /\\ v /\\ \\__/ /_              \n"\
"'    |_ + __ <  > <  > __ + _|     0.           \n"\
"       /_/  \\ \\/_^_\\/ /  \\_\\           .   \n"\
"  .  @   .'\\/  _ v _  \\/`.      *     8       \n"\
"         > x / _| |_ \\ x < @                  .\n"\
" .   :   \\.^_\\/_ + _\\/_^./   Oo    *         \n"\
"      '         |_|                             \n"\
/* this software is she12ware    *  .o  '. @    o. */
#define PAYPLAN "!!!PLEASE DONATE TO SDF.ORG!!!"
#define DID_PAY 0

#define VERSION 1.0

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#ifdef __unix__
 #include <sys/ioctl.h>

 #define DECXY struct winsize win;
 #define GETXY ioctl(STDIN_FILENO,TIOCGWINSZ,&win)!=-1
 #define GETXV win.ws_col
 #define GETYV win.ws_row

 #define ENVXV "COLUMNS"
 #define ENVYV "LINES"

 #define SEEDR srandom(time(NULL))
 #define GETRA 32+random()%(126-32+1)
 #define GETRC(S) S[random()%strlen(S)]
 #define GETRN(B,F) B+random()%F

 #define CLEAR printf("\x1B[H\x1B[2J")
 #define SLEEP(T) usleep(T)
#else
 #include <windows.h>

 #define DECXY CONSOLE_SCREEN_BUFFER_INFO win
 #define GETXY GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),&win)
 #define GETXV win.srWindow.Right-win.srWindow.Left+1
 #define GETYV win.srWindow.Bottom-win.srWindow.Top+1

 #define ENVXV "COLS"
 #define ENVYV "ROWS"

 #define SEEDR srand(time(NULL))
 #define RANDM rand()
 #define GETRA 32+rand()%(126-32+1)
 #define GETRC(S) S[rand()%strlen(S)]
 #define GETRN(B,F) B+rand()%F

 #define CLEAR system("cls")
 #define SLEEP(T) sleep(T/1000000)
#endif

char *init_string(char,long);
char **init_strings(char,long,long);
void read_strings(char*,char**,long,long);

int main(int argc,char **argv){
   int c,s;
   int cascade=0;
   int debug=0;
   int frame=0;
   int increase=0;
   int maximize=0;
   int randomize=0;
   int tail=0;
   int upwards=0;
   int wide=0;

   long h,w,x;
   long length;
   long density=750;
   long height=10;
   long iterations=-1;
   long swing=0;
   long timeout=140000;
   long width=53;

   char bg=' ';
   char *background=NULL;
   char *charset=NULL;
   char *frameset="--||+";
   char *framebar_top;
   char *framebar_bot;
   char *intro="It'Snowtime!";
   char *overlay=DID_PAY?NULL:PAYPLAN;
   char *snow=".*'.'*:oOO008@****...@";
   char **buf;
   char **top;
   char **bot;
   
   while((c=getopt(argc,argv,"b:c:d:Df:Fh:i:Io:LRs:St:Tw:WUX"))!=-1)
         switch(c){
                case'b':if(strlen(optarg)>1)
                           background=optarg;
                        else bg=*optarg;break;
                case'c':snow=optarg;break;
                case'd':density=strtol(optarg,NULL,0);break;
                case'f':frameset=optarg;break;
                case'h':height=strtol(optarg,NULL,0);break;
                case'i':iterations=strtol(optarg,NULL,0);break;
                case'o':overlay=optarg;break;
                case's':swing=strtol(optarg,NULL,0);break;
                case't':timeout=strtol(optarg,NULL,0);break;
                case'w':width=strtol(optarg,NULL,0);break;
                case'D':debug=1;break;
                case'F':frame=1;break;
                case'I':increase=1;break;
                case'L':cascade=1;break;
                case'R':cascade=2;break;
                case'S':randomize=1;break;
                case'T':tail=1;break;
                case'U':upwards=1;break;
                case'W':wide=1;break;
                case'X':maximize=1;break;
                default:printf("\n%s\nusage: %s [b:c:d:Df:Fh:i:Io:LRs:St:Tw:WUX]\n"
                               "\t-b <c>[c*].....set background character, string or file\n"
                               "\t-c <s>.........set flake charset string\n"
                               "\t-d <ld>........set background density\n"
                               "\t-f <cccc>[c]...set framebar characters: <top><bottom><left><right>[corner]\n"
                               "\t-h <ld>........set height\n"
                               "\t-i <ld>........set iterations\n"
                               "\t-o <s>.........set overlay string or file\n"
                               "\t-s <ld>........set flake swing range\n"
                               "\t-t <ld>........set timeout in microseconds\n"
                               "\t-w <ld>........set width\n"
                               "\n"
                               "\t-D.............show debug information\n"
                               "\t-F.............draw frame\n"
                               "\t-I.............increase density each iteration\n"
                               "\t-L.............cascade to left\n"
                               "\t-R.............cascade to right\n"
                               "\t-S.............random parameters\n"
                               "\t-T.............flakes draw tails\n"
                               "\t-U.............flakes go upwards\n"
                               "\t-W.............full width flakes\n"
                               "\t-X.............maximize to current terminal size\n"
                               ,SNOWFLAKE,*argv);
                        return 1;
      }

   SEEDR;

   /* get terminal window size    .8o   .@ :  o*.  */
   if(maximize){
      DECXY;
      if(GETXY){
         width=GETXV;
         height=GETYV;
         height-=debug*(5+strlen(snow)/width)+1;
      }else if(getenv(ENVXV)&&getenv(ENVYV)){
         width=strtol(getenv(ENVXV),NULL,0);
         height=strtol(getenv(ENVYV),NULL,0);
         height-=debug*(5+strlen(snow)/width)+1;
      }
   }

   height=height>0?height:1;
   width=width>0?width:1;

   /* randomize parameters   .0  : .*:.   .'  O' 0 */
   if(randomize){
      height=GETRN(1,height);
      width=GETRN(1,width);
      randomize=GETRN(1,10*height);
      bg=GETRA;
      background="/dev/urandom";
      density=GETRN(10,randomize+10*height);
      swing=GETRN(0,height);
      timeout=GETRN(35000,900000-35000);
      increase=GETRN(0,2);
      cascade=GETRN(0,3);
      tail=GETRN(0,2);
      upwards=GETRN(0,2);
      wide=GETRN(0,2);

      frame=GETRN(0,2);
      frameset=init_string('\0',5);
      if(frame&&!frameset)return 1;
      for(w=0;w<5;w++)
          frameset[w]=GETRA;

      snow=init_string('\0',randomize);
      if(!snow)return 1;
      for(w=0;w<randomize;w++)
          snow[w]=GETRA;
   }

   /* create framebars   . *        .@*@*  *  8 'O */
   if(frame){
      if(height>2&&width>2){
         framebar_top=init_string(frameset[0],width);
         framebar_bot=init_string(frameset[1],width);
         if(!framebar_top||!framebar_bot)return 1;
         if(frameset[4])
            framebar_top[0]
           =framebar_bot[0]
           =framebar_top[width-1]
           =framebar_bot[width-1]
           =frameset[4];

         height-=2;
         width-=2;
      }else frame=0;
   }

   /* create background    '*8 '.    o   . .*   0  */
   bot=init_strings(bg,height,width);
   if(!bot)return 1;

   if(background)
      read_strings(background,bot,height,width);

   /* create snowerlay     .   *   O.o.  * .'*  o  */
   top=init_strings(bg,height,width);
   if(!top)return 1;

   if(overlay)
      read_strings(overlay,top,height,width);

   /* create charset      * '     * .   O  :. o0 8 */
   charset=init_string('\0',density+strlen(snow)+2);
   if(!charset)return 1;
   memset(charset,bg,density);
   strcat(charset,snow);

   /* create buffer      . 'o .   *8  O * '   @.  :*/
   length=(cascade?width+height:width)+swing;

   buf=init_strings(bg,height,length);
   if(!buf)return 1;

   if(debug){
      x=length/2-strlen(intro)/2;
      if(x>=0)
         memcpy(buf[height/2]+x,intro,strlen(intro));
   }

   /* start of output      . * *    . .  O. @'     */
   while(iterations>-1?iterations--:iterations!=0){
         CLEAR;

         if(debug){
            if(frame)putchar(' ');
            for(w=1;w<=width;w++)
                putchar(w%5?',':'|');
            putchar('\n');
         }

         if(frame)puts(framebar_top);

         /* snow row by row      ' 8 .  . @ *o *  .*/
         for(c=h=0;h<height;h++){
             if(frame)putchar(frameset[2]);

             if(swing){
                if(c==0||c==swing)
                   s=c?0:1;
                s?c++:c--;

                if(swing>height)
                   c=GETRN(1,height);
             }

             if(cascade)
                x=cascade+upwards==2?height-h:h;
             else x=0;

             /* snow flake by flake   . @8 @* . 8  */
             for(w=c+x;w<width+c+x;w++)
                 if(top[h][w-c-x]!=bg)
                    putchar(top[h][w-c-x]);
                 else if(buf[h][wide?c+cascade:w]!=bg)
                    putchar(buf[h][wide?c+cascade:w]);
                 else putchar(bot[h][w-c-x]);

             if(frame)putchar(frameset[3]);

             putchar('\n');
         }
   
         if(frame)puts(framebar_bot);

         if(debug)
            printf("height: %ld width: %ld round: %ld timeout: %ld\n"
                   "cascade: %s swing: %d/%ld density: %ld increase: %d\n"
                   "direction: %s tail: %s wide: %s random: %s\n"
                   "background: '%c' frameset: '%s' charset: '%s'\n"
                   ,height,width,iterations,timeout
                   ,cascade?cascade<2?"left":"right":"no",c,swing,density,increase
                   ,upwards?"up":"down",tail?"yes":"no",wide?"yes":"no",randomize?"yes":"no"
                   ,bg,frameset,snow);

         /* end of output    *. @    .8o  O  :'  . */

         if(increase&&density){
            x=100;
            density=strlen(charset)-strlen(snow);
            increase=density?density>x?density/x:1:0;
            charset+=increase;
         }

         /* shift rows      .O   :'@      :.  * 8  */
         if(upwards){
            for(h=0;h<height-1;h++)
                strncpy(buf[h],buf[h+1],length);
            c=h-1;
            x=0;
         }else{
            for(--h;h;h--)
                strncpy(buf[h],buf[h-1],length);
            c=h+1;
            x=height/2;
         }

         /* recreate last row   *' 0 0 *   .     @ */
         for(w=0;w<length;w++)
             if((tail
               &&height>1)
               &&buf[c][w]!=bg
               &&buf[c][w]!=buf[GETRN(x,height/2)][w])
                 buf[h][w]=buf[c][w];
             else
                if(w==0)
                   buf[h][w]=GETRC(charset);
                else
                   for(buf[h][w]=GETRC(charset);
                       buf[h][w]==buf[h][w-1]
                     &&buf[h][w]!=bg&&density>1;
                       buf[h][w]=GETRC(charset))
                       ;

         SLEEP(timeout);
   }

   return 0;
}

/* allocate and fill char array    *  .0 *   oO   .*/
char *init_string(char bg,long width){
   char *str=NULL;

   str=malloc(width+1);
   if(!str)return NULL;

   memset(str,'\0',width+1);
   memset(str,bg,width);

   return str;
}

/* allocate and fill 2D char array    .o    '* .@  */
char **init_strings(char bg,long height,long width){
   long h;
   char **strs=NULL;

   strs=malloc(height*sizeof(*strs));
   if(!strs)return NULL;

   for(h=0;h<height;h++){
       strs[h]=malloc(width+1);
       if(!strs[h])return NULL;

       memset(strs[h],'\0',width+1);
       memset(strs[h],bg,width);
   }

   return strs;
}

/* read file or string and fill 2D char array   .o@*/
void read_strings(char *input,char **strs,long height,long width){
   long h,w;
   char c,*p;
   FILE *in=NULL;

   /* read input file      . *    8o   . :'   ' @  */
   if(strlen(input)==1&&*input=='-')
      in=stdin;
   else
      in=fopen(input,"r");

   if(in){
      input=init_string(' ',height*width);

      w=0;
      while((c=fgetc(in))!=EOF&&w<height*width)
            input[w++]=c;            
            
      if(in!=stdin)
         fclose(in);
   }

   /* transfer ascii chars into 2D array      *' 8  */
   p=input;

   for(h=0;h<height;h++)
       for(w=0;w<width;w++)
           switch(c=*p++){
                  case'\0':h=height;w=width;break;
                  case'\r':
                  case'\n':w=width;break;
                  case'\t':w+=3;break;
                  default:if(c<32||c>126)break;
                          strs[h][w]=c;
           }

   if(in)free(input);
}