#include "scpview.h"
#include "hybrid.h"


Hybrid* hybrid_new(SCProblem* problem) {

    Hybrid* h=NULL;

    /*Check parameter*/
    assert(problem!=NULL);

    /*Create Hybrid*/
    h = (Hybrid*)malloc(sizeof(Hybrid));
    assert(h!=NULL);

    /*Assign problem*/
    h->problem=problem;

    h->columnMask1=NULL;
    h->masterSolution=NULL;
 
    /*Set subalgorithms to NULL*/
    h->gdSolver=NULL;
    h->lagrangeSolver=NULL;
    h->lceSolver=NULL;
    h->nbgaSolver=NULL;
    h->simanSolver=NULL;

    /*Statistics*/
    h->stLceRunFlag=0;
    h->stNbgaRunFlag=0;
    h->stInitialLagrangeCost=0;
    h->stLagrangeanZMAX=0;
    h->stInitialGreedyCost=0;
    h->stSimanRunFlag=0;
    h->stBeforeLCECost=0;
    h->stNBGACost=0;
    h->stSACost=0;
    h->stFixedCost=0;

    /*Time statistics*/
    h->stInitialGreedyTime=0.0;
    h->stInitialLagrangeTime=0.0;
    h->stNBGATime=0.0;
    h->stSATime=0.0;
    h->stLCETime=0.0;
    h->stTotalTime=0.0;

    /*Return*/
    return h;

}

