package page.example.secpay;

import java.io.IOException;

import org.wikiwebserver.core.WareHouse;
import org.wikiwebserver.handler.http.FormData;
import org.wikiwebserver.handler.http.HTTPException;
import org.wikiwebserver.handler.http.HTTPHandler;
import org.wikiwebserver.handler.http.HTTPOutputStream;
import org.wikiwebserver.handler.http.interfaces.HTTPResponder;

public class CardCracker implements HTTPResponder {
    
    private static final int CARD_LENGTH = 16;
    private static final int NUM_THREADS = 10;
    
    private volatile String match = null;

    @Override
    public Object respond(HTTPHandler conn) throws IOException {

        conn.getResponse().getHeaders().set("Content-Encoding", "none");
        conn.getResponse().getHeaders().set("Content-Type", "text/plain");

        // Process form data        
        FormData formData = conn.getRequest().getFormData();
        if (formData == null) {
            throw new HTTPException(500, "Required params not sent!");
        }

        String prefix = formData.getFirst("prefix");
        String suffix = formData.getFirst("suffix");
        String hash = formData.getFirst("hash");
        
        int requiredNumbers = CARD_LENGTH - (prefix.length() + suffix.length());
        long searchSpace = (long) (Math.pow(10, requiredNumbers));
        
        if (searchSpace > 10000000) {
            throw new HTTPException(500, "Search space too large");
        }
        
        HTTPOutputStream writer = conn.getOutputStream();        
        writer.write("Searching for match using " + NUM_THREADS + " threads...\n");
        writer.flush();
        
        // Create and start searchers
        long time = System.currentTimeMillis();
        
        long start = 0, end;
        long threadSearchSpace = searchSpace / NUM_THREADS;
        ThreadGroup searchers = new ThreadGroup("CardCrackers");
        for (int i=0; i<NUM_THREADS; i++) {
            end = start + threadSearchSpace;
            Runnable r = createSearcher(prefix, suffix, hash, start, end);
            new Thread(searchers, r).start();
            start += threadSearchSpace;
        }
        
        // Wait for a match
        try {
            while (match == null && searchers.activeCount() > 0) {
                Thread.sleep(10);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // Display the result
        if (match != null) {
            writer.write(String.valueOf(match) + "\n");
            String result = "Took " + (System.currentTimeMillis() - time) + "ms.";
            writer.write(result);
        } else {
            writer.write("No match found");
        }
        
        return null;
    }
    
    private Runnable createSearcher(final String prefix, final String suffix, final String hash,
                                    final long searchStart, final long searchEnd) {
    
        Runnable r = new Runnable() {
            public void run() {
                
                int requiredNumbers = CARD_LENGTH - (prefix.length() + suffix.length());
                
                for (long i=searchStart; i<=searchEnd && match == null; i++) {
                    String cc = prefix + pad(i, requiredNumbers) + suffix;
                    if (luhn(cc) && WareHouse.getSha1String(cc).equals(hash)) {
                        match = cc;
                        break;
                    }
                }
            }
        };
        
        return r;
    }

 
    
    
    
    public static String pad(long num, int requiredNumbers) {
        String numString = String.valueOf(num);
        StringBuilder padded = new StringBuilder();
        for (int i = 0; i < requiredNumbers - numString.length(); i++) {
            padded.append("0");
        }
        return padded.toString() + numString;
    }
 

    public static boolean luhn(String cc) {
        int len = cc.length();
        int sum = 0, val, weight;

        weight = ((len % 2) == 0) ? 2 : 1;

        for (int i = 0; i != len; i++) {
            val = (cc.charAt(i) - '0') * weight;
            if (val > 9) val -= 9;

            sum += val;
            weight = (weight == 2) ? 1 : 2;
        }

        return (sum % 10) == 0;
    }
}
