#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "boolean.h"
#include "file.h"
#include "xym.h"
#include "triangle.h"
#include "verbose.h"
#include "verification.h"
#include "opm_param.h"
#include "matched.h"

void usage(void);
void check_input(int argc, char *argv[], char *input1, char *input2, char *output, char *param_file);
triangle search_next_match(FILE *flog, triangle tri1, int narray, triangle *tri2, int *pos, double xrange, double ymin, double *coef, opm_param *setting);
void rescale_tran(double magnify, Boolean mirror, double *coef);

int main(int argc, char *argv[]){
   int ndata1, ndata2, start1, start2, nverify1, nverify2, nset, narray, i, pos;
   int nverify, niter;
   FILE *flog;
   char input1[FILE_LENGTH], input2[FILE_LENGTH], output[FILE_LENGTH];
   char param_file[FILE_LENGTH];
   double magnify, epsilon;
   double mag_cut1, mag_cut2;
   double ymax, ymin, xrange, coef[6], magnified, rotated;
   Boolean cut1, cut2, mirror;
   xym *data1, *data2;
   triangle *tri1, *tri2, match;
   opm_param setting;
#ifdef VERBOSE
   Boolean verb=TRUE;
   flog=stderr;
#else
   Boolean verb=FALSE;
   flog=NULL;
#endif

   check_input(argc,argv,input1,input2,output,param_file);
   if(strcmp(param_file,"NULL")==0||strcmp(param_file,"NONE")==0){
      get_opm_param_default(&setting);
   } else {
      set_opm_param(&setting,param_file);
   }

   if(verb==TRUE&&flog!=NULL){
      print_start(flog); print_opm_param(flog,&setting); fprintf(flog,"\n");
   }

   data1=load_xym(input1,&ndata1);
   if(verb==TRUE) print_load_xym(flog,ndata1,input1);
   sort_xym_x(ndata1,data1);
   sort_xym_m(ndata1,data1);
   cut1=get_opm_cut1(&setting);
   mag_cut1=get_opm_mag_cut1(&setting);
   if(cut1==TRUE){
      if((start1=binsearch_xym_m(mag_cut1,ndata1,data1))<0) start1=0;
      if(flog!=NULL){
         if(start1!=0) print_mag_cut(flog,input1,mag_cut1,start1);
         else fprintf(stderr,"#\tNo input1 objects are brighter than mag=%g.\n",mag_cut1);
      }
   } else start1=0;
   magnify=get_opm_magnify(&setting);
   scale_xym(ndata1,data1,magnify);
   if((mirror=get_opm_mirror(&setting))==TRUE){
      mirror_xym_x(ndata1,data1);
      if(verb==TRUE&&flog!=NULL) fprintf(flog,"#\tx coordinates of data1 were reversed.\n");
   }

   data2=load_xym(input2,&ndata2);
   if(verb==TRUE&&flog!=NULL) print_load_xym(flog,ndata2,input2);
   sort_xym_x(ndata2,data2);
   sort_xym_m(ndata2,data2);
   cut2=get_opm_cut2(&setting);
   mag_cut2=get_opm_mag_cut2(&setting);
   if(cut2==TRUE){
      if((start2=binsearch_xym_m(mag_cut2,ndata2,data2))<0) exit(1);
      if(flog!=NULL){
         if(start2!=0) print_mag_cut(flog,input2,mag_cut2,start2);
         else fprintf(stderr,"#\tNo input2 objects are brighter than mag=%g.\n",mag_cut2);
      }
   } else start2=0;

   nset=get_opm_nset(&setting);
   if(ndata1-start1<nset||ndata2-start2<nset){
      fprintf(stderr,"!!! nset was reduced from %d ",nset);
      if(ndata1-start1<ndata2-start2) nset=ndata1-start1;
      else nset=ndata2-start2;
      fprintf(stderr,"to %d !!!\n",nset);
      if(nset<3){
         fprintf(stderr,"!!! nset is less than three !!!\n"); exit(1);
      }
   }
   if(verb==TRUE&&flog!=NULL) print_nset(flog,nset,start1,start2);
   narray=nset*(nset-1)*(nset-2)/6;
   tri1=make_triangle_array(nset,data1+start1);
   sort_triangles_ratiometric(narray,tri1);
   tri2=make_triangle_array(nset,data2+start2);
   sort_triangles_ratio(narray,tri2);
   if(verb==TRUE&&flog!=NULL) print_make_tri_array(flog,nset,narray);

   nverify1=ndata1; nverify2=ndata2;
   if((nverify=get_opm_nverify(&setting))>0){
      if(nverify<nverify1) nverify1=nverify;
      if(nverify<nverify2) nverify2=nverify;
   }
   epsilon=get_opm_epsilon(&setting);
   niter=get_opm_niter(&setting);
   for(i=0;i<narray;i++){
      ymax=(1+epsilon)*get_triangle_ratio(tri1[i]);
      ymin=(1-epsilon)*get_triangle_ratio(tri1[i]);
      xrange=get_triangle_metric(tri1[i])*epsilon;
      if((pos=binsearch_triangles_ratio(ymax,narray,tri2))<0) continue;
      while(pos<narray){
         if((match=search_next_match(flog,tri1[i],narray,tri2,&pos,xrange,ymin,coef,&setting))!=NULL){
            if(verification(flog,nverify1,data1,nverify2,data2,coef,&setting)==TRUE) goto matched;
         }
      }
   }
   goto finish;
matched:
   rescale_tran(magnify,mirror,coef);
   output_matched_result(flog,output,ndata1,data1,ndata2,data2,coef,&setting);
   if(verb==TRUE&&flog!=NULL){
      fprintf(flog,"\n"); print_opm_success(flog,coef);
      calc_coef_param(get_opm_mirror(&setting),coef,&magnified,&rotated);
      fprintf(flog,"#\tmagnify=%.3f  angle=%.1f\n",magnified,rotated);
   }
finish:
   if(verb==TRUE&&flog!=NULL) print_end(flog);
   //if(flog!=NULL) fclose(flog);
   destroy_xym(ndata1,data1); destroy_xym(ndata2,data2);
   destroy_triangle_array(narray,tri1);
   destroy_triangle_array(narray,tri2);
   return 0;
}

