#!/usr/bin/perl -w
# -----------------------------------------------------------------------------

use strict;

$| = 1;

my $myself = $0;
$myself =~ s|^.*/||;

die "$0: must run from top-level gnumeric directory.\n"
    unless -r "gnumeric-config.h.in";
my $dst = "src/tools/solver/lp_solve";

my $lpdir = $ARGV[0];
die "$0: must specify lpsolve directory on command line.\n"
    unless -d $lpdir;

-d $dst or mkdir $dst or
    die "$0: cannot mkdir $dst: $!\n";

my @files =
    (# Header files
     "shared/commonlib.h",
     "lp_types.h",
     "lp_Hash.h",
     "lp_utils.h",
     "lp_matrix.h",
     "lp_SOS.h",
     "lp_mipbb.h",
     "lp_lib.h",
     "lp_crash.h",
     "lp_MPS.h",
     # "lp_wlp.h",
     "lp_report.h",
     "lp_scale.h",
     "lp_presolve.h",
     "lp_pricePSE.h",
     "lp_price.h",
     "lp_simplex.h",
     "lp_MDO.h",
     "bfp/bfp_LUSOL/LUSOL/lusol.h",
     "bfp/bfp_LUSOL/lp_LUSOL.h",
     "bfp/lp_BFP.h",
     "bfp/bfp_LUSOL/LUSOL/myblas.h",
     # "shared/mmio.h",
     # C files
     "bfp/lp_BFP1.c",
     "bfp/lp_BFP2.c",
     "bfp/bfp_LUSOL/lp_LUSOL.c",
     "bfp/bfp_LUSOL/LUSOL/lusol.c",
     "bfp/bfp_LUSOL/LUSOL/lusol2.c",
     "bfp/bfp_LUSOL/LUSOL/lusol6l0.c",
     "bfp/bfp_LUSOL/LUSOL/lusol6a.c",
     "bfp/bfp_LUSOL/LUSOL/lusol1.c",
     "bfp/bfp_LUSOL/LUSOL/lusol7a.c",
     "bfp/bfp_LUSOL/LUSOL/lusol8a.c",
     "colamd/colamd.h",
     "colamd/colamd.c",
     # "ini.c",
     "shared/commonlib.c",
     # "shared/mmio.c",
     "shared/myblas.c",
     "lp_crash.c",
     "lp_Hash.c",
     "lp_lib.c",
     "lp_matrix.c",
     "lp_MDO.c",
     "lp_mipbb.c",
     # "lp_MPS.c",
     # "lp_params.c",
     "lp_presolve.c",
     "lp_price.c",
     "lp_pricePSE.c",
     "lp_report.c",
     "lp_scale.c",
     "lp_simplex.c",
     "lp_SOS.c",
     "lp_utils.c",
     # "lp_wlp.c",
     );

my $headers = '';

my $dstfile = "$dst/lp_solve.c";
open (DST, ">$dstfile.new") or
    die "$0: Cannot write $dstfile.new: $!\n";
print STDERR "Creating $dstfile...";
print DST "/* Generated by $myself -- do not edit! */\n";
print DST "#include <numbers.h>\n";
print DST "#include <glib.h>\n";
print DST "#include <math.h>\n";
print DST "#include <string.h>\n";
print DST "#include <stdio.h>\n";
print DST "#include <stdlib.h>\n";
print DST "#include <errno.h>\n";
print DST "#include <time.h>\n";
print DST "#include <sys/timeb.h>\n\n";

print DST "#define LoadInverseLib FALSE\n";
print DST "#define LoadLanguageLib FALSE\n\n";
print DST "#undef calloc\n";
print DST "#define calloc(_a,_b) g_malloc0 ((_a)*(_b))\n";