int hybrid_solve(Hybrid* h,int removeDominance) {

    int cols=0;
    int i=0;

    int oc,or,nc,nr;

    int gdCost=0;
    int lgCost=0;
    int gaCost=0;
    int lcCost=0;
    int saCost=0;
    int lgZMAX=0;
    IList* fixedColumnsList=NULL;
    IList* tempList=NULL;

    unsigned char *gdSolution,*lgSolution,*gsSolution,*lcSolution;
    SCPView *orView,*gdView,*lgView,*gaView,*saView,*masterView,*lcView;
    
    int mub = PS_INFINITY;
    int classCounter=0;

    unsigned char* bestSolPtr=NULL;
    SCPView* bestSolView=NULL;

    /*Check parameter*/
    assert(h!=NULL);

    /*Initialize and clear master solution*/
    h->masterSolution=(unsigned char*)malloc(h->problem->columns);
    assert(h->masterSolution!=NULL);
    memset(h->masterSolution,0,h->problem->columns);
    
    /*Create a master view to the SCP and check whether the problem is
      solvable*/
    masterView=scp_view_new(h->problem);
    /* If not, we have to report that the problem has no solutions*/
    if (scp_view_check_solvable(masterView)==0) {
        free(h->masterSolution);
        scp_view_delete(masterView);
        return HYBRID_NOTSOLVABLE;
    }

    /*orView (stand for original problem view is a view to the cyclic core*/
    orView=scp_view_new(h->problem);
    fixedColumnsList=ilist_new();

    
    do {

        oc=orView->columns;
        or=orView->rows;

        if (removeDominance==1) {
            scp_view_remove_dominance(&orView);
            
        }

        /*Remove fixed columns. If all fixed, we have a solution*/
        tempList=scp_view_remove_fixed_columns(&orView);
        
    
        ilist_start_iteration(tempList);
        while(ilist_iterate(tempList,&i)) {
            ilist_append(fixedColumnsList,i);
        }
        ilist_delete(tempList);

        if (orView==NULL) {
            hybrid_make_master_solution(h,NULL,orView,fixedColumnsList,masterView);
            scp_view_delete(orView);
            return HYBRID_IMPLICIT;
        }
    
        nc=orView->columns;
        nr=orView->rows;
    }
    while (nc<oc || nr<or);
   

    /*Cost of fixed columns has to calculated and at the end added to
      all costs*/
    ilist_start_iteration(fixedColumnsList);
    while(ilist_iterate(fixedColumnsList,&i)) {
        h->stFixedCost+=scp_view_get_cost(masterView,i);
    }

   

    /*Number of columns of the cyclic core*/
    cols=orView->columns;
    
    /*Number of L-classes found by LCE*/
    classCounter=0;

    /*Solutions computed by subalgorithms*/
    gdSolution=(unsigned char*)malloc(cols);
    lgSolution=(unsigned char*)malloc(cols);
    gsSolution=(unsigned char*)malloc(cols);
    lcSolution=(unsigned char*)malloc(cols);

    assert(gdSolution!=NULL && lgSolution!=NULL && gsSolution!=NULL && lcSolution!=NULL);

    memset(gdSolution,0,cols);
    memset(lgSolution,0,cols);
    memset(gsSolution,0,cols);
    memset(lcSolution,0,cols);

    /*Each subalgorithm has its own problem view*/
    lcView=scp_view_get_copy(orView);
    gdView=scp_view_get_copy(orView);
    lgView=scp_view_get_copy(orView);
    gaView=scp_view_get_copy(orView);
    saView=scp_view_get_copy(orView);

    

    /*Allocate temporary column mask*/
    h->columnMask1=(unsigned char*)malloc(cols);
    assert(h->columnMask1!=NULL);
    /*Clear column mass*/
    memset(h->columnMask1,0,cols);
   

    printf("[1] HYBRID: GREEDY\n");
    /* STEP 1 - APPROXIMATE SOLUTION BY GREEDY ALGORITHM*
     * ==================================================*/
    hybrid_phase_greedy(h,gdView,gdSolution,&gdCost);


    assert(scp_view_is_solution(orView,gdSolution)==1);
   
    /* STEP 2 - LOWER BOUND BY LAGRANGEAN HEURISTIC
     * ===============================================*/
    
    printf("[2] HYBRID: INITIAL LAGRANGEAN HEURISTIC\n");
    hybrid_phase_lagrange(h,lgView,lgSolution,&lgCost,&lgZMAX);
    
    /*If cost of feasuble solution computed by lagrangean heuristic
     *is same as its lower bound, it is optimal solution*/
    if (lgCost<=lgZMAX) {
        bestSolPtr=lgSolution;
        bestSolView=lgView;
        h->masterSolutionCost=lgCost;
        goto done_success;
    }

    /*Lagrangean heuristic has to always find feasible solution*/
    assert(scp_view_is_solution(orView,lgSolution)==1);

    /*If cost of solution computed by greedy algorithm is same as its
     *lower bound, it is optimal solution*/
    if (gdCost<=lgZMAX) {
        bestSolPtr=gdSolution;
        bestSolView=gdView;
        h->masterSolutionCost=gdCost;
       goto done_success;
    }

    
    /* STEP 3A - NON-BINARY GENETHIC ALGORITHM
     * ======================================
     */
 
    if (h->parmSAInsteadOfNBGA==0) {

        printf("[3A] HYBRID: NBGA\n");
        
        hybrid_phase_nbga(h,gaView,gsSolution,&gaCost,lgZMAX,(gdCost<lgCost)?gdCost:lgCost);
        
        
        /*Solution found is optimal, no need for L-class enumeration*/
        if (gaCost<=h->lagrangeSolver->ZMAX) {
            bestSolPtr=gsSolution;
            bestSolView=gaView;
            h->masterSolutionCost=gaCost;
            goto done_success;
        }
       
    }
    else {
        /* STEP 3B SIMULATED ANNEALING
         * =======================================================
         */

        printf("[3B] HYBRID: SIMULATED ANNEALING\n");
        if (gdCost<lgCost) {
            hybrid_phase_sa(h,saView,gsSolution,&saCost,gdSolution);
        }
        else {
            hybrid_phase_sa(h,saView,gsSolution,&saCost,lgSolution);
        }


        /*Test whethe NBGA or SA found optimal solution*/
        if (saCost<=lgZMAX) {
            bestSolPtr=gsSolution;
            bestSolView=saView;
            h->masterSolutionCost=saCost;
            goto done_success;
        }

    }

    /* STEP 4 - L-CLASS ENUMERATION. EXACT, RESOURCE DEMANDING
     *========================================================
     */

    /*We take into account cheapest solution. It will become initial
     *record for LCE*/
    
    if (gdCost<mub) {mub=gdCost;bestSolPtr=gdSolution;bestSolView=gdView;}
    if (lgCost<mub) {mub=lgCost;bestSolPtr=lgSolution;bestSolView=lgView;}

    if (h->parmSAInsteadOfNBGA==0) {
        if (gaCost<mub) {mub=gaCost;bestSolPtr=gsSolution;bestSolView=gaView;}
        h->stNBGACost=gaCost;
    }
    else {
        if (saCost<mub) {mub=saCost;bestSolPtr=gsSolution;bestSolView=saView;}
        h->stSACost=saCost;
    }


    h->stBeforeLCECost=mub;
   
    printf("[4] HYBRID: L-CLASS ENUMERATION (Record: %d)\n",mub);
    hybrid_phase_lce(h,lcView,lcSolution,&lcCost,&classCounter,mub);

    /*Obtain results, if LCE found any class*/
    if (classCounter>0) {
        h->masterSolutionCost=lcCost;
        assert(scp_view_is_solution(orView,lcSolution)==1);
        bestSolView=lcView;
        bestSolPtr=lcSolution;
    }
    else {
        h->masterSolutionCost=mub;
        assert(scp_view_is_solution(orView,bestSolPtr)==1);
    }

    printf("[5] HYBRID ALGORITHM FINISHED\n");

    /* FINALIZATION
     * =========================================================
     */
    done_success:

        /*Creating master solution*/
    hybrid_make_master_solution(h,bestSolPtr,orView,fixedColumnsList,masterView);

        
        scp_view_delete(gdView);
        scp_view_delete(lgView);
        scp_view_delete(gaView);
        scp_view_delete(lcView);
        scp_view_delete(saView);
        scp_view_delete(orView);
        scp_view_delete(masterView);
        

        free(gdSolution);
        free(lgSolution);
        free(gsSolution);
        free(lcSolution);

        ilist_delete(fixedColumnsList);

        h->stTotalTime=h->stInitialGreedyTime+h->stInitialLagrangeTime+h->stNBGATime+h->stSATime+h->stLCETime;

        /*Updating costs taking fixed into account*/
        h->stBeforeLCECost+=h->stFixedCost;
        h->stInitialGreedyCost+=h->stFixedCost;
        h->stInitialLagrangeCost+=h->stFixedCost;
        h->stNBGACost+=h->stFixedCost;
        h->stSACost+=h->stFixedCost;
        h->stLagrangeanZMAX+=h->stFixedCost;

        return HYBRID_OK;
}