void usage(void){
   fprintf(stderr,"\nUsage -> opm  input1  input2  output param_file\n");
   fprintf(stderr,"    input1+input2 : x y m\n");
   fprintf(stderr,"    output : the name of output (format: id1 x1 y1 m1 id2 x2 y2 m2)\n");
   fprintf(stderr,"    param_file : the name of param_file\n");
   fprintf(stderr,"  get_default_param program will give you default param_file\n\n");
   exit(1);
}

void check_input(int argc, char *argv[], char *input1, char *input2, char *output, char *param_file){
   if(argc!=5) usage();
   strcpy(input1,argv[1]);
   strcpy(input2,argv[2]);
   strcpy(output,argv[3]);
   strcpy(param_file,argv[4]);
}

triangle search_next_match(FILE *flog, triangle tri1, int narray, triangle *tri2, int *pos, double xrange, double ymin, double *coef, opm_param *setting){
   int i, j;
   Boolean mirror, rotate;
   double metric1, ratio1, metric2, ratio2, pv_limit, magnify, angle, angle_tmp;
   double tmp[6];
   pv_limit=get_opm_pv_limit(setting);
   rotate=get_opm_rotate(setting);
   mirror=get_opm_mirror(setting);
   angle=get_opm_angle(setting);
   metric1=get_triangle_metric(tri1);
   ratio1=get_triangle_ratio(tri1);
   for(j=*pos;j<narray;j++){
      ratio2=get_triangle_ratio(tri2[j]);
      if(ratio2<ymin) break;
      metric2=get_triangle_metric(tri2[j]);
      if(fabs(metric1-metric2)<xrange){
         if(pre_verif(tri1,tri2[j],coef,pv_limit)!=TRUE) continue;
         for(i=0;i<6;i++) tmp[i]=coef[i];
         rescale_tran(get_opm_magnify(setting),mirror,tmp);
         calc_coef_param(mirror,tmp,&magnify,&angle_tmp);
         if(rotate==TRUE&&fabs(angle-angle_tmp)>ANGLE_LIMIT) continue;
         if(flog!=NULL){
            fprintf(flog,"\n# pre_verif : %7.3f %7.3f %7.3f %7.3f %7.3f %7.3f\n",tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5]);
            fprintf(flog,"#\tmagnify=%.3f   angle=%.1f\n",magnify,angle_tmp);
         }
         *pos=j+1;
         return tri2[j];
      }
   }
   *pos=narray;
   return NULL;
}

void rescale_tran(double magnify, Boolean mirror, double *coef){
   coef[0]*=magnify; coef[1]*=magnify;
   coef[3]*=magnify; coef[4]*=magnify;
   if(mirror==TRUE){
      coef[0]*=-1; coef[3]*=-1;
   }
}