foreach my $file (@files) {
    my @defines = ();
    open (SRC, "<$lpdir/$file") or
	die "$0: Cannot read $lpdir/$file: $!\n";
    print DST "/* ", "-" x 73, " */\n";
    print DST "/* Imported $file */\n\n";
    while (<SRC>) {
	next if /^\s*\#\s*include\s*(".*"|<.*>)/;

	next if /^\s*\#\s*define\s+(TRUE|FALSE|MIN|MAX|LoadableBlasLib)\b/;

	s/^(\s*\#\s*define\s+libBLAS\b).*$/$1 0/;
	s/^(\s*\#\s*define\s+STATIC\b).*$/$1 static/;

	if (/^\#\s*ifndef\s+(__BORLANDC__|LLONG|COUNTER|REAL|MAXU?INT(32|64)|MYBOOL|NULL|(BFP|XLI)_CALLMODEL)\b/ ... /^\#\s*endif/) {
	    next;
	}
	if (/^\#\s*ifdef\s+(RoleIsExternalInvEngine|WIN32)\b/ ... /^\#\s*endif/) {
	    next;
	}

	# Crappy intentation forces us to do this.
	next if /^\#\s*ifndef\s+(__WINAPI|CMP_CALLMODEL|BLAS_CALLMODEL)\b/ ... /^$/;

	s/\b(XLI|BFP|BLAS|CMP)_CALLMODEL\s*\b//;
	s/\b__BFP_EXPORT_TYPE\s*\b//g;
	s/\b__EXPORT_TYPE\s*\b//g;
	s/\b__WINAPI\s*\b//;
	s/\b__VACALL\s*\b//;
	s/\bMYBOOL\b/gboolean/g;
	s/\bREAL\b/gnm_float/g;
	s/\bLLONG\b/gint64/g;
	s/\bCOUNTER\b/gint64/g;
	s/\bMAXINT32\b/G_MAXINT32/g;
	s/\bMAXUINT32\b/G_MAXUINT32/g;
	s/\bMAXINT64\b/G_MAXINT64/g;
	s/\bMAXUINT64\b/G_MAXUINT64/g;

	s/\bNZERO\b/MY_NZERO/g; # Avoid clash with /usr/include/limits.h
	s/\bgcd\b/mygcd/g; # Avoid clash with mathfunc.c

	# The API
	s/\bset_(int|timeout|lowbo|upbo|constr_type|maxim|minim|rh|mat|scalelimit)\b/lp_solve_set_$1/g;
	s/\bsolve\b/lp_solve_solve/g;
	s/\b((make|delete|print)_lp)\b/lp_solve_$1/g;
	s/\bget_(total_iter)\b/lp_solve_get_$1/g;
	s/\bget_var_dualresult\b/lp_solve_get_dual/g;
	s/\bget_var_primalresult\b/lp_solve_get_primal/g;
	s/\bget_Nrows\b/lp_solve_get_nrows/g;

	s/\b(malloc)\b/g_malloc/g;
	s/\b(free\s*\()\b/g_$1/g;
	s/\b(realloc)\b/g_realloc/g;
	s/\b(isspace|tolower|toupper)\b/g_ascii_$1/g;

	if (/\bstruct\s+timeb\b/ ... /return/) {
	    next unless /return/;
	    $_ = ("  GTimeVal tim;\n" .
		  "  g_get_current_time (\&tim);\n" .
		  "  return tim.tv_sec + tim.tv_usec / 1e6;\n");
	}

	s/\(([^()]+)\*\s*\)\s*calloc\s*\(([^,]+),\s*sizeof\s*\([^()]*(\([^()]*\))?\)\s*\)/g_new0 ($1, $2)/;

	s/\bchar\s+\*format\b/const char *format/;
	if (!/\bconst\b/ && /^void\s+blockWrite/) {
	    s/\bchar\s+\*label\b/const char *label/;
	}
	s/^(char\s*\*\s*LUSOL_informstr\b)/const $1/;
	s/\bchar\s+\*method\b/const char *method/;
	if (/\b(bfp_name|BFPchar|LUSOL_pivotLabel|get_str_piv_rule|get_statustext(_func)?|get_str_constr_class|get_str_constr_type|REPORT_modelinfo|REPORT_constraintinfo|stallMonitor_create|write_lpcomment)\b/) {
	    s/\bchar\s+\*/const char */;
	}
	if (/\bstatic\b.*\b(pivotText|informText)\b/) {
	    s/static/static const/;
	}
	if (/^struct _values\b/ ... /^\}/) {
	    s/\bchar\s+\*/const char */;
	}
	if (/^struct _functions\b/ ... /^\}/) {
	    s/\bchar\s+\*/const char */;
	}

	s|^/\* INLINE \*/\s+||;
	s/^PUBLIC\s+//;
	s/^PRIVATE\s+/static /;

	if (/^(INLINE\s+|const\s+)*(char|void|int|long|gint64|gboolean|double|gnm_float|LUSOLmat|lprec|LUSOLrec)\b(\s|\*)*(bfp_LUSOL(setcolumn|identity|factorize|tighten)|relationChar|HUP|HDOWN|HINSERT|HCHANGE|HDELETE|HBUILD|LU[167][a-zA-Z0-9_]+|QS_(validate|updatelink|sort|finish)|clean_realloc|LUSOL_realloc_a|LUSOL_expand_a|LUSOL_realloc_r|LUSOL_realloc_c|invert|debug_print|debug_print_solution|debug_print_bounds|mod|mygcd|del_splitvars|is_int|is_binary|is_SOS_var|is_action|is_add_rowmode|is_anti_degen|is_bb_mode|is_break_at_first|is_chsign|is_constr_type|is_debug|is_feasible|is_fixedvar|is_infinite|is_integerscaling|is_lag_trace|is_maxim|is_nativeBFP|is_nativeBLAS|is_nativeXLI|compRedundant|compSparsity|compAggregate|CurtisReidMeasure|CurtisReidScales|LU8RPC|LUSOL_assign|LUSOL_btran|LUSOL_clear|LUSOL_create|LUSOL_dump|LUSOL_factorize|LUSOL_findColumnPosition|LUSOL_free|LUSOL_ftran|LUSOL_informstr|LUSOL_loadColumn|LUSOL_matcreate|LUSOL_matfree|LUSOL_pivotLabel|LUSOL_replaceColumn|LUSOL_report|LUSOL_setpivotmodel|LUSOL_sizeto|LUSOL_tightenpivot|LUSOL_timer|LUSOL_vecdensity|MIP_stepOF|QS_addfirst|QS_append|QS_delete|QS_execute|QS_insert|QS_replace|QS_swap|REPORT_constraintinfo|REPORT_constraints|REPORT_debugdump|REPORT_duals|REPORT_extended|REPORT_lp|REPORT_mat_mmsave|REPORT_modelinfo|REPORT_objective|REPORT_scales|REPORT_solution|REPORT_tableau|SOS_can_activate|SOS_fix_list|SOS_fix_unmarked|SOS_get_candidates|SOS_get_type|SOS_infeasible|SOS_is_GUB|SOS_is_active|SOS_is_feasible|SOS_is_full|SOS_is_marked|SOS_is_member|SOS_is_member_of_type|SOS_is_satisfied|SOS_member_count|SOS_member_delete|SOS_member_index|SOS_memberships|SOS_set_GUB|SOS_set_marked|SOS_unmark|SOS_usecount|add_SOS|add_column|add_columnex|add_constraint|add_constraintex|add_lag_con|bfp_btran_double|bfp_btran_normal|bfp_canresetbasis|bfp_colcount|bfp_compatible|bfp_createMDO|bfp_efficiency|bfp_factorize|bfp_findredundant|bfp_finishfactorization|bfp_finishupdate|bfp_free|bfp_ftran_normal|bfp_ftran_prepare|bfp_implicitslack|bfp_indexbase|bfp_init|bfp_isSetI|bfp_memallocated|bfp_mustrefactorize|bfp_name|bfp_nonzeros|bfp_pivotRHS|bfp_pivotalloc|bfp_pivotcount|bfp_pivotmax|bfp_pivotvector|bfp_preparefactorization|bfp_prepareupdate|bfp_refactcount|bfp_resize|bfp_restart|bfp_rowoffset|bfp_status|bfp_updaterefactstats|blockWriteAMAT|blockWriteBMAT|blockWriteBOOL|blockWriteINT|blockWriteLREAL|blockWriteREAL|callcount|clear_action|cloneINT|cloneMYBOOL|cloneREAL|colamd|colamd_recommended|colamd_report|colamd_set_defaults|column_in_lp|comp_bits|compareBoundFlipVar|compareCHAR|compareINT|compareImprovementQS|compareImprovementVar|compareREAL|compareSubstitutionQS|compareSubstitutionVar|daxpy|dcopy|ddot|ddrand|default_basis|del_column|del_constraint|dload|dnormi|dscal|dswap|dualize_lp|explain|findIndex|findIndexEx|free_lp|getMDO|get_Lrows|get_Ncolumns|get_Norig_columns|get_Norig_rows|get_OF_active|get_anti_degen|get_basis|get_basiscolumn|get_basiscrash|get_bb_depthlimit|get_bb_floorfirst|get_bb_rule|get_bounds|get_bounds_tighter|get_break_at_value|get_col_name|get_column|get_columnex|get_constr_type|get_constr_value|get_constraints|get_dual_solution|get_epsb|get_epsd|get_epsel|get_epsint|get_epsperturb|get_epspivot|get_improve|get_infinite|get_lambda|get_lowbo|get_lp_index|get_lp_name|get_mat|get_mat_byindex|get_max_level|get_maxpivot|get_mip_gap|get_multiprice|get_nameindex|get_negrange|get_nonzeros|get_obj_bound|get_objective|get_orig_index|get_origcol_name|get_origrow_name|get_partialprice|get_piv_rule|get_pivoting|get_presolve|get_presolveloops|get_primal_solution|get_print_sol|get_pseudocosts|get_ptr_constraints|get_ptr_dual_solution|get_ptr_lambda|get_ptr_primal_solution|get_ptr_sensitivity_obj|get_ptr_sensitivity_objex|get_ptr_sensitivity_rhs|get_ptr_variables|get_rh|get_rh_lower|get_rh_range|get_rh_upper|get_row|get_row_name|get_rowex|get_scalelimit|get_scaling|get_sensitivity_obj|get_sensitivity_objex|get_sensitivity_rhs|get_simplextype|get_solutioncount|get_solutionlimit|get_status|get_statustext|get_timeout|get_total_nodes|get_upbo|get_var_branch|get_var_priority|get_variables|get_verbose|get_working_objective|has_BFP|has_XLI|hpsort|hpsortex|idamax|init_BLAS|is_negative|is_obj_in_basis|is_piv_mode|is_piv_rule|is_presolve|is_scalemode|is_scaletype|is_semicont|is_splitvar|is_trace|is_unbounded|is_use_names|load_BLAS|make_lag|mat_get_data|mat_set_rowmap|mdo_calloc|mdo_free|modifyOF1|mustinitBLAS|my_daxpy|my_dcopy|my_ddot|my_dload|my_dnormi|my_dscal|my_dswap|my_idamax|obtain_column|posmat|presolve_getcolumnEQ|print_L0|print_constraints|print_debugdump|print_duals|print_objective|print_scales|print_solution|print_str|print_tableau|printmatSQ|printmatUT|printvec|put_abortfunc|put_bb_branchfunc|put_bb_nodefunc|put_logfunc|put_msgfunc|randomdens|randomseed|rename_var|report|reset_basis|reset_params|resize_lp|set_BFP|set_XLI|set_action|set_add_rowmode|set_anti_degen|set_basis|set_basiscrash|set_basisvar|set_bb_depthlimit|set_bb_floorfirst|set_bb_rule|set_binary|set_bounds|set_bounds_tighter|set_break_at_first|set_break_at_value|set_callbacks|set_col_name|set_column|set_columnex|set_debug|set_epsb|set_epsd|set_epsel|set_epsint|set_epslevel|set_epsperturb|set_epspivot|set_improve|set_infinite|set_lag_trace|set_lp_name|set_maxpivot|set_mip_gap|set_multiprice|set_negrange|set_obj|set_obj_bound|set_obj_fn|set_obj_fnex|set_obj_in_basis|set_outputfile|set_outputstream|set_partialprice|set_pivoting|set_preferdual|set_presolve|set_print_sol|set_pseudocosts|set_rh_lower|set_rh_range|set_rh_upper|set_rh_vec|set_row|set_row_name|set_rowex|set_scaling|set_semicont|set_sense|set_simplextype|set_solutionlimit|set_trace|set_unbounded|set_use_names|set_var_branch|set_var_weights|set_verbose|sortByINT|sortByREAL|sortREALByINT|str_add_column|str_add_constraint|str_add_lag_con|str_set_obj_fn|str_set_rh_vec|submat|symamd|symamd_report|time_elapsed|undoscale|unload_BLAS|unscale|userabort|timeNow)\b/) {
	    s/^/static /;
	    s/^static static/static /i;
	}

	if (/^(const\s+)?(void|int|gboolean|gnm_float|char|LREAL)\b(\s|\*)*\(bfp_[a-zA-Z0-9_]+\).*;$/) {
	    s/^/static /;
	}

	if (/^BLAS_([a-z]+)_func\s+\*BLAS_\1;$/) {
	    s/^/static /;
	}


	next if /\b(copy_lp|read_XLI|write_XLI|read_params|write_params|read_LPhandle|read_basis|write_basis|write_freemps|write_freeMPS|MPS_readhandle|write_lp|write_LP|write_mps|write_MPS|read_MPShandle|set_outputfile)(_func)?\b.*;/;

	if (/^((STATIC|static|INLINE)\s+)?(char|void|int|gboolean|gnm_float|lprec|PVrec|BBrec|MATrec)\b(\s|\*)*(freePackedVector|vec_compress|compareLink|mat_setitem|prod_Ax|set_dv_bounds|get_dv_lower|get_dv_upper|print_indent|allocFREE|set_bitoff|normalizeVector|chsign_bounds|sizeLink|countActiveLink|prevInactiveLink|verifyLink|createPackedVector|pushPackedVector|unpackPackedVector|getvaluePackedVector|popPackedVector|mat_extractmat|mat_indexrange|mat_equalRows|mat_additem|mat_mergemat|mat_matinsert|vec_expand|fimprove|findself_BB|check_if_less|verify_solution|is_slackbasis|validate_bounds|varmap_validate|varmap_addcolumn|varmap_addconstraint|set_OF_override|guess_basis|debug_print|debug_print_solution|debug_print_bounds|verifyPricer|update_reducedcosts|partial_activeBlocks|partial_blockNextPos|partial_isVarActive|multi_size|multi_valueList|multi_getvar|LU1MCP|QS_updatelink|compute_violation|isPrimalSimplex|isPhase1|isDegenerateBasis|findNonBasicSlack|replaceBasisVar|mat_findcolumn|verifyMDO|probe_BB|presolve_BB|presolve_mustupdate|presolve_debugmap|presolve_debugcheck|presolve_rowlengthdebug|presolve_rowfix|presolve_probetighten01|presolve_invalideq2|presolve_debugdump|longdual_testset|stallMonitor_creepingObj|stallMonitor_shortSteps|findBasicArtificial|write_(lp|LP|mps|MPS)|read_(free|)(mps|MPS)|write_free(mps|MPS)|write_basis|read_basis|read_XLI|write_XLI|read_lp|read_LP|LP_readhandle|copy_lp|LUSOL_dump|LUSOL_findColumnPosition|LUSOL_timer|QS_delete|QS_replace|REPORT_mat_mmsave|SOS_member_count|SOS_memberships|bfp_createMDO|blockWriteBMAT|blockWriteLREAL|cloneINT|cloneMYBOOL|colamd_report|comp_bits|compareCHAR|dcopy|dload|dswap|findIndex|get_bounds|get_column|is_use_names|posmat|print_L0|printmatSQ|printmatUT|randomdens|randomseed|set_column|set_columnex|set_use_names|symamd_report|printvec|submat|ddrand|ddot|dnormi|LUSOL_vecdensity|cloneREAL|set_outputfile)\b/ .. (/^\}/ || /^[A-Za-z].*;$/)) {
	    next;
	}

	if ($file =~ /\.c$/ && /^\s*\#\s*define\s+([a-zA-Z_0-9]+)/) {
	    push @defines, $1;
	}

	if (/^(void|int|gint64|gboolean|gnm_float|lprec\s*\*)\s+lp_solve_\S+\s*\(.*\)\s*;$/ ||
	    /^typedef\b.*\blprec;$/ ||
	    /^\#define\s*ROWTYPE_(LE|GE|EQ)\b/ ||
	    (m{^/\* Solver status values \*/} ... /^$/)) {
	    $headers .= $_;
	}

	print DST;
    }
    close (SRC);
    if (@defines) {
	print DST "/* Cleaning up after import of $file */\n";
	foreach my $sym (@defines) {
	    print DST "#undef $sym\n";
	}
    }
}
close (DST);
&update_file ($dstfile);


{
    my $dstfile = "$dst/lp_solve.h";
    open (DST, ">$dstfile.new") or
	die "$0: Cannot write $dstfile.new: $!\n";
    print STDERR "Creating $dstfile...";
    print DST "/* Generated by $myself -- do not edit! */\n";
    print DST "#ifndef LP_SOLVE_H\n";
    print DST "#define LP_SOLVE_H\n";
    print DST "#include <glib.h>\n";
    print DST "#include <numbers.h>\n";
    print DST $headers;
    print DST "#endif\n";
    close (DST);
    &update_file ($dstfile);
}

# -----------------------------------------------------------------------------

sub update_file {
    my ($old) = @_;
    my ($new) = "$old.new";

    if (!-r $old) {
	rename $new, $old or
	    die "$0: Cannot rename $new to $old: $!\n";
	print STDERR " -- done.\n";
    } else {
	system ("cmp '$old' '$new' >/dev/null");
	if ($? == 0) {
	    print STDERR " -- unchanged.\n";
	    unlink $new;
	} else {
	    rename $new, $old or
		die "$0: Cannot rename $new to $old: $!\n";
	    print STDERR " -- done.\n";
	}
    }
}

# -----------------------------------------------------------------------------
