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