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

typedef int Stat;
typedef Stat Level;
typedef long long Money;

#define DOLLARS * ((Money)100)
#define CENTS

typedef enum
{
  Str,
  End,
  Dex,
  Agi,
  Pre,
  Wil,
  Kno,
  Cun,
  Num_Abilities
} Ability;

typedef enum
{
  Powerhouse,
  Grunt,
  Ace,
  Athlete,
  Enchanter,
  Devotee,
  Expert,
  Ranger,
  Num_Classes
} Class;

typedef enum
{
  Skill_N,
  Skill_X,
  Skill_C,
  Num_Skill_Availabilities
} Skill_Avail;

typedef enum
{
  Male,
  Female,
  OtherGender,
  Num_Genders
} Gender;

typedef struct
{
  const char* name;
  Ability ab;
  Skill_Avail avail[Num_Classes];
} Skill;

typedef struct
{
  const Skill* skill;
  Stat rank;
} Ranked_skill;

typedef struct
{
  const char* name;
  Stat prereq_ability[Num_Abilities];
  size_t num_prereq_skills;
  Ranked_skill* prereq_skill;
  size_t num_prereq_feats;
  struct Feat** prereq_feat;
} Feat;

typedef struct
{
  Money cash;
  Money savings;
  const char* name;
  Level level[ Num_Classes ];
  Stat ability[ Num_Abilities ];
  size_t num_skills;
  Ranked_skill* skills;
  size_t num_feats;
  Feat** feats;
  Stat ip;
  Stat karma;
  Stat cp;
  Stat hp;
  Gender sex;
  Stat age;
  Stat size;
//  height;
//  weight;  
} Character;


static const Stat IP_PER_ABILITY=2;

#define Unk Num_Abilities

