classdef nfsftUnitTests
  %UNITTEST Summary of this class goes here
  %   Detailed explanation goes here
  
  properties
    perform_exhaustive_tests_flag = 0;

    testcases_online_trafo = { ...
      nfsftTestcaseDelegateOnline(32, 10, 'trafo') ...
      nfsftTestcaseDelegateOnline(32, 100, 'trafo') ...
      nfsftTestcaseDelegateOnline(32, 1000, 'trafo') ...
      nfsftTestcaseDelegateOnline(64, 10, 'trafo') ...
      nfsftTestcaseDelegateOnline(64, 100, 'trafo') ...
      nfsftTestcaseDelegateOnline(128, 10, 'trafo') ...
      nfsftTestcaseDelegateOnline(128, 100, 'trafo') ...
      nfsftTestcaseDelegateOnline(256, 10, 'trafo') ...
      nfsftTestcaseDelegateOnline(256, 100, 'trafo') ...
                             };

    testcases_online_trafo_exhaustive = { ...
      nfsftTestcaseDelegateOnline(128, 1000, 'trafo') ...
      nfsftTestcaseDelegateOnline(256, 1000, 'trafo') ...
      nfsftTestcaseDelegateOnline(512, 10, 'trafo') ...
      nfsftTestcaseDelegateOnline(512, 100, 'trafo') ...
      nfsftTestcaseDelegateOnline(1024, 10, 'trafo') ...
      nfsftTestcaseDelegateOnline(2048, 10, 'trafo') ...
    };

    testcases_online_adjoint = { ...
      nfsftTestcaseDelegateOnline(32, 10, 'adjoint') ...
      nfsftTestcaseDelegateOnline(32, 100, 'adjoint') ...
      nfsftTestcaseDelegateOnline(32, 1000, 'adjoint') ...
      nfsftTestcaseDelegateOnline(64, 10, 'adjoint') ...
      nfsftTestcaseDelegateOnline(64, 100, 'adjoint') ...
      nfsftTestcaseDelegateOnline(128, 10, 'adjoint') ...
      nfsftTestcaseDelegateOnline(128, 100, 'adjoint') ...
      nfsftTestcaseDelegateOnline(256, 10, 'adjoint') ...
      nfsftTestcaseDelegateOnline(256, 100, 'adjoint') ...
                       };

    testcases_online_adjoint_exhaustive = { ...
      nfsftTestcaseDelegateOnline(128, 1000, 'adjoint') ...
      nfsftTestcaseDelegateOnline(256, 1000, 'adjoint') ...
      nfsftTestcaseDelegateOnline(512, 10, 'adjoint') ...
      nfsftTestcaseDelegateOnline(512, 100, 'adjoint') ...
      nfsftTestcaseDelegateOnline(1024, 10, 'adjoint') ...
      nfsftTestcaseDelegateOnline(2048, 10, 'adjoint') ...
    };

    initializers_std = { ...
      nfsftTestcaseInitDelegate('init') ...
      nfsftTestcaseInitDelegate('init_advanced') ...
      nfsftTestcaseInitDelegate('init_guru',NFSFT_USE_DPT,FPT_NO_FAST_ALGORITHM,bitshift(1, 4),6) ...
      nfsftTestcaseInitDelegate('init_class') ...
      nfsftTestcaseInitDelegate('init_class', NFSFT_USE_DPT, FPT_NO_FAST_ALGORITHM) ...
    };

    testcases_online_trafo_equispaced = { ...
      nfsftTestcaseDelegateOnline(16, 0, 'trafo') ...
      nfsftTestcaseDelegateOnline(32, 0, 'trafo') ...
      nfsftTestcaseDelegateOnline(64, 0, 'trafo') ...
    };

    testcases_online_adjoint_equispaced = { ...
      nfsftTestcaseDelegateOnline(16, 0, 'adjoint') ...
      nfsftTestcaseDelegateOnline(32, 0, 'adjoint') ...
      nfsftTestcaseDelegateOnline(64, 0, 'adjoint') ...
                             };
  
    initializers_equispaced = { ...
      nfsftTestcaseInitDelegate('init_class',NFSFT_EQUISPACED) ...
    };

    testcases_gl_quadrature_online = { ...
      nfsftTestcaseDelegateGLQuadratureOnline(4) ...
      nfsftTestcaseDelegateGLQuadratureOnline(8) ...
      nfsftTestcaseDelegateGLQuadratureOnline(16) ...
      nfsftTestcaseDelegateGLQuadratureOnline(32) ...
      nfsftTestcaseDelegateGLQuadratureOnline(64) ...
      nfsftTestcaseDelegateGLQuadratureOnline(128) ...
      nfsftTestcaseDelegateGLQuadratureOnline(256) ...
      nfsftTestcaseDelegateGLQuadratureOnline(512) ...
    };

    testcases_gl_quadrature_online_large = { ...
      nfsftTestcaseDelegateGLQuadratureOnline(1024) ...
    };

    testcases_gl_quadrature_online_exhaustive = { ...
      nfsftTestcaseDelegateGLQuadratureOnline(2048) ...
    };
  
    initializers_quadrature = { ...
      nfsftTestcaseInitDelegate('init_advanced',NFSFT_NORMALIZED) ...
      nfsftTestcaseInitDelegate('init_advanced',bitor(NFSFT_NORMALIZED,NFSFT_USE_DPT)) ...
      nfsftTestcaseInitDelegate('init_class',NFSFT_NORMALIZED) ...
      nfsftTestcaseInitDelegate('init_class',bitor(NFSFT_NORMALIZED,NFSFT_USE_DPT)) ...
    };

    initializers_quadrature_large = { ...
      nfsftTestcaseInitDelegate('init_class',NFSFT_NORMALIZED) ...
    };
  end
  
  methods
    function ok = check_single(~, testcase, init_delegate, check_delegate, trafo_delegate)
      ok = 0;
      
      testcase = testcase.setup;
      
      if isempty(ver('octave'))
        bound = trafo_delegate.acc(init_delegate.nfft_cutoff_m);
        X = testcase.x;
        N = testcase.N;
        switch trafo_delegate.name
          case 'trafo'
            fh_testcase = testcase.f_hat;
            f_mat = legendre(0,cos(X(2,:)),'norm').' * fh_testcase(0,0) * sqrt(2);
            for n=1:N
              Lp = legendre(n,cos(X(2,:)),'norm') * sqrt(2/(2*n+1));
              f_mat=f_mat + (flipud(Lp).* exp((-n:0)'*1i*X(1,:))).'* fh_testcase(n,-n:0)...
                + (Lp(2:end,:).* exp((1:n)'*1i*X(1,:))).'* fh_testcase(n,1:n);
            end
            f_testcase = testcase.f;
            err = max(abs(f_testcase-f_mat)) / sum(sum(abs(double(fh_testcase))));
            if err > bound
              ok = 0;
              fprintf(' -> %-4s (incorrect reference val)\n','FAILED');
              return;
            end
          case 'adjoint'
            fh_testcase = testcase.f_hat;
            f_testcase = testcase.f;
            fh2_mat = zeros(size(testcase.f_hat));
            fh2_mat(1)= legendre(0,cos(X(2,:)),'norm') * sqrt(2) * f_testcase;
            for n=1:N
              Lp = legendre(n,cos(X(2,:)),'norm') * sqrt(2/(2*n+1));
              fh2_mat(n^2+1:n^2+n+1)= (flipud(Lp).* exp(-(-n:0)'*1i*X(1,:)))* f_testcase;
              fh2_mat(n^2+n+2:n^2+2*n+1)= (Lp(2:end,:).* exp(-(1:n)'*1i*X(1,:)))* f_testcase;
            end
            fh2_mat = f_hat(fh2_mat);
            err = max(max(abs(double(fh_testcase-fh2_mat)))) / sum(abs(f_testcase));
            if err > bound
              ok = 0;
              fprintf(' -> %-4s (incorrect reference val)\n','FAILED');
              return;
            end
        end
      end

      init_txt = init_delegate.txt;
      fprintf(', %-18s', init_txt);
      [init_delegate, plan] = init_delegate.init(testcase.N, testcase.M);

      if ~isempty(init_delegate.nfft_cutoff_m)
        fprintf(', m = %2d', init_delegate.nfft_cutoff_m);
      else
        fprintf(', m std.');
      end
      fprintf(', %-14s', trafo_delegate.name);

      % Equispaced nodes are automatically set in nfsft_init
      if ~bitand(init_delegate.flags, NFSFT_EQUISPACED)
        if isnumeric(plan)
          nfsft_set_x(plan, testcase.x);
          nfsft_precompute_x(plan);
        else
          plan.x = testcase.x;
        end
      end

      run_test = 1;
      if (trafo_delegate.isCost)
        cost = trafo_delegate.cost(plan);
        if (cost > 0.1)
          fprintf(' -> %-4s (cost too high)\n','OK');
          ok = 1;
          run_test = 0;
        end
      end
      
      if run_test
        plan = check_delegate.prepare(plan, testcase.f, testcase.f_hat);
        
        plan = trafo_delegate.trafo(plan);
        
        err = check_delegate.compare(plan, testcase.f, testcase.f_hat);
        bound = trafo_delegate.acc(init_delegate.nfft_cutoff_m);
        ok = err < bound;
        if ~ok; okstr = 'FAIL'; else, okstr = 'OK'; end
        fprintf(' -> %-4s %.3e (%.3e)\n', okstr, err, bound);
      end
      
      testcase.destroy;
      if isnumeric(plan)
        nfsft_finalize(plan);
      else
        delete(plan);
      end
      nfsft_forget;
    end

    function result = check_many(h, testcases, initializers, check_delegate, trafos)
      result = 1;
      for k=1:length(trafos)
        for i=1:length(testcases)
          for j=1:length(initializers)
            r = h.check_single(testcases{i}, initializers{j}, check_delegate, trafos{k});
            result = min(result, r);
          end
        end
      end
    end

    function result = nfsft_check_online(h)
      result = h.check_many(h.testcases_online_trafo, h.initializers_std, nfsftTestcaseCheckDelegate('trafo'), {nfsftTestcaseTrafoDelegate('trafo')});
      if h.perform_exhaustive_tests_flag
        result2 = h.check_many(h.testcases_online_trafo_exhaustive, h.initializers_std, nfsftTestcaseCheckDelegate('trafo'), {nfsftTestcaseTrafoDelegate('trafo')});
        result = min(result,result2);
      end
    end
    
    function result = nfsft_check_adjoint_online(h)
      result = h.check_many(h.testcases_online_adjoint, h.initializers_std, nfsftTestcaseCheckDelegate('adjoint'), {nfsftTestcaseTrafoDelegate('adjoint')});
      if h.perform_exhaustive_tests_flag
        result2 = h.check_many(h.testcases_online_adjoint_exhaustive, h.initializers_std, nfsftTestcaseCheckDelegate('adjoint'), {nfsftTestcaseTrafoDelegate('adjoint')});
        result = min(result,result2);
      end
    end

    function result = nfsft_check_trafo_equispaced_online(h)
      result = h.check_many(h.testcases_online_trafo_equispaced, h.initializers_equispaced, nfsftTestcaseCheckDelegate('trafo'), {nfsftTestcaseTrafoDelegate('trafo')});
    end

    function result = nfsft_check_adjoint_equispaced_online(h)
      result = h.check_many(h.testcases_online_adjoint_equispaced, h.initializers_equispaced, nfsftTestcaseCheckDelegate('adjoint'), {nfsftTestcaseTrafoDelegate('adjoint')});
    end

    function ok = check_single_quadrature(~, testcase, init_delegate, trafo_delegate)
      ok = 0;
      
      init_txt = init_delegate.txt;
      fprintf('... %-18s', init_txt);
      [init_delegate, plan] = init_delegate.init(testcase.N, testcase.M);

      if ~isempty(init_delegate.nfft_cutoff_m)
        fprintf(', m = %2d', init_delegate.nfft_cutoff_m);
      else
        fprintf(', m std.');
      end
      fprintf(', %-14s', trafo_delegate.name);

      if isnumeric(plan)
        nfsft_set_x(plan, testcase.x);
        nfsft_precompute_x(plan);
      else
        plan.x = testcase.x;
      end

      run_test = 1;
      if (trafo_delegate.isCost)
        cost = trafo_delegate.cost(plan);
        if (cost > 0.1)
          fprintf(' -> %-4s (cost too high)\n','OK');
          ok = 1;
          run_test = 0;
        end
      end
      
      if run_test
        fh = testcase.f_hat;

        f_mul_W = testcase.f;
        if isnumeric(plan)
          nfsft_set_f(plan,f_mul_W);
        else
          plan.f = f_mul_W;
        end
          
        plan = trafo_delegate.trafo(plan);

        if isnumeric(plan)
          fh2 = nfsft_get_f_hat(plan);
        else
          fh2 = plan.fhat;
        end

        err = norm(fh-fh2) / norm(fh);
        bound = 2^22* eps;
        ok = err < bound;
        if ~ok; okstr = 'FAIL'; else, okstr = 'OK'; end
        fprintf(' -> %-4s %.3e (%.3e)\n', okstr, err, bound);
      end
      
      if isnumeric(plan)
        nfsft_finalize(plan);
      else
        delete(plan);
      end
    end
    
    function result = check_many_quadrature(h, testcases, initializers, trafos)
      result = 1;
      for i=1:length(testcases)
        testcase = testcases{i}.setup;
        bound = 2^22* eps;
        err = max(abs(testcase.f-testcase.f2)) / sum(sum(abs(double(testcase.f_hat))));
        ok = err < bound;
        if ~ok
          fprintf('Testcase setup FAILED\n');
          result = 0;
          continue;
        end

        fprintf('\n');

        for j=1:length(initializers)
          for k=1:length(trafos)
            r = h.check_single_quadrature(testcase, initializers{j}, trafos{k});
            result = min(result, r);
          end
          nfsft_forget;
        end
        testcase.destroy;
      end
    end

    function result = nfsft_check_quadrature_online(h)
      result = h.check_many_quadrature(h.testcases_gl_quadrature_online, h.initializers_quadrature, {nfsftTestcaseTrafoDelegate('adjoint')});

      % Don't perform large test in 32bit Octave
      archstring = computer;
      if isempty(strfind(archstring,'i686'))
        result2 = h.check_many_quadrature(h.testcases_gl_quadrature_online_large, h.initializers_quadrature_large, {nfsftTestcaseTrafoDelegate('adjoint')});
        result = min(result,result2);
      end

      if h.perform_exhaustive_tests_flag
        result2 = h.check_many_quadrature(h.testcases_gl_quadrature_online_exhaustive, h.initializers_quadrature, {nfsftTestcaseTrafoDelegate('adjoint')});
        result = min(result,result2);
      end
    end

  end
end