void hybrid_delete(Hybrid* h) {

    if (h==NULL) return;
    
    lce_delete(h->lceSolver);
    nbga_delete(h->nbgaSolver);
    lagrangesolver_delete(h->lagrangeSolver);
    greedy_solver_delete(h->gdSolver);
    siman_solver_delete(h->simanSolver);

    if (h->columnMask1!=NULL) free(h->columnMask1);
    if (h->masterSolution!=NULL) free(h->masterSolution);

    free(h);
}


void hybrid_phase_greedy(Hybrid* h,SCPView* view,unsigned char* solPtr,int* solCost) {

    clock_t cpuBegin,cpuEnd;

    h->gdSolver=greedy_solver_new(view);

    cpuBegin=clock();
    greedy_solver_initialize(h->gdSolver,NULL);
    greedy_solver_solve_chvatal(h->gdSolver,NULL);
    cpuEnd=clock();

    h->stInitialGreedyTime=hybrid_calc_time(cpuBegin,cpuEnd);

    /*Obtain results*/
    memcpy(h->columnMask1,h->gdSolver->columnMask,view->columns);

    *solCost=scp_view_get_cost_mask(view,h->columnMask1,view->columns);
    h->stInitialGreedyCost=*solCost;
    hybrid_map_solution_to_parent(view,h->columnMask1,solPtr);
    
    
}

