@ -4,14 +4,16 @@
 
		
	
		
			
				
 
		
	
		
			
				- export ( [ start_link / 0 ,  init / 1 ,  handle_cast / 2 ,  handle_call / 3 ] ) .  
		
	
		
			
				
 
		
	
		
			
				- export ( [ init_db / 2 ,  generate / 2 ] ) .  
		
	
		
			
				- include ( " erlk.hrl " ) .  
		
	
		
			
				
 
		
	
		
			
				- record ( state ,  { database_file  =  none ,  database  =  none ,  teams  =  [ ] ,  config  =  none } ) .  
		
	
		
			
				
 
		
	
		
			
				start_link ( )  - >  
		
	
		
			
				  gen_server : start_link ( { local ,  ? MODULE } ,  ? MODULE ,  [ ] ,  [ ] ) . 
 
		
	
		
			
				
 
		
	
		
			
				init ( [ ] )  - >  
		
	
		
			
				  rand : seed ( default ) , 
 
		
	
		
			
				  { ok ,  [ ] } . 
 
		
	
		
			
				  { ok ,  #state { } } . 
 
		
	
		
			
				
 
		
	
		
			
				duplicate ( 1 ,  Original ,  Constructed )  - >  lists : append ( Original ,  Constructed ) ;  
		
	
		
			
				duplicate ( N ,  Original ,  Constructed )  - >  duplicate ( N - 1 ,  Original ,  lists : append ( Original ,  Constructed ) ) .  
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
			
			@ -40,14 +42,7 @@ fill_padding_schedule(Matches, Teams) ->
 
		
	
		
			
				hash_teams_list ( Teams )  - >  
		
	
		
			
				  [  Y  | |  < < X : 4 > >  < =  crypto : hash ( sha ,  Teams ) ,  Y  < -  integer_to_list ( X , 16 ) ] . 
 
		
	
		
			
				
 
		
	
		
			
				generate ( Process ,  { DatabaseFile ,  Division ,  Round ,  Seed ,  Matches ,  Teams } )  - >  gen_server : call ( Process ,  { new_schedule ,  DatabaseFile ,  Division ,  Round ,  Seed ,  Matches ,  Teams } ) .  
		
	
		
			
				init_db ( Process ,  DatabaseFile )  - >  gen_server : call ( Process ,  { init_db ,  DatabaseFile } ) .  
		
	
		
			
				
 
		
	
		
			
				init_db ( File )  - >  
		
	
		
			
				  IsFile  =  filelib : is_regular ( File ) , 
 
		
	
		
			
				  if  IsFile  - >  file : delete ( File ) ; 
 
		
	
		
			
				     true  - >  true  end , 
 
		
	
		
			
				  { ok ,  Database }  =  sqlite3 : open ( anonymous ,  [ { file ,  File } ] ) , 
 
		
	
		
			
				init_db ( Database )  - >  
		
	
		
			
				  ok  =  sqlite3 : create_table ( Database ,  teams ,  [ { name ,  text ,  [ not_null ] } , 
 
		
	
		
			
				                                              { inspection ,  blob } ] , 
 
		
	
		
			
				                            [ { primary_key ,  [ name ] } ] ) , 
 
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
			
			@ -61,30 +56,30 @@ init_db(File) ->
 
		
	
		
			
				                                                { red_2 ,  text ,  [ not_null ] } , 
 
		
	
		
			
				                                                { blue_1 ,  text ,  [ not_null ] } , 
 
		
	
		
			
				                                                { blue_2 ,  text ,  [ not_null ] } ] , 
 
		
	
		
			
				                           [ { primary_key ,  [ division ,  round ,  size ,  teams ,  number ,  seed ] } , 
 
		
	
		
			
				                            { check ,  " round IN ('practice', 'qualification', 'elimination') " } , 
 
		
	
		
			
				                            { foreign_key ,  { [ blue_1 ] ,  teams ,  [ name ] ,  " ON DELETE RESTRICT " } } , 
 
		
	
		
			
				                            { foreign_key ,  { [ blue_2 ] ,  teams ,  [ name ] ,  " ON DELETE RESTRICT " } } , 
 
		
	
		
			
				                            { foreign_key ,  { [ red_1 ] ,  teams ,  [ name ] ,  " ON DELETE RESTRICT " } } , 
 
		
	
		
			
				                            { foreign_key ,  { [ red_2 ] ,  teams ,  [ name ] ,  " ON DELETE RESTRICT " } } ] ) , 
 
		
	
		
			
				                             [ { primary_key ,  [ division ,  round ,  size ,  teams ,  number ,  seed ] } , 
 
		
	
		
			
				                              { check ,  " round IN ('practice', 'qualification', 'elimination') " } , 
 
		
	
		
			
				                              { foreign_key ,  { [ blue_1 ] ,  teams ,  [ name ] ,  " ON DELETE RESTRICT " } } , 
 
		
	
		
			
				                              { foreign_key ,  { [ blue_2 ] ,  teams ,  [ name ] ,  " ON DELETE RESTRICT " } } , 
 
		
	
		
			
				                              { foreign_key ,  { [ red_1 ] ,  teams ,  [ name ] ,  " ON DELETE RESTRICT " } } , 
 
		
	
		
			
				                              { foreign_key ,  { [ red_2 ] ,  teams ,  [ name ] ,  " ON DELETE RESTRICT " } } ] ) , 
 
		
	
		
			
				  ok  =  sqlite3 : create_table ( Database ,  match_scores ,  [ { division ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                              { round ,  text ,  [ not_null ] } , 
 
		
	
		
			
				                                              { size ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                              { teams ,  text ,  [ not_null ] } , 
 
		
	
		
			
				                                              { number ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                              { seed ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                              { instance ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                              { score ,  blob ,  [ not_null ] } ] , 
 
		
	
		
			
				                           [ { primary_key ,  [ division ,  round ,  size ,  teams ,  number ,  seed ,  instance ] } , 
 
		
	
		
			
				                            { foreign_key ,  { [ division ,  round ,  size ,  teams ,  number ,  seed ] ,  matches ,  [ division ,  round ,  size ,  teams ,  number ,  seed ] ,  " ON DELETE CASCADE " } } ] ) , 
 
		
	
		
			
				                                                      { round ,  text ,  [ not_null ] } , 
 
		
	
		
			
				                                                      { size ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                                      { teams ,  text ,  [ not_null ] } , 
 
		
	
		
			
				                                                      { number ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                                      { seed ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                                      { instance ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                                      { score ,  blob ,  [ not_null ] } ] , 
 
		
	
		
			
				                             [ { primary_key ,  [ division ,  round ,  size ,  teams ,  number ,  seed ,  instance ] } , 
 
		
	
		
			
				                              { foreign_key ,  { [ division ,  round ,  size ,  teams ,  number ,  seed ] ,  matches ,  [ division ,  round ,  size ,  teams ,  number ,  seed ] ,  " ON DELETE CASCADE " } } ] ) , 
 
		
	
		
			
				  ok  =  sqlite3 : create_table ( Database ,  match_states ,  [ { division ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                              { round ,  text ,  [ not_null ] } , 
 
		
	
		
			
				                                              { size ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                              { teams ,  text ,  [ not_null ] } , 
 
		
	
		
			
				                                              { number ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                              { seed ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                              { time ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                              { state ,  blob ,  [ not_null ] } ] , 
 
		
	
		
			
				                                                      { round ,  text ,  [ not_null ] } , 
 
		
	
		
			
				                                                      { size ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                                      { teams ,  text ,  [ not_null ] } , 
 
		
	
		
			
				                                                      { number ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                                      { seed ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                                      { time ,  integer ,  [ not_null ] } , 
 
		
	
		
			
				                                                      { state ,  blob ,  [ not_null ] } ] , 
 
		
	
		
			
				                            [ { primary_key ,  [ division ,  round ,  size ,  teams ,  number ,  seed ,  time ] } , 
 
		
	
		
			
				                             { foreign_key ,  { [ division ,  round ,  size ,  teams ,  number ,  seed ] ,  matches ,  [ division ,  round ,  size ,  teams ,  number ,  seed ] ,  " ON DELETE CASCADE " } } ] ) , 
 
		
	
		
			
				
 
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
			
			@ -96,25 +91,43 @@ init_db(File) ->
 
		
	
		
			
				                                                      { score ,  blob ,  [ not_null ] } ] , 
 
		
	
		
			
				                            [ { primary_key ,  [ team ,  type ,  attempt ] } , 
 
		
	
		
			
				                             { foreign_key ,  { [ team ] ,  teams ,  [ name ] ,  " ON DELETE CASCADE " } } ] ) , 
 
		
	
		
			
				  sqlite3 : close ( Database ) . 
 
		
	
		
			
				
 
		
	
		
			
				write ( DatabaseFile ,  Division ,  Round ,  Seed ,  Matches ,  Teams )  - >  
		
	
		
			
				  MatchList  =  create_schedule ( Seed ,  Matches ,  Teams ) , 
 
		
	
		
			
				  ok . 
 
		
	
		
			
				
 
		
	
		
			
				delete_teams ( Database ,  Teams )  - >  
		
	
		
			
				  SQL  =  lists : append ( [ " BEGIN;  " , 
 
		
	
		
			
				         lists : append ( [ io_lib : format ( " DELETE FROM teams WHERE name =  ~p ;  " , 
 
		
	
		
			
				                                     [ Team ] )  | |  Team  < -  Teams ] ) , 
 
		
	
		
			
				         " COMMIT; " ] ) , 
 
		
	
		
			
				  sqlite3 : sql_exec_script ( Database ,  SQL ) . 
 
		
	
		
			
				
 
		
	
		
			
				add_teams ( Database ,  Teams )  - >  
		
	
		
			
				  SQL  =  lists : append ( [ " BEGIN;  " , 
 
		
	
		
			
				         lists : append ( [ io_lib : format ( " INSERT INTO teams(name) VALUES(' ~s ') ON CONFLICT(name) DO NOTHING;  " , 
 
		
	
		
			
				                                     [ Team ] )  | |  Team  < -  Teams ] ) , 
 
		
	
		
			
				         " COMMIT; " ] ) , 
 
		
	
		
			
				  sqlite3 : sql_exec_script ( Database ,  SQL ) . 
 
		
	
		
			
				
 
		
	
		
			
				first_error ( [ ] )  - >  ok ;  
		
	
		
			
				first_error ( [ ok  |  Rest ] )  - >  first_error ( Rest ) ;  
		
	
		
			
				first_error ( [ Error  |  _ ] )  - >  Error .  
		
	
		
			
				
 
		
	
		
			
				delete_matches ( Database ,  Division ,  Size ,  Seed ,  TeamsHash ,  Round )  - >  
		
	
		
			
				  SQL  =  io_lib : format ( " DELETE FROM matches WHERE division =  ~p  AND size =  ~p  AND seed =  ~p  AND teams = ' ~s ' AND round = ' ~p ' " ,  [ Division ,  Size ,  Seed ,  TeamsHash ,  Round ] ) , 
 
		
	
		
			
				  sqlite3 : sql_exec_script ( Database ,  SQL ) . 
 
		
	
		
			
				
 
		
	
		
			
				write_matches ( Database ,  Division ,  Size ,  Seed ,  TeamsHash ,  Round ,  MatchTeams )  - >  
		
	
		
			
				  Schedule  =  [ [ { division ,  Division } , 
 
		
	
		
			
				               { round ,  atom_to_list ( Round ) } , 
 
		
	
		
			
				               { size ,  Matches } , 
 
		
	
		
			
				               { teams ,  hash_teams_list ( Teams ) } , 
 
		
	
		
			
				               { size ,  Size } , 
 
		
	
		
			
				               { teams ,  TeamsHash } , 
 
		
	
		
			
				               { number ,  N } , 
 
		
	
		
			
				               { seed ,  Seed } , 
 
		
	
		
			
				               { blue_1 ,  B1 } , 
 
		
	
		
			
				               { blue_2 ,  B2 } , 
 
		
	
		
			
				               { red_1 ,  R1 } , 
 
		
	
		
			
				               { red_2 ,  R2 } ]  | |  { N ,  [ B1 ,  B2 ,  R1 ,  R2 ] }  < -  lists : enumerate ( MatchList ) ] , 
 
		
	
		
			
				  { ok ,  Database }  =  sqlite3 : open ( anonymous ,  [ { file ,  DatabaseFile } ] ) , 
 
		
	
		
			
				               { red_2 ,  R2 } ]  | |  { N ,  [ B1 ,  B2 ,  R1 ,  R2 ] }  < -  lists : enumerate ( MatchTeams ) ] , 
 
		
	
		
			
				  [ ok  |  Resp ]  =  sqlite3 : write_many ( Database ,  matches ,  Schedule ) , 
 
		
	
		
			
				  ok  =  lists : last ( Resp ) , 
 
		
	
		
			
				  ok  =  sqlite3 : close ( Database ) . 
 
		
	
		
			
				
 
		
	
		
			
				  lists : last ( Resp ) . 
 
		
	
		
			
				
 
		
	
		
			
				create_schedule ( Seed ,  Matches ,  Teams )  - >  
		
	
		
			
				  TeamsRepeated  =  duplicate ( Matches ,  Teams ) , 
 
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
			
			@ -139,9 +152,92 @@ pick_matches(Teams, Matches) ->
 
		
	
		
			
				
 
		
	
		
			
				pick_matches ( Teams )  - >  pick_matches ( Teams ,  [ ] ) .  
		
	
		
			
				
 
		
	
		
			
				handle_cast ( _ ,  State )  - >  { noreply ,  State } .  
		
	
		
			
				handle_call ( { new_db ,  DatabaseFile } ,  _ ,  State )  - >  
		
	
		
			
				  ok  =  if  State #state.database  =:=  none  - >  ok ; 
 
		
	
		
			
				          true  - >  sqlite3 : close ( State #state.database ) 
 
		
	
		
			
				       end , 
 
		
	
		
			
				  Exists  =  filelib : is_regular ( DatabaseFile ) , 
 
		
	
		
			
				  ok  =  if  Exists  - >  file : delete ( DatabaseFile ) ; 
 
		
	
		
			
				          true  - >  ok 
 
		
	
		
			
				       end , 
 
		
	
		
			
				  { ok ,  Database }  =  sqlite3 : open ( schedule_db ,  [ { file ,  DatabaseFile } ] ) , 
 
		
	
		
			
				  ok  =  sqlite3 : sql_exec ( Database ,  " PRAGMA foreign_keys = ON; " ) , 
 
		
	
		
			
				  { reply ,  init_db ( Database ) ,  State #state { database_file  =  DatabaseFile ,  database  =  Database } } ; 
 
		
	
		
			
				handle_call ( { load_db ,  DatabaseFile } ,  _ ,  State )  - >  
		
	
		
			
				  true  =  filelib : is_regular ( DatabaseFile ) , 
 
		
	
		
			
				  { ok ,  Database }  =  if  DatabaseFile  ==  State #state.database_file  - > 
 
		
	
		
			
				                        { ok ,  State #state.database } ; 
 
		
	
		
			
				                      true  - > 
 
		
	
		
			
				                        ok  =  if  State #state.database  =:=  none  - >  ok ; 
 
		
	
		
			
				                                true  - >  sqlite3 : close ( State #state.database ) 
 
		
	
		
			
				                             end , 
 
		
	
		
			
				                        { ok ,  DB }  =  sqlite3 : open ( schedule_db ,  [ { file ,  DatabaseFile } ] ) , 
 
		
	
		
			
				                        { sqlite3 : sql_exec ( DB ,  " PRAGMA foreign_keys = ON; " ) ,  DB } 
 
		
	
		
			
				                   end , 
 
		
	
		
			
				  { reply ,  ok ,  State #state { database_file  =  DatabaseFile ,  database  =  Database } } . 
 
		
	
		
			
				
 
		
	
		
			
				handle_cast ( { delete_teams ,  Removed } ,  State )  - >  
		
	
		
			
				  Teams  =  lists : filter ( fun ( X )  - >  lists : member ( X ,  Removed )  =:=  false  end ,  State #state.teams ) , 
 
		
	
		
			
				  ok  =  first_error ( delete_teams ( State #state.database ,  Removed ) ) , 
 
		
	
		
			
				  { noreply ,  State #state { teams  =  Teams } } ; 
 
		
	
		
			
				handle_cast ( { add_teams ,  Teams } ,  State )  - >  
		
	
		
			
				  ok  =  first_error ( add_teams ( State #state.database ,  Teams ) ) , 
 
		
	
		
			
				  { noreply ,  State #state { teams  =  Teams } } ; 
 
		
	
		
			
				handle_cast ( { new_schedule ,  Division ,  Size ,  Seed ,  Teams ,  Round } ,  State )  - >  
		
	
		
			
				  MatchTeams  =  create_schedule ( Seed ,  Size ,  Teams ) , 
 
		
	
		
			
				  ok  =  write_matches ( State #state.database ,  Division ,  Size ,  Seed ,  hash_teams_list ( Teams ) ,  Round ,  MatchTeams ) , 
 
		
	
		
			
				  { noreply ,  State } ; 
 
		
	
		
			
				handle_cast ( { delete_schedule ,  Division ,  Size ,  Seed ,  TeamsHash ,  Round } ,  State )  - >  
		
	
		
			
				  ok  =  first_error ( delete_matches ( State #state.database ,  Division ,  Size ,  Seed ,  TeamsHash ,  Round ) ) , 
 
		
	
		
			
				  { noreply ,  State } . 
 
		
	
		
			
				
 
		
	
		
			
				get_matches_from_db ( Database ,  Division ,  Round ,  Seed ,  Size ,  TeamsHash )  - >  
		
	
		
			
				  if  Size  ==  0  - >  [ ] ; 
 
		
	
		
			
				     Size  >  0  - >  SQL  =  io_lib : format ( " SELECT number, blue_1, blue_2, red_1, red_2 FROM 
 
		
	
		
			
				                                     matches  WHERE 
 
		
	
		
			
				                                     division  =  '~p'  AND 
 
		
	
		
			
				                                     round  =  '~p'  AND 
 
		
	
		
			
				                                     size  =  '~p'  AND 
 
		
	
		
			
				                                     seed  =  '~p'  AND 
 
		
	
		
			
				                                     teams  =  '~s' ; " , 
 
		
	
		
			
				                                     [ Division ,  Round ,  Size ,  Seed ,  TeamsHash ] ) , 
 
		
	
		
			
				                 [ { columns ,  Columns } ,  { rows ,  Rows } ]  =  sqlite3 : sql_exec ( Database ,  SQL ) , 
 
		
	
		
			
				                 [ ] 
 
		
	
		
			
				  end . 
 
		
	
		
			
				
 
		
	
		
			
				handle_call ( { init_db ,  DatabaseFile } ,  _ ,  State )  - >  
		
	
		
			
				  { reply ,  init_db ( DatabaseFile ) ,  State } ; 
 
		
	
		
			
				handle_call ( { new_schedule ,  DatabaseFile ,  Division ,  Round ,  Seed ,  Matches ,  Teams } ,  _ ,  State )  - >  
		
	
		
			
				  { reply ,  write ( DatabaseFile ,  Division ,  Round ,  Seed ,  Matches ,  Teams ) ,  State } . 
 
		
	
		
			
				parse_division ( Division )  - >  
		
	
		
			
				  TeamsBinary  =  maps : get ( < < " teams " > > ,  Division ) , 
 
		
	
		
			
				  TeamsList  =  [ binary_to_list ( X )  | |  X  < -  TeamsBinary ] , 
 
		
	
		
			
				  TeamsHash  =  schedule : hash_teams_list ( TeamsList ) , 
 
		
	
		
			
				
 
		
	
		
			
				  PracticeRounds  =  maps : get ( < < " practice_rounds " > > ,  Division ) , 
 
		
	
		
			
				  PracticeSeed  =  maps : get ( < < " practice_seed " > > ,  Division ) , 
 
		
	
		
			
				
 
		
	
		
			
				  QualificationRounds  =  maps : get ( < < " qualification_rounds " > > ,  Division ) , 
 
		
	
		
			
				  QualificationSeed  =  maps : get ( < < " qualification_seed " > > ,  Division ) , 
 
		
	
		
			
				
 
		
	
		
			
				  #division_config { 
 
		
	
		
			
				     fields  =  [ binary_to_list ( X )  | |  X  < -  maps : get ( < < " fields " > > ,  Division ) ] , 
 
		
	
		
			
				     elimination_alliances  =  maps : get ( < < " elimination_alliances " > > ,  Division ) , 
 
		
	
		
			
				     practice_rounds  =  PracticeRounds , 
 
		
	
		
			
				     practice_seed  =  PracticeSeed , 
 
		
	
		
			
				     qualification_rounds  =  QualificationRounds , 
 
		
	
		
			
				     qualification_seed  =  QualificationSeed , 
 
		
	
		
			
				     teams  =  TeamsList , 
 
		
	
		
			
				     teams_hash  =  TeamsHash 
 
		
	
		
			
				    } . 
 
		
	
		
			
				
 
		
	
		
			
				parse_event_config ( ConfigJSON )  - >  
		
	
		
			
				  Config  =  jsone : decode ( list_to_binary ( ConfigJSON ) ) , 
 
		
	
		
			
				  Divisions  =  lists : map ( fun  parse_division / 1 ,  maps : get ( < < " divisions " > > ,  Config ) ) , 
 
		
	
		
			
				  TestingFields  =  [ binary_to_list ( X )  | |  X  < -  maps : get ( < < " testing_fields " > > ,  Config ) ] , 
 
		
	
		
			
				  SkillsFields  =  [ binary_to_list ( X )  | |  X  < -  maps : get ( < < " skills_fields " > > ,  Config ) ] , 
 
		
	
		
			
				  FinalsFields  =  [ binary_to_list ( X )  | |  X  < -  maps : get ( < < " finals_fields " > > ,  Config ) ] , 
 
		
	
		
			
				
 
		
	
		
			
				  #event_config { 
 
		
	
		
			
				     database_file  =  binary_to_list ( maps : get ( < < " database " > > ,  Config ) ) , 
 
		
	
		
			
				     finals_fields  =  FinalsFields , 
 
		
	
		
			
				     skills_fields  =  SkillsFields , 
 
		
	
		
			
				     testing_fields  =  TestingFields , 
 
		
	
		
			
				     divisions  =  Divisions 
 
		
	
		
			
				    } .