#include "straighten.h"
#include "plethysm.h"
#include <getopt.h>

int main(int argc, char **argv)
{
	srand(time(NULL));
	int c;
	int optint;
	uint32_t * shape;
	uint32_t shape_length;
	struct tableau * straighten = NULL;
	struct shape_data_c s_data = {};
	struct sstd_data_c sstd_data = {};
    struct sstd_data_c_options options = {};
    uint8_t max_filling_value = 0;
    uint8_t * content;
	uint32_t max_num_straighten = 0;
	uint32_t num_straighten = 0;
	int32_t plethysm_inner = 0;
	int32_t plethysm_outer = 0;
	int32_t coeff_min = 0;
	int32_t coeff_max = INT32_MAX;
	int32_t compact_mode = 0;
	int32_t num_random = 0;
	int32_t args_valid1 = -1;
	int32_t args_valid2 = -1;
	int32_t mode = 0;

	char * help = "Usage: straighten-tools [MODE]... [OPTIONS]...\n"
				  "Combinatorial tools for optimized straightening of fillings of Young diagrams and applications involving straightening.\n"
				  "\n"
				  "-s, --straighten SHAPE FILLING1 FILLING2 ...       straighten the supplied fillings of shape SHAPE and print the results\n"
				  "                                                   the expected format of filling is [box1,box2,...],coefficient\n"
				  "-f, --straighten-file SHAPE FILENAME               straighten the fillings in FILENAME and print the results\n"
				  "                                                   the expected format in FILENAME is one filling per line in format [box1,box2,...],coefficient\n"
				  "-r, --random-sstd SHAPE CONTENT                    print a random semistandard tableau of shape SHAPE and content CONTENT\n"
				  "-d, --random-dictionary SHAPE CONTENT              print a random dictionary tableau of shape SHAPE and content CONTENT\n"
				  "-p, --plethysm-coeff SHAPE OUTER INNER             print the plethysm coefficient for the shape SHAPE in Sym^OUTER Sym^INNER (V) where V is OUTER dimensional complex space\n"
				  "-a, --plethysm-coeff range OUTER INNER MIN MAX     print all shapes with plethsym coefficient at least MIN and at most MAX, if MAX is omitted then it is set to INT32_MAX\n"
				  "-g, --gen-isotypic-basis OUTER INNER               generate the combinatorial basis indexed by semistandard tableau for subspaces spanned by highest weight vectors\n"
				  "                                                   in  Sym^OUTER Sym^INNER (V) where V is OUTER dimensional complex space\n"
				  "-h, --help                                         display this help message\n"
				  "-v, --verbosity LEVEL                              set the message verbosity to display all messages of at least level LEVEL\n"
				  "                                                   level 0 is very verbose, level 1 is verbose, level 2 is (default) info, level 3 is warnings,\n"
				  "                                                   level 4 is errors, level 5 is fatal errors\n"
				  "-d, --display-mode MODE                            sets the display mode for messages to MODE\n"
				  "                                                   mode 0 does not display message type, mode 1 (default) displays message type initial, mode 2 displays verbose message type\n"
				  "-c, --compact-output                               all tableau will be output in compact mode\n"
				  "-l, --compact-no-log-output                        same as --compact-output but additionally verbosity will be set to level 4 (only errors displayed) and \n"
				  "                                                   display mode will be set to 0 (minimal)\n";
	while (1) {
		int option_index = 0;
		static struct option long_options[] = 
		{
		  {"straighten",     	  required_argument, NULL,  's' },
		  {"straighten-file",     required_argument, NULL,  'f' },
		  {"random-sstd",  		  required_argument, NULL,  'r'},
		  {"random-dictionary",   required_argument, NULL,  'd' },
		  {"plethysm-coeff",      required_argument, NULL,  'p' },
		  {"plethysm-coeff-range",required_argument, NULL,  'a' },
		  {"gen-isotypic-basis",  required_argument, NULL,  'g' },
		  {"help",                no_argument,       NULL,  'h' },
		  {"verbosity", 		  required_argument, NULL,  'v' },
		  {"display-mode", 		  required_argument, NULL,  'm' },
		  {"compact-output",      no_argument,       NULL,  'c' },
		  {"compact-no-log-output", no_argument,   NULL,  'l' },
		  {NULL,      			0,                 NULL,  0 }
		};

		c = getopt_long(argc, argv, "-s:rdmvcpl", long_options, &option_index);
		if (c == -1) {
			break;
		}

		switch (c) 
			{
			case 's':
			    args_valid1 = string_to_shape(&shape, &shape_length, optarg);
			    if(args_valid1 != -1) {
			    	construct_shape_data_c(&s_data, shape, shape_length);
			    }
			    mode = 'S';
			    break;
			case 'f':
			    args_valid1 = string_to_shape(&shape, &shape_length, optarg);
			    if(args_valid1 != -1) {
			    	construct_shape_data_c(&s_data, shape, shape_length);
			    }
			    mode = 'F';
			    break;
			case 'r':
				args_valid1 = string_to_shape(&shape, &shape_length, optarg);
			    if(args_valid1 != -1) {
			    	construct_shape_data_c(&s_data, shape, shape_length);
			    }
			    mode = 'R';
				break;
			case 'd':
				args_valid1 = string_to_shape(&shape, &shape_length, optarg);
			    if(args_valid1 != -1) {
			    	construct_shape_data_c(&s_data, shape, shape_length);
			    }
			    mode = 'D';
				break;
			case 'p':
				args_valid1 = string_to_shape(&shape, &shape_length, optarg);
			    if(args_valid1 != -1) {
			    	construct_shape_data_c(&s_data, shape, shape_length);
			    }
			    mode = 'P';
			    break;
			case 'h':
				printf("%s", help);
				break;
			case 'g':
				plethysm_outer = atoi(optarg);
				args_valid1 = 1;
				mode = 'G';
				break;
			case 'a':
				plethysm_outer = atoi(optarg);
				args_valid1 = 1;
				mode = 'A';
				break;
			case 'l':
				straighten_set_log_level(4);
				straighten_log(STRAIGHTEN_VVINFO, "Set log level to 4.");
				straighten_set_log_display(0);
				// there is no break here on purpose
			case 'c':
				compact_mode = 1;
				straighten_log(STRAIGHTEN_VVINFO, "Setting tableau display mode to compact.");
				break;
			case 'v':
				optint = atoi(optarg);
				straighten_set_log_level(optint);
				straighten_log(STRAIGHTEN_VVINFO, "Set log level to %d.", optint);
				break;
			case 'm':
				optint = atoi(optarg);
				straighten_set_log_display(optint);
				straighten_log(STRAIGHTEN_VVINFO, "Set log display mode to %d.", optint);
				break;
			case 1:
				if(mode == 'P' || mode == 'G' || mode == 'A') {
					if(args_valid1 != -1) {
						if(plethysm_outer == 0) {
			    			plethysm_outer = atoi(optarg);
			    		}
			    		else if (plethysm_inner == 0){
			    			plethysm_inner = atoi(optarg);
			    		}
			    		else if (coeff_min == 0) {
			    			coeff_min = atoi(optarg);
			    		}
			    		else {
			    			coeff_max = atoi(optarg);
			    		}
			    	}
				}
			    if(mode == 'S') {
			    	if(args_valid1 != -1) {
			    		args_valid2 = string_to_tableau(&straighten, &max_num_straighten, &num_straighten, optarg, &s_data);
			    		num_straighten++;
			    	}
			    }
			    if(mode == 'F') {
			    	if(args_valid1 != -1) {
			    		args_valid2 = file_to_tableau(&straighten, &max_num_straighten, &num_straighten, optarg, &s_data);
			    	}
			    	mode = 'S';
			    }
			    if(mode == 'R' || mode == 'D') {
			    	if(num_random == 0) {
				    	if(args_valid1 != -1) {
				    		args_valid2 = string_to_content(&content, &max_filling_value, optarg);
				    	}
				    	num_random = 1;
				    }
				    else {
				    	num_random = atoi(optarg);
				    	if(num_random == 0) {
				    		straighten_log(STRAIGHTEN_WARNING, "The third agument should be a positive integer.");
				    	}
				    }
			    }
			    break;
			}
	}

	switch(mode)
		{
			case 'A':
				if(plethysm_outer < 1 || plethysm_inner < 1) {
					straighten_log(STRAIGHTEN_FATAL, "The outer and inner coefficients must be positive integers.");
				}
				else if(coeff_min < 0 || coeff_max < 1) {
					straighten_log(STRAIGHTEN_FATAL, "The minimum coefficient must be non-negative and the maximum coefficients must be a positive integer.");
				}
				else {
					uint32_t * partitions;
    				uint32_t * plethysms;
					uint32_t num_partitions = load_foulkes(plethysm_outer, plethysm_inner, &partitions, &plethysms);

					char shape_str[128];
					int32_t s_index = 0;
					uint32_t num_found = 0;
    
    				for(int par = 0; par < num_partitions; par++) {
				    	if(plethysms[par] >= coeff_min && plethysms[par] <= coeff_max) {
				    		num_found++;
						    s_index = sprintf(&shape_str[0], "[");
						    for(int entry = 0; entry < plethysm_outer; entry++) {
						        if(partitions[(plethysm_outer * par) + entry] > 0) {
						            s_index += sprintf(&shape_str[s_index], "%u,", partitions[(plethysm_outer * par) + entry]);
						        }
						    }
						    s_index--; //removes the trailing comma
						    s_index += sprintf(&shape_str[s_index], "]");
				        	if(compact_mode) {
				        		s_printf("%s,%d\n", shape_str, plethysms[par]);
				        	}
				        	else {
				            	s_printf("The plethysm coefficient for shape %s is %d.\n", shape_str, plethysms[par]);
				            }
				        }
				    }
				    straighten_log(STRAIGHTEN_INFO, "Total of %u shapes match the constraints.", num_found);
				    free(partitions);
				    free(plethysms);
				    free(shape);
				}
				break;
			case 'G':
				if(plethysm_outer < 1 || plethysm_inner < 1) {
					straighten_log(STRAIGHTEN_FATAL, "The outer and inner coefficients must be positive integers.");
				}
				else {
					straighten_timing_macro(construct_isotypic_basis_all(plethysm_outer, plethysm_inner, 1), "Finding isotypic basis all");
    				straighten_log(STRAIGHTEN_INFO, "Successfully freed memory and exiting.");
				}
				break;
			case 'P':
				if(args_valid1 == -1) {
					straighten_log(STRAIGHTEN_FATAL, "There was an error in the shape argument.");
				}
				else if(plethysm_outer < 1 || plethysm_inner < 1) {
					straighten_log(STRAIGHTEN_FATAL, "The outer and inner coefficients must be positive integers.");
				}
				else if(s_data.num_boxes != (plethysm_outer * plethysm_inner) || s_data.shape_length > plethysm_outer) {
					straighten_log(STRAIGHTEN_FATAL, "The supplied shape has the incorrect number of boxes.");
				}
				else {
					uint32_t * partitions;
    				uint32_t * plethysms;
					uint32_t num_partitions = load_foulkes(plethysm_outer, plethysm_inner, &partitions, &plethysms);
    
    				int32_t equal;
				    for(int par = 0; par < num_partitions; par++) {
				    	equal = 1;
				    	for(int entry = 0; entry < s_data.shape_length; entry++) {
				    		if(partitions[(plethysm_outer * par) + entry] != s_data.shape[entry]) {
				    			equal = 0;
				    		}
				    	}
				        if(equal) {
				        	if(compact_mode) {
				        		s_printf("%d\n", plethysms[par]);
				        	}
				        	else {
				            	s_printf("The plethysm coefficient for shape %s is %d.\n", s_data.shape_string, plethysms[par]);
				            }
				        }
				    }

				    free(partitions);
				    free(plethysms);
				    free(shape);
				}
				break;
			case 'R':
				if(args_valid1 == -1) {
					straighten_log(STRAIGHTEN_FATAL, "There was an error in the shape argument. Can not generate random semistandard tableau.");
				}
				else if(args_valid2 == -1) {
					straighten_log(STRAIGHTEN_FATAL, "There was an error in the content argument. Can not generate random semistandard tableau.");
				}
				else {
					// set the options to just create the semistandard tableau
				    options.set_sstd = 1;
				    // if we are straightening then the semistandard tableau array must be sorted in the row word order
				    // however, if we are only interested in a random tableau then they do not need to be sorted
				    options.do_not_sort_sstd = 1;

				    // this constructs the sstd_data_c struct that contains the array of semistandard tableau and the D-basis
				    construct_sstd_data_c(&sstd_data, content, max_filling_value, &s_data, &options);

				    // print a random semistandard tableau
				    for(int ran = 0; ran < num_random; ran++) {
				    	print_tableau(random_sstd(&sstd_data), &s_data, 0, !compact_mode, !compact_mode, compact_mode);
				    }

				    // free the data
				    destruct_shape_data_c(&s_data);
				    destruct_sstd_data_c(&sstd_data);
				    free(shape);
				    free(content);
				    straighten_log(STRAIGHTEN_INFO, "Successfully freed memory and exiting.");
				}

				break;
			case 'D':
				if(args_valid1 == -1) {
					straighten_log(STRAIGHTEN_FATAL, "There was an error in the shape argument. Can not generate random dictionary tableau.");
				}
				else if(args_valid2 == -1) {
					straighten_log(STRAIGHTEN_FATAL, "There was an error in the content argument. Can not generate random dictionary tableau.");
				}
				else {
					// set the options to just create the semistandard tableau
				    options.set_dictionary = 1;

				    // this constructs the sstd_data_c struct that contains the array of semistandard tableau and the D-basis
				    construct_sstd_data_c(&sstd_data, content, max_filling_value, &s_data, &options);

				    // print a random semistandard tableau
				    for(int ran = 0; ran < num_random; ran++) {
				    	print_packed_tableau(random_dictionary_tableau(&sstd_data), &s_data, !compact_mode, !compact_mode, compact_mode);
				    }

				    // free the data
				    destruct_shape_data_c(&s_data);
				    destruct_sstd_data_c(&sstd_data);
					free(shape);
				    free(content);
				    straighten_log(STRAIGHTEN_INFO, "Successfully freed memory and exiting.");
				}

				break;
			case 'S':
				if(args_valid1 == -1) {
					straighten_log(STRAIGHTEN_FATAL, "There was an error in the shape argument. Can not straighten.");
				}
				else if(args_valid2 == -1) {
					straighten_log(STRAIGHTEN_FATAL, "There was an error in the filling to straighten argument. Can not straighten.");
				}
				else {
					s_printf("Straightening:\n");
					for(int tab = 0; tab < num_straighten; tab++) {
						print_tableau(&straighten[tab], &s_data, !compact_mode, !compact_mode, !compact_mode, compact_mode);
					}
					options.set_sstd = 1;
	    			options.set_Dbasis = 1;
	    			get_tableau_content(&content, &max_filling_value, straighten, &s_data);
	    			construct_sstd_data_c(&sstd_data, content, max_filling_value, &s_data, &options);

	    			int64_t * straighten_result = (int64_t*) calloc(sstd_data.num_sstd_tableau, sizeof(int64_t));

				    // straighten and then print the result
				    for(int tab = 0; tab < num_straighten; tab++) {
				    	straighten_sstd_basis_single_int64(&straighten[tab], &sstd_data, &s_data, straighten_result);
				    }				    

				    s_printf("Result:\n");
				    print_straighten_result_int64_t(straighten_result, &sstd_data, &s_data, 0, compact_mode);

				    destruct_shape_data_c(&s_data);
	    			destruct_sstd_data_c(&sstd_data);

				    free(straighten_result);
				    free(shape);
				    free(content);
				    free(straighten[0].entries);
				    free(straighten);
				    straighten_log(STRAIGHTEN_INFO, "Successfully freed memory and exiting.");
				}
				break;
		}

	return 0;
}