void hybrid_phase_lagrange(Hybrid* h,SCPView* view,unsigned char* solPtr,int* solCost,int* zmax) {

    clock_t cpuBegin,cpuEnd;

    h->lagrangeSolver=lagrangesolver_new(view,h->parmInitialLagrangeanHeuristicType);

    cpuBegin=clock();
    lagrangesolver_solve(h->lagrangeSolver,-1,0);
    cpuEnd=clock();

    h->stInitialLagrangeTime=hybrid_calc_time(cpuBegin,cpuEnd);

    /*Obtain results*/
    memcpy(h->columnMask1,h->lagrangeSolver->xbest,view->columns);
    
    *solCost=scp_view_get_cost_mask(view,h->columnMask1,view->columns);
    *zmax=h->lagrangeSolver->ZMAX;
    h->stLagrangeanZMAX=*zmax;

    h->stInitialLagrangeCost=*solCost;
    hybrid_map_solution_to_parent(view,h->columnMask1,solPtr);
}

void hybrid_phase_nbga(Hybrid* h,SCPView* view,unsigned char* solPtr,int* solCost,int lb,int ub) {

    clock_t cpuBegin,cpuEnd;

    /*Solve*/
    h->stNbgaRunFlag=1;
    h->nbgaSolver=nbga_new(view,h->parmNBGAPopulationSize,h->parmNBGAMaxIterations,h->parmNBGAMutationProbability,lb,ub);

    cpuBegin=clock();
    nbga_solve(h->nbgaSolver);
    cpuEnd=clock();
    h->stNBGATime=hybrid_calc_time(cpuBegin,cpuEnd);

    /*Obtain results*/
    nbga_get_solution(h->nbgaSolver,h->columnMask1);
    *solCost=scp_view_get_cost_mask(view,h->columnMask1,view->columns);
    hybrid_map_solution_to_parent(view,h->columnMask1,solPtr);
}

void hybrid_phase_sa(Hybrid* h,SCPView* view,unsigned char* solPtr,int* solCost,unsigned char* baseSolution) {

    clock_t cpuBegin,cpuEnd;

    /*Solve*/
    h->stSimanRunFlag=1;
    h->simanSolver=siman_solver_new(view,NULL);

    cpuBegin=clock();
    siman_solver_solve(h->simanSolver,h->parmSATemp,h->parmSAFrozenTemp,h->parmSAIterations,1,h->parmSAConstant,baseSolution,(int)h->lagrangeSolver->ZMAX);
    cpuEnd=clock();
    h->stSATime=hybrid_calc_time(cpuBegin,cpuEnd);

    /*Obtain result*/
    siman_solver_get_solution(h->simanSolver,h->columnMask1);
    *solCost=scp_view_get_cost_mask(view,h->columnMask1,view->columns);
    hybrid_map_solution_to_parent(view,h->columnMask1,solPtr);
        
}

void hybrid_phase_lce(Hybrid* h,SCPView* view,unsigned char* solPtr,int* solCost,int* classCounter,int record) {

    clock_t cpuBegin,cpuEnd;

    /*Solve*/
    h->stLceRunFlag=1;
    h->lceSolver = lce_new(view,h->parmLCELagrangeanHeuristicType,h->parmLCEUseLagrange);

    cpuBegin=clock();
    lce_solve(h->lceSolver,record);
    h->stLCETime=h->lceSolver->stTotalCPUTime;
    cpuEnd=clock();
    h->stLCETime=hybrid_calc_time(cpuBegin,cpuEnd);

    *classCounter=h->lceSolver->stIntegerClassesFound;
    if (*classCounter>0) {

        lce_get_solution(h->lceSolver,h->columnMask1);
        *solCost=scp_view_get_cost_mask(view,h->columnMask1,view->columns);
        hybrid_map_solution_to_parent(view,h->columnMask1,solPtr);

        *classCounter=h->lceSolver->stIntegerClassesFound;
        
    }

}


void hybrid_map_solution_to_parent(SCPView* problemView,unsigned char* inMask,unsigned char* retMask) {

    int i=0;

    memset(retMask,0,problemView->columns);

    for(i=0;i<problemView->columns;i++) {
        if (inMask[i]==1) {
            retMask[problemView->parentColumnMap[i]]=1;
        }
    }
    
}

double hybrid_calc_time(clock_t begin,clock_t end) {
    return ((double) (end - begin)) / CLOCKS_PER_SEC;
}