const Skill skill_list[] =
{
  /* Name                Ab     Po       Gr       Ac       At       En       De       Ex       Ra */
  { "WU: 1"            , Str, { Skill_C, Skill_C, Skill_X, Skill_C, Skill_N, Skill_X, Skill_X, Skill_X} },
  { "WU: 2"            , Dex, { Skill_X, Skill_C, Skill_C, Skill_C, Skill_N, Skill_X, Skill_X, Skill_C} },
  { "WU: 3"            , Unk, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_X, Skill_N, Skill_X, Skill_C} },
  
  { "Str: 1"           , Str, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  { "Str: 2"           , Str, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_X, Skill_C, Skill_X, Skill_C} },
  { "Str: 3"           , Str, { Skill_C, Skill_C, Skill_X, Skill_C, Skill_X, Skill_X, Skill_X, Skill_C} },
  
  { "Toughness"        , End, { Skill_C, Skill_C, Skill_X, Skill_C, Skill_N, Skill_N, Skill_N, Skill_X} },
  { "Resist F/H/T"     , End, { Skill_C, Skill_C, Skill_X, Skill_C, Skill_X, Skill_C, Skill_X, Skill_X} },
  { "Resist D/P"       , End, { Skill_C, Skill_C, Skill_X, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  
  { "Dex: 1"           , Dex, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  { "Dex: 2"           , Dex, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_X, Skill_C, Skill_C} },
  { "Dex: 3"           , Dex, { Skill_X, Skill_X, Skill_C, Skill_X, Skill_X, Skill_X, Skill_C, Skill_X} },
  
  { "Dodge"            , Agi, { Skill_X, Skill_C, Skill_C, Skill_C, Skill_C, Skill_X, Skill_C, Skill_C} },
  { "Roll"             , Agi, { Skill_X, Skill_C, Skill_C, Skill_C, Skill_C, Skill_X, Skill_C, Skill_C} },
  { "Agi: 3"           , Agi, { Skill_X, Skill_C, Skill_C, Skill_C, Skill_C, Skill_X, Skill_C, Skill_C} },
  
  { "Pre: 1"           , Pre, { Skill_X, Skill_X, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  { "Pre: 2"           , Pre, { Skill_C, Skill_C, Skill_X, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  { "Pre: 3"           , Pre, { Skill_X, Skill_X, Skill_X, Skill_X, Skill_C, Skill_C, Skill_C, Skill_X} },

  { "Resist H/I"       , Wil, { Skill_C, Skill_C, Skill_X, Skill_X, Skill_C, Skill_C, Skill_C, Skill_X} },
  { "Resist MAD/T"     , Wil, { Skill_C, Skill_C, Skill_X, Skill_C, Skill_X, Skill_C, Skill_X, Skill_C} },
  { "Resist MC/P"      , Wil, { Skill_X, Skill_C, Skill_X, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  
  { "Kno: 1"           , Kno, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  { "Kno: 2"           , Kno, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  { "Kno: 3"           , Kno, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  
  { "Notice Sight"     , Cun, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  { "Cun: 2"           , Cun, { Skill_C, Skill_X, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  { "Cun: 3"           , Cun, { Skill_X, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  
  { "Class: 1"         , Unk, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  { "Class: 2"         , Unk, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
  { "Class: 3"         , Unk, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
//  { "Class: 4"         , Unk, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
//  { "Class: 5"         , Unk, { Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C, Skill_C} },
};


const size_t skill_list_size = sizeof(skill_list)/sizeof(Skill);

const char* class2name( Class c )
{
  static const char* const array[ Num_Classes ] =
  {
    [Powerhouse]="Powerhouse",
    [Grunt]="Grunt",
    [Ace]="Ace",
    [Athlete]="Athlete",
    [Enchanter]="Enchanter",
    [Devotee]="Devotee",
    [Expert]="Expert",
    [Ranger]="Ranger"
  }; 

  const char* returnVal;
  
  if( c < Num_Classes )
  {
    returnVal = array[c];
  }
  else
  {
    returnVal=NULL;
  }
  
  return returnVal;
}

char ability2char( Ability a )
{
  
  static const char array[ Num_Abilities ] =
  {
    [Str]='S',
    [End]='E',
    [Dex]='D',
    [Agi]='A',
    [Pre]='P',
    [Wil]='W',
    [Kno]='K',
    [Cun]='C'
  };
  
  char returnVal;
  
  if( a < Num_Abilities )
  {
    returnVal=array[a];
  }
  else
  {
    returnVal='U';
  }
  
  return returnVal;
}

char gender2char( Gender g )
{
  
  static const char array[ Num_Genders ] =
  {
    'M',
    'F',
    'U'
  };
  
  char returnVal;
  
  if( g < Num_Genders )
  {
    returnVal=array[g];
  }
  else
  {
    returnVal='U';
  }
  
  return returnVal;
}


void clear_character( Character* u )
{
  Class c;
  Ability a;
  u->name = NULL;
  for( c=(Class)0; c<Num_Classes; c++ )
  {
    u->level[c]=0;
  }
  for( a=(Ability)0; a<Num_Abilities; a++)
  {
    u->ability[a]=0;
  }
  u->num_skills=0;
  u->skills=NULL;
  u->num_feats=0;
  u->feats=NULL;
  u->ip=0;
  u->cp=0;
  u->hp=0;
  u->karma=0;
  u->sex=OtherGender;
  u->age=0;
  u->size=0;
//  height;
//  weight;  
  u->cash= 0 DOLLARS;
  u->savings= 0 DOLLARS;
}


Stat max_skill ( const Character* u, const Skill* s )
{
  Stat total=2;
  Class c;
  
  for( c=(Class)0; c<Num_Classes; c++)
  {
    switch( s->avail[c] )
    {
      case( Skill_C ):
	total += (u->level[c] * 2);
	break;
	
      case( Skill_X ):
	total += (u->level[c] );
	break;
	

      default:
	// Do nothing
	break;
    }
  }
  
  return (total/2);
}

Stat max_ability ( const Character* u, Ability a )
{
  Stat total=300;
  Class c;
  
  for( c=(Class)0; c<Num_Classes; c++)
  {
    if( c==(Class)a )
    {
      total += (u->level[c] * 5);
    }
    else
    {
      total += (u->level[c] * 3);
    }
  }
  
  return (total/30);
}

void delete_character( Character* u )
{
  if( u!=NULL )
  {
    u->num_skills=0;
    free( u->skills );
    u->num_feats=0;
    free( u->feats );    
    free(u);
  }
}


Stat total_ip( const Character* u )
{
  Stat total=0;
  Ability a;
  size_t i;
  
  for( a=(Ability)0; a<Num_Abilities; a++)
  {
    total += u->ability[a];
  }
  
  total *= IP_PER_ABILITY;
  
  for( i=0; i<u->num_skills; i++ )
  {
    total += u->skills[i].rank;
  }
  
  return total;
}


Level total_level( const Character* u)
{
  Level total=0;
  Class c;
  
  for( c=(Class)0; c<Num_Classes; c++ )
  {
    total += u->level[c];
  }
  
  return total;
}

void print_str_online( FILE* out, size_t w, const char* s )
{
  char* buf=(char*)malloc(w+1);
  size_t i=0;
  size_t j;
  size_t len;
  
  if(buf == NULL ) 
  {
    for( i=0 ; i<w; i++)
    {
      fputc( 'X', out );
    }
  } 
  else if(s == NULL)
  {
    for( i=0 ; i<w; i++)
    {
      fputc( '_', out );
    }
  } 
  else
  {
    len=strlen( s );
    
    if( w > (len + 1) )
    {
      buf[i++]='_';
    }
    
    for( j = 0; (j < len) && (i < w); j++ )
    {
      if( isspace(s[j]) )
      {
	buf[i++]='_';
      }
      else
      {
	buf[i++]=s[j];
      }
    }
    
    while( i<w )
    {
      buf[i++]='_';
    }
    
    buf[w]='\0';
    
    fprintf ( out, "%s", buf );
    
  }

  free( buf );
}

void print_num_online( FILE* out, size_t w, int x )
{
  char* buf=(char*)malloc(w+1);
  size_t i;
  int len;

  if(buf == NULL )
  {
    for( i=0 ; i<w; i++)
    {
      fputc( 'X', out);
    }
  } 
  else
  {
    len = snprintf( buf, w+1, "%d", x);
    
    if( (len < 0) ||
        ((unsigned)len > w)
      )
    {
      for( i = 0; i<w;i++)
      {
	buf[i]='X';
      }
      buf[w]='\0';
    }
    else if( (unsigned)len < w )
    {
      buf[w]='\0';
      buf[--w]='_';
      while(len>0)
      {
	buf[--w]=buf[--len];
      }
    
      while( w>0 )
      {
	buf[--w]='_';
      }
    }

    fprintf ( out, "%s", buf );
    
  }
  
  free(buf);

}

void print_character( FILE * out, const Character* u )
{
  size_t i;
  Class c;
  if( u != NULL )
  {
    fprintf( out, "Name:" );
    print_str_online( out, 35, u->name );
    fprintf( out, "Player:_________________________________\nSex:%c_", gender2char(u->sex));
    fprintf( out, "Age:___Hght:____Wght:____Size:__Hair Color/Style:_________Eye Color:______\nHistory/Appearance:_____________________________________________________________\n________________________________________________________________________________\n");
  
    fprintf( out, "Total Level:" );
    
    print_num_online( out, 6, total_level(u) );

    fprintf( out, "Equipped Level:_______" );

    i=1;
    
    for( c=(Class)0; c<Num_Classes; c++)
    {
      if( u->level[c] != 0 )
      {
	fprintf( out, "Class:");
	print_str_online( out, 24, class2name(c) );
	fprintf( out, "Level:");
	print_num_online( out, 4, u->level[c]);
	if( ++i == 2)
	{
	  i=0;
	  fputc('\n', out );
	}
      }
    }
    
    if( i == 1)
    {
      fprintf( out, "Class:________________________Level:____\n");
    }

    fprintf( out, "Karma:" );
    print_num_online( out, 13, u->karma );
    fprintf( out, " HP:_______/" );
    print_num_online( out, 8, u->hp );
    fprintf( out, " IP:" );
    print_num_online( out, 8, total_ip(u) );
    fputc( '/', out );
    print_num_online( out, 8, u->ip );
    fprintf( out, " CP:" );
    print_num_online( out, 16, u->cp );
    
    fprintf( out, "\nStrength:" );
    print_num_online( out, 5, u->ability[Str] );
    fprintf( out, "/_____Endurance:");
    print_num_online( out, 4, u->ability[End] );
    fprintf( out, "/_____Dexterity:");
    print_num_online( out, 5, u->ability[Dex] );
    fprintf( out, "/_____Agility:");
    print_num_online( out, 5, u->ability[Agi] );
    fprintf( out, "/_____\nPresence:");
    print_num_online( out, 5, u->ability[Pre] );
    fprintf( out, "/_____Willpower:");
    print_num_online( out, 4, u->ability[Wil] );
    fprintf( out, "/_____Knowledge:");
    print_num_online( out, 5, u->ability[Kno] );
    fprintf( out, "/_____Cunning:");
    print_num_online( out, 5, u->ability[Cun] );
    fprintf( out, "/_____\nSkills              Max  Stat Misc Rank Skills              Max  Stat Misc Rank\n");


    for(i=0; i<u->num_skills; i++)
    {
      print_str_online( out, 19, u->skills[i].skill->name );
      fprintf( out, "|");
      print_num_online( out, 4, max_skill( u, u->skills[i].skill ) );
      fprintf( out, "|_%c__|____|", ability2char( u->skills[i].skill->ab ) );
      print_num_online( out, 4, u->skills[i].rank );
      
      if(i%2==1)
      {
	fputc('\n', out );
      }
      else
      {
	fprintf( out, "  ");
      }
	
    }
    
    if( (i % 2) == 1 )
    {
      fprintf( out, "___________________|____|____|____|____\n" );
    }

    fprintf( out, "Feats:\n" );

    for( i=0; i<u->num_feats; i++ )
    {
      print_str_online( out, 19, u->feats[i]->name );
      
      if( i % 4 == 3 )
      {
	fputc('\n', out );
      }
      else
      {
	fprintf( out, " ");
      }
    }
	
    if ( (i % 4) != 0 )
    {
      while( (i++ % 4) != 0 )
      {
	fprintf( out, "___________________ ");
      }

      fputc('\n', out );
    }
  }
}

Feat* get_feat( const char* name )
{
  Feat* f=(Feat*)malloc(sizeof(Feat));
  Ability a;
  
  if(f != NULL)
  {
    f->name=name;
    for( a=(Ability)0; a<Num_Abilities; a++ )
    {
      f->prereq_ability[a]=0;
    }
    f->num_prereq_skills=0;
    f->prereq_skill=NULL;
    f->num_prereq_feats=0;
    f->prereq_feat=NULL;
  }
  
  return f;
}


Character* get_character( Stat ip )
{
  Character* u=(Character*)malloc(sizeof(Character));
  Level level;
  Level l;
  Stat tmp;
  size_t i;
  size_t j;
  size_t class_feats;
  Ability a;
  Ability primary;
  Class c;
  
  if( u != NULL )
  {
    clear_character(u);
    
    level= (ip - 100) / 20;
    
    if(level>0)
    {
      l=(rand() % level) + 1;
      u->level[ (rand() % Num_Classes) ] += l;
      level-=l;
    }
    
    if(level>0)
    {
      l=(rand() % level) + 1;
      u->level[ (rand() % Num_Classes) ] += l;
      level-=l;
    }
    
    u->level[ (rand() % Num_Classes) ] += level;
    
    u->ip=ip;

    level = total_level( u );
    
    primary=(Ability)0;
    for( a=(Ability)1; a<Num_Abilities; a++)
    {
      if( u->level[(Class)a]>u->level[(Class)primary] )
      {
	primary=a;
      }
    }
    
    tmp = 50 + level / 2;
    ip -= (tmp * 2);
    
    while( tmp>0 )
    {
      a=rand() % (Num_Abilities + 1);
      if( a >= Num_Abilities )
      {
	a=primary;
      }
      
      if( u->ability[a] < max_ability( u, a) )
      {
	(u->ability[a])++;
	tmp--;
      }
    }
    
    //Add Skills
    u->skills=(Ranked_skill*)malloc( skill_list_size * sizeof(Ranked_skill) );
    if( u->skills!=NULL )
    {
      u->num_skills=skill_list_size;
      
      for( i=0; i<skill_list_size; i++)
      {
	if( (i % 2) == 0 )
	{
	  j=i/2;
	}
	else
	{
	  j= ( i + skill_list_size ) / 2;
	}
	u->skills[i].skill=&skill_list[j];
	u->skills[i].rank=0;
      }
      
      
      // Allocate Skill Points
      while( ip > 0 )
      {
	i=(size_t)rand() % u->num_skills;
	
	if( u->skills[i].rank < max_skill( u, u->skills[i].skill) )
	{
	  u->skills[i].rank++;
	  ip--;
	}
      }
    }
    
    u->feats=(Feat**)malloc(((size_t)level + 4)*sizeof(Feat*));
    if(u->feats!=NULL)
    {
      u->num_feats=(size_t)level+4;
      i=0;
      
      u->feats[i++]=get_feat( "Class" );
      u->feats[i++]=get_feat( "Class" );

      for( c=(Class)0; c<Num_Classes; c++)
      {
	class_feats=(2*((size_t)u->level[c]+1)/3);
	for( j=0; j<class_feats; j++)
	{
	  u->feats[i++]=get_feat( class2name(c) );
	}
      }
      
      while( i<u->num_feats )
      {
	u->feats[i++]=get_feat( "General" );
      }
    }
    
  u->cp=0;
  u->hp=level+u->ability[End]+u->skills[12].rank;
  u->karma=0;
  u->sex=OtherGender;
  u->age=0;
  u->size=0;
//  height;
//  weight;  
  u->cash= 0 DOLLARS;
  u->savings= 0 DOLLARS;
    
    
    
  }

  return u;
}



int main( int argc, char* argv[] )
{
  Character* myChar;
  Stat ip;
  char option='n';
  char line_in[256];
  srand( (unsigned)time(NULL) );
  FILE* out;
  char* ptr;
  
  do
  {
    if( argc > 1 )
    {
      ip = atoi( argv[1] );
      if( ip < 100 )
      {
	/* Assume it is actually a level */
	ip = 100 + ip * 20 + (rand() % 20);
      }
    }
    else
    {
      ip=100 + (rand() % 200);
    }   
    
    myChar=get_character( ip );
    
    print_character(stdout, myChar);
    
    printf( "Save/Next/Quit? " );
    
    if( fgets( line_in, 256, stdin ) == NULL )
    {
      option='q';
    }
    else
    {
      option=tolower(line_in[0]);
    }
    
    if( option == 's' )
    {
      do
      {
	printf( "\nName: " );
      }
      while ( fgets( line_in, 256, stdin ) == NULL );
      
      ptr=line_in;
      
      do
      {
	if(*ptr == '\n')
	{
	  *ptr = '\0';
	}
      }
      while( *ptr++ != '\0' );
      
      
      out = fopen( line_in, "w" );
      myChar->name=line_in;
      print_character( out, myChar );
      myChar->name=NULL;
      fclose(out);
    }
    
    delete_character(myChar);
  } 
  while( option != 'q' );
  return EXIT_SUCCESS;
}