void hybrid_print_statistics(Hybrid* h) {

    /*Check parameter*/
    assert(h!=NULL);

    printf("Solution cost:%d\n",h->masterSolutionCost);
    
    /*Number of rows and columns. Whether NBGA and LCE was run*/
    printf("*General information and initial stage:\n");
    printf("R:%d,C:%d,NBGA:%d,SA:%d,LCE:%d,IGRDY:%d,ILGRN:%d,ILGZMAX:%d\n",h->problem->rows,h->problem->columns,
            h->stNbgaRunFlag,h->stSimanRunFlag,h->stLceRunFlag,h->stInitialGreedyCost,
            h->stInitialLagrangeCost,(int)h->lagrangeSolver->ZMAX);

    /*Information about NBGA*/
    printf("*Attempts to improve solution:\n");
    if (h->stNbgaRunFlag==1) {
        printf("NBGACROSS:%d,NBGACOST:%d\n",h->nbgaSolver->stCrossoverCounter,h->stNBGACost);
    }
    if (h->stSimanRunFlag==1) {
        printf("SAITRS:%d,SACOST:%d\n",h->simanSolver->stIterationCounter,h->stSACost);
    }


    /*Information about LCE*/
    printf("*Exact LCE:\n");
    if (h->stLceRunFlag==1) {
        printf("MUB:%d,ITR:%d,LHIMPR:%d,LEXMIN:%d,LEXMINNOFEAS:%d,GTH:%d,GTHLHIMPR:%d,GTHFEASIBLE:%d,ILCF:%d\n",h->stBeforeLCECost,h->lceSolver->stIterationCount,
                h->lceSolver->stLexminLagrangeImproved,h->lceSolver->stLexminRunCount,h->lceSolver->stLexminNoFeasibleCount,
                h->lceSolver->stGTHRunCount,h->lceSolver->stGTHLagrangeImproved,h->lceSolver->stGTHFeasibleCount,
                h->lceSolver->stIntegerClassesFound);
    }
    /*Information about time*/
    printf("*Time:\n");
    printf(",TTOT:%f,TIGD:%f,TILG:%f,TNBGA:%f,TSA:%f,TLCE:%f",h->stTotalTime,h->stInitialGreedyTime,h->stInitialLagrangeTime,
            h->stNBGATime,h->stSATime,h->stLCETime);
    printf("\n");
}

void hybrid_print_csv(Hybrid* h,FILE* resultFile,const char* fname,int resultPrintHeader,char* dateStart,char* dateEnd) {

    /*Check parameters*/
    assert(h!=NULL && resultFile!=NULL);

    if (resultPrintHeader==1) {
        fprintf(resultFile,"R,C,GAITERS,GAPOPSIZE,GAMUTPROB,SATEMP,SAFRZT,SACOOL,SAITERS,INLGHTYPE,LCELGHTYPE,");
        fprintf(resultFile,"SOLCOST,TOTTIME,IGDYTIME,ILGHTIME,GATIME,SATIME,LCETIME,");
        fprintf(resultFile,"IGDYCOST,ILGZMAX,ILGCOST,");
        fprintf(resultFile,"GARUN,GACROSS,GACOST,SARUN,SAITRS,SACOST,");
        fprintf(resultFile,"LCERUN,LCEMUB,LCEITRS,LCELEXMIN,LCELEXMINOFEAS,LCELEXMINLGHIMPR,LCEGTH,LCEGTHLGHIMPR,LCEGTHFEAS,LCEICF,FNAME,SD,ED\n");
    }

    /*Problem specification section*/
    fprintf(resultFile,"%d,%d,%d,%d,%f,",h->problem->rows,h->problem->columns,
            h->parmNBGAMaxIterations,h->parmNBGAPopulationSize,h->parmNBGAMutationProbability);

    /*SA*/
    fprintf(resultFile,"%f,%f,%f,%d,",h->parmSATemp,h->parmSAFrozenTemp,h->parmSAConstant,h->parmSAIterations);

    /*Lagrangean heuristics*/
    fprintf(resultFile,"%d,%d,",h->parmInitialLagrangeanHeuristicType,h->parmLCELagrangeanHeuristicType);

    /*Time*/
    fprintf(resultFile,"%d,%f,%f,%f,%f,%f,%f,",h->masterSolutionCost,h->stTotalTime,h->stInitialGreedyTime,h->stInitialLagrangeTime,
            h->stNBGATime,h->stSATime,h->stLCETime);

    /*Greedy and initial lagrangean relaxation*/
    fprintf(resultFile,"%d,%d,%d,",h->stInitialGreedyCost,h->stLagrangeanZMAX,h->stInitialLagrangeCost);

    /*NBGA*/
    if (h->stNbgaRunFlag==1) {
        fprintf(resultFile,"Y,%d,%d,",h->nbgaSolver->stCrossoverCounter,h->stNBGACost);
    } else fprintf(resultFile,"N,-,-,");
    /*SA*/
    if (h->stSimanRunFlag==1) {
        fprintf(resultFile,"Y,%d,%d,",h->simanSolver->stIterationCounter,h->stSACost);
    }
    else fprintf(resultFile,"N,-,-,");

    /*LCE*/
    if (h->stLceRunFlag==1) {
        fprintf(resultFile,"Y,%d,%d,%d,%d,%d,%d,%d,%d,%d,",h->stBeforeLCECost,
                h->lceSolver->stIterationCount,h->lceSolver->stLexminRunCount,
                h->lceSolver->stLexminNoFeasibleCount,h->lceSolver->stLexminLagrangeImproved,
                h->lceSolver->stGTHRunCount,h->lceSolver->stGTHLagrangeImproved,
                h->lceSolver->stGTHFeasibleCount,h->lceSolver->stIntegerClassesFound);
    }
    else fprintf(resultFile,"N,-,-,-,-,-,-,-,-,");

    fprintf(resultFile,"%s,%s,%s",fname,dateStart,dateEnd);
    /*Final endline*/
    fprintf(resultFile,"\n");

}

void hybrid_set_parameters(Hybrid* h,int nbgaMaxIterations,double nbgaMutationProbability,int nbgaPopulationSize,int initialLGHType,int LCELGHType,int LCEUseLGH) {

    /*Check parameter. Others are checked by subalgorithms*/
    assert(h!=NULL);

    /*Set*/
    h->parmNBGAMaxIterations=nbgaMaxIterations;
    h->parmNBGAMutationProbability=nbgaMutationProbability;
    h->parmNBGAPopulationSize=nbgaPopulationSize;
    h->parmInitialLagrangeanHeuristicType=initialLGHType;
    h->parmLCELagrangeanHeuristicType=LCELGHType;
    h->parmLCEUseLagrange=LCEUseLGH;

    /*Done*/
}

void hybrid_set_sa_parameters(Hybrid* h,int saPerform,double startTemp,double frozenTemp,double constant,int iterations) {

    /*Check parameter*/
    assert(h!=NULL);

    /*Set*/
    h->parmSAInsteadOfNBGA=saPerform;
    h->parmSATemp=startTemp;
    h->parmSAConstant=constant;
    h->parmSAIterations=iterations;
    h->parmSAFrozenTemp=frozenTemp;

    /*Done*/
}

unsigned char* hybrid_get_solution(Hybrid* h,int* cost) {

    unsigned char* mask=NULL;

    assert(h!=NULL);
    mask=(unsigned char*)malloc(h->problem->columns);
    assert(mask!=NULL);
    
    *cost=h->masterSolutionCost;

    memcpy(mask,h->masterSolution,h->problem->columns);
    return mask;
    
}

void hybrid_make_master_solution(Hybrid *h,unsigned char* mask,SCPView* view,IList* fixedList,SCPView* masterView) {

    int i=0;
    assert(h!=NULL);

    h->masterSolutionCost=0;

    memset(h->masterSolution,0,h->problem->columns);
    if (mask!=NULL) {
        for(i=0;i<view->columns;i++) {
            h->masterSolution[view->columnMap[i]]=mask[i];
        }
    }

    ilist_start_iteration(fixedList);
    while(ilist_iterate(fixedList,&i)) {
        h->masterSolution[i]=1;
    }

    h->masterSolutionCost=scp_view_get_cost_mask(masterView,h->masterSolution,masterView->columns);

    
}


void hybrid_print(Hybrid* h) {
    if (h!=NULL) {
        printf("Hybrid");
    }
}

