/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package torrent;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.swing.JFileChooser;

public class Torrent3 {

    private static void encodeObject(Object o, OutputStream out) throws IOException {
        if (o instanceof String) {
            encodeString((String) o, out);
        } else if (o instanceof Map) {
            encodeMap((Map) o, out);
        } else if (o instanceof byte[]) {
            encodeBytes((byte[]) o, out);
        } else if (o instanceof Number) {
            encodeLong(((Number) o).longValue(), out);
        } else {
            throw new Error("Unencodable type");
        }
    }

    private static void encodeLong(long value, OutputStream out) throws IOException {
        out.write('i');
        out.write(Long.toString(value).getBytes("US-ASCII"));
        out.write('e');
    }

    private static void encodeBytes(byte[] bytes, OutputStream out) throws IOException {
        out.write(Integer.toString(bytes.length).getBytes("US-ASCII"));
        out.write(':');
        out.write(bytes);
    }

    private static void encodeString(String str, OutputStream out) throws IOException {
        encodeBytes(str.getBytes("UTF-8"), out);
    }

    private static void encodeMap(Map<String, Object> map, OutputStream out) throws IOException {
        // Sort the map. A generic encoder should sort by key bytes
        SortedMap<String, Object> sortedMap = new TreeMap<String, Object>(map);
        out.write('d');
        for (Entry<String, Object> e : sortedMap.entrySet()) {
            encodeString(e.getKey(), out);
            encodeObject(e.getValue(), out);
        }
        out.write('e');

    }
    static boolean firstrun = true;
    static Vector v = new Vector();

    private static byte[] hashPieces(File file, int pieceLength) throws IOException {
        MessageDigest sha1;
        //v = new Vector();
        int totalcount = 0;
        int newcount = 0;
        int totalbytes = 0;
        try {
            sha1 = MessageDigest.getInstance("SHA");
        } catch (NoSuchAlgorithmException e) {
            throw new Error("SHA1 not supported");
        }
        InputStream in = new FileInputStream(file);
        ByteArrayOutputStream pieces = new ByteArrayOutputStream();
        ByteArrayOutputStream pieces2 = new ByteArrayOutputStream();
        byte[] bytes = new byte[pieceLength];
        int pieceByteCount = 0, readCount = in.read(bytes, 0, pieceLength);
        OutputStream out = null;
        if (firstrun) {
            out = new FileOutputStream(new File(file.getPath() + "_ncz/source/" + file.getName() + ".1scount"));
        }
int stopper=0;byte[] sha;
        while (readCount != -1 && stopper<4000000) {
            newcount = 0;
            stopper++;
            pieceByteCount += readCount;
            sha1.update(bytes, 0, readCount);
            
            if (pieceByteCount == pieceLength) {
                pieceByteCount = 0;

                sha = sha1.digest();
                if(stopper>300000){
                pieces2.write(sha);
                }else{
                    pieces.write(sha);
                }
                //System.out.println(sha);
                // v.add(byteArrayToByteString(sha));

                //System.out.println(bitsInByteArray(bytes)+" :is the number of bits in the bytes"+countBits(bytes[0]));
                for (int i = 0; i < pieceLength; i++) {
                    newcount = newcount + countBits(bytes[i]);

                    totalbytes++;
                    // System.out.println("1s in byte: "+newcount);
                }
                totalcount = totalcount + newcount;
                if (firstrun) {
                    out.write(newcount);
                }

            } else {
                for (int i = 0; i < pieceByteCount; i++) {
                    newcount = newcount + countBits(bytes[i]);
                    totalbytes++;
                    // System.out.println("1s in byte: "+newcount);
                }
                totalcount = totalcount + newcount;
                if (firstrun) {
                    out.write(newcount);//store count of 1s per block
                    out.write(pieceByteCount);//store remainder blocks size
                }
                System.out.println(pieceLength + " byte piecelength has a remainder of " + pieceByteCount + " bytes left");
            }
            readCount = in.read(bytes, 0, pieceLength - pieceByteCount);
        }
        if (firstrun) {
            out.write(totalcount);//store total number of 1s
            out.write(pieceLength);//store block size in byte size pieces
            out.close();
        }
        in.close();
        if (pieceByteCount > 0) {
            // pieces.write(sha1.digest());
           sha = sha1.digest();
            pieces.write(sha);
            //System.out.println(sha);
            //v.add(byteArrayToByteString(sha));
            // System.out.println("v is:"+ byteArrayToByteString(sha));

        }

        System.out.println("total bytes: " + totalbytes + " - total 1s:" + totalcount + "\n");

        return pieces.toByteArray();
    }

    static public void zipFolder(String srcFolder, String destZipFile) throws Exception {
        ZipOutputStream zip = null;
        FileOutputStream fileWriter = null;
        fileWriter = new FileOutputStream(destZipFile);
        zip = new ZipOutputStream(fileWriter);
        addFolderToZip("", srcFolder, zip);
        zip.flush();
        zip.close();

    }

    static public void zipFolders(File[] srcFolder, String destZipFile) throws Exception {
        ZipOutputStream zip = null;
        FileOutputStream fileWriter = null;
        fileWriter = new FileOutputStream(destZipFile);
        zip = new ZipOutputStream(fileWriter);
        for (int i = 0; i < srcFolder.length; i++) {
            if (srcFolder[i].isDirectory()) {
                addFolderToZip("", srcFolder[i].toString(), zip);
            } else {
                addFileToZip("", srcFolder[i].toString(), zip);
            }
        }
        zip.flush();
        zip.close();

    }

    static private void addFileToZip(String path, String srcFile, ZipOutputStream zip)
            throws Exception {
        File folder = new File(srcFile);
        if (folder.isDirectory()) {
            addFolderToZip(path, srcFile, zip);
        } else {

            byte[] buf = new byte[1024];
            int len;
            FileInputStream in = new FileInputStream(srcFile);
            zip.putNextEntry(new ZipEntry(path + "/" + folder.getName()));
            while ((len = in.read(buf)) > 0) {
                zip.write(buf, 0, len);
            }
            in.close();

        }
    }

    static private void addFolderToZip(String path, String srcFolder, ZipOutputStream zip)
            throws Exception {
        File folder = new File(srcFolder);

        for (String fileName : folder.list()) {

            if (path.equals("")) {
                addFileToZip(folder.getName(), srcFolder + "/" + fileName, zip);

            } else {

                addFileToZip(path + "/" + folder.getName(), srcFolder + "/" + fileName, zip);
            }

        }

    }
    public static Vector v2 = new Vector();
    public static Vector perm = new Vector();
    public static Vector v3 = new Vector();
    public static Vector v4 = new Vector();
    public static BigInteger lcount = BigInteger.ONE;
    public static BigInteger lfinal = new BigInteger("4294967296");
    public static BigInteger ltemp = new BigInteger("0");
    public static int fcount = 0;

    public static int cores = Runtime.getRuntime().availableProcessors();

    public static void createTorrent(int size, File file, File sharedFile, String announceURL) throws IOException {
        //final int pieceLength = 1*128;
        final int pieceLength = size;
        Map<String, Object> info = new HashMap<String, Object>();
        info.put("name", sharedFile.getName());
        System.out.println("\n------------------------\nCreating File: " + file.getName() + " - Original File: " + sharedFile.getName() + " - File Size: " + sharedFile.length() + " - block size: " + pieceLength + " bytes");
        info.put("length", sharedFile.length());
        info.put("piece length", pieceLength);
        info.put("pieces", hashPieces(sharedFile, pieceLength));
        Map<String, Object> metainfo = new HashMap<String, Object>();
        metainfo.put("announce", announceURL);
        metainfo.put("info", info);
        OutputStream out = new FileOutputStream(file);
        encodeMap(metainfo, out);
        out.close();

        //Vector v = new Vector();
        // System.out.println("pieceshashcode: " + info.get("pieces").hashCode());
        //verify the files we just made is correct and valid
        //TorrentFileHandler tfh = new TorrentFileHandler();
       // TorrentFile tf = tfh.openTorrentFile(file.toString());
       // System.out.println("start verification\nfile size: " + tf.file_length + " bytes - tracker: " + tf.tracker_url
               // + " - hash: " + tf.info_hash_as_hex);
       // System.out.println("blocksize: " + tf.piece_length + " bytes - pieces: " + tf.piece_hash_values_as_hex.size() + "\nend verification\n");

        //System.out.println(v.size() + " blocks in the INPUT  file and the first checksum is: " + v.get(0));
        // System.out.println(v.size()+" vhex is: "+ byteArrayToByteString(v.get(0).hashCode()));
        //System.out.println(tf.piece_hash_values_as_binary.size()+" t is: "+ tf.piece_hash_values_as_binary.get(0).hashCode());
      //  System.out.println(tf.piece_hash_values_as_hex.size() + " blocks in the OUTPUT file and the first checksum is: " + tf.piece_hash_values_as_hex.get(0));

        // if(v.contains(tf.piece_hash_values_as_hex.get(0))){System.out.println("FOUND IT!!!!!!!!!!!!!!!");}
       // if (v.equals(tf.piece_hash_values_as_hex)) {
           // System.out.println("Output file(s) verified against the original file(s)! ALL CHECKSUMS MATCH!");
       // }

        //if building with 8bit file this will validate the rebuildability of the file by confimring only 256 possible checksums
        if (firstrun) {
            // tf.piece_hash_values_as_hex.get(0);
            int k = 0;
            int counter = 0;
            boolean go = true;
            boolean bit8 = false;
            boolean verify = false;
            int bitlevel = 0;

            //32 bit testing
            if (1 == 1) {
                cores = 1;
                MessageDigest sha1;
                try {
                    sha1 = MessageDigest.getInstance("SHA");
                } catch (NoSuchAlgorithmException e) {
                    throw new Error("SHA1 not supported");
                }
                int z1 = -128;
                int z2 = -128;
                int z3 = -128;
                int z4 = -128;
                // int lcount = 1;

                for (int i = 0; i < v.size(); i++) {
                    if (!v2.contains(v.get(i))) {
                        v2.add(v.get(i));
                    }
                }

                System.out.println(cores + " processors available, creating threads and dividing problem between threads\n"
                        + v2.size() + " unique bit patterns in the file out of a total of " + v.size() + " bit patterns");

                /*
                Scanner input = new Scanner(System.in);
                System.out.println("Please input the number of Threads you want to create: ");
                int n = input.nextInt();
                System.out.println("You selected " + n + " Threads");
                 */

 /*took out 7/3
                //
                for (int x = 0; x < cores; x++) {
                    MyThread temp = new MyThread("Thread #" + x, x);
                    perm = v;
                    temp.start();
                    System.out.println("Started Thread:" + x);
}
                //
                 */
 /*
                Vector v3 = new Vector();
                Vector v4 = new Vector();
                String bytestring = "";
                for (int i1 = 0; i1 < 256; i1++) {
                    for (int i2 = 0; i2 < 256; i2++) {
                        for (int i3 = 0; i3 < 256; i3++) {
                            for (int i4 = 0; i4 < 256; i4++) {
                                byte[] test = new byte[4];
                                test[0] = (byte) z1;
                                test[1] = (byte) z2;
                                test[2] = (byte) z3;
                                test[3] = (byte) z4;
                                sha1.update(test);
                                byte[] sha = sha1.digest();

                                //if(v2.contains(byteArrayToByteString(sha))){
                                bytestring = byteArrayToByteString(sha);
                                if (v2.contains(bytestring)) {
                                    v3.add(test);
                                    v4.add(bytestring);
                                    fcount++;
                                    ltemp = lcount.divide(lfinal);
                                    System.out.println((ltemp) + "% Complete - iteration: " + lcount + "/4,294,967,296 - We found a pattern!" + z1 + " " + z2 + " " + z3 + " " + z4 + " saving - " + fcount + "/" + v2.size() + " - " + (v2.size() - v3.size()) + " left - checksum: " + bytestring);

                                }

                                z4++;
                                //lcount++;
                                lcount = lcount.add(BigInteger.valueOf(1));

                                //System.out.println(z1 + " " + z2 + " " + z3 + " " + z4);
                                if (i4 == 255) {
                                    z4 = -128;
                                }
                            }
                            z3++;
                            if (i3 == 255) {
                                z3 = -128;
                            }
                        }
                        z2++;
                        if (i2 == 255) {
                            z2 = -128;
                        }
                    }
                    z1++;
                    if (i1 == 255) {
                        z1 = -128;
                    }
                }
                 */
            }

            //end 32 bit testing
            if (verify) {
                if (bit8) {
                    bitlevel = 256;
                } else {
                    bitlevel = 65535;
                }

                String[] bitpatterns = new String[bitlevel];
                for (int i = 0; i < bitlevel; i++) {
                    bitpatterns[i] = "";
                }
                for (int i = 0; i < v.size(); i++) {
                    for (int j = 0; j < bitlevel; j++) {
                        if (bitpatterns[j].equals(v.get(i) + "")) {
                            go = false;
                        }
                    }
                    if (go) {
                        bitpatterns[k] = v.get(i) + "";
                        System.out.println(k + " adding " + v.get(i));
                        k++;
                        go = true;
                        counter++;

                    } else {
                        //System.out.println(k + " already eists " + v.get(i));
                        go = true;
                        counter++;
                    }

                }
                System.out.println("TOTAL COUNTER LOOP IS: " + counter);

                //start tests
                MessageDigest sha1;
                try {
                    sha1 = MessageDigest.getInstance("SHA");
                } catch (NoSuchAlgorithmException e) {
                    throw new Error("SHA1 not supported");
                }

                int totalbytecount = 0;
                int notfound = 0;
                if (bit8) {
                    String[][] finalout = new String[256][2];
                    int z = -128;
                    for (int i = 0; i < 256; i++) {

                        byte test = (byte) z;
                        sha1.update(test);
                        byte[] sha = sha1.digest();
                        z++;

                        finalout[i][0] = byteArrayToByteString(sha);
                        finalout[i][1] = test + "";
                        //System.out.println(finalout[i][1] + " sha " + finalout[i][0]);

                        if (v.contains(finalout[i][0])) {
                            totalbytecount++;
                            System.out.println(finalout[i][1] + " sha " + finalout[i][0] + " matches our data: " + totalbytecount);
                        } else {
                            notfound++;
                            //System.out.println(finalout[i][1] + " sha " + finalout[i][0]);
                        }
                    }
                } else if (1 == 2) {//32 bit not finished as I do not have enough ram (memory) and other things to test for now

                    /*
                //String[][] finalout = new String[2147483647][2];
                String[][] finalout = new String[965535][2];
                int z = -482768;
                //for (int i = -2147483648; i < 2147483647; i++) {
                for (int i = 0; i < 965535; i++) {
                    byte[] test = intToBytes32(z);
                    sha1.update(test);
                    byte[] sha = sha1.digest();
                    z++;

                    finalout[i][0] = byteArrayToByteString(sha);
                    finalout[i][1] = test + "";
                    //System.out.println(finalout[i][1] + " sha " + finalout[i][0]);
                    if (v.contains(finalout[i][0])) {
                        totalbytecount++;
                        System.out.println(finalout[i][1] + " sha " + finalout[i][0] + " matches our data: " + totalbytecount);
                    } else {
                        notfound++;
                        //System.out.println(finalout[i][1] + " sha " + finalout[i][0]);
                    }
                }
                     */
                } else {
                    String[][] finalout = new String[65535][2];
                    int z = -32768;
                    for (int i = 0; i < 65535; i++) {

                        byte[] test = intToBytes(z);
                        sha1.update(test);
                        byte[] sha = sha1.digest();
                        z++;

                        finalout[i][0] = byteArrayToByteString(sha);
                        finalout[i][1] = test + "";
                        //System.out.println(finalout[i][1] + " sha " + finalout[i][0]);
                        if (v.contains(finalout[i][0])) {
                            totalbytecount++;
                            System.out.println(finalout[i][1] + " sha " + finalout[i][0] + " matches our data: " + totalbytecount);
                        } else {
                            notfound++;
                            //System.out.println(finalout[i][1] + " sha " + finalout[i][0]);
                        }
                    }
                }

                System.out.println("There were " + totalbytecount + " bit blocks that correspond to " + v.size() + " that were found in your compressed file.");
                System.out.println("There were " + (v.size() - totalbytecount) + " total blocks that were identical duplicates and will be reused hence bt block count: " + totalbytecount + " vs " + v.size());
                System.out.println("There were " + notfound + " bit blocks that were not found in your compressed file. (This is good)");
            }
            //end tests
        }

    }

    public static byte[] intToBytes32(final int i) {
        ByteBuffer bb = ByteBuffer.allocate(4);
        bb.putInt(i);

        return bb.array();
    }

    public static byte[] intToBytes(final int i) {
        ByteBuffer bb = ByteBuffer.allocate(4);
        bb.putInt(i);
        byte[] newbyte = bb.array();
        byte[] nbyte = new byte[2];
        nbyte[0] = newbyte[2];
        nbyte[1] = newbyte[3];
        return nbyte;
    }

    public static String byteArrayToByteString(byte in[]) {
        byte ch = 0x00;
        int i = 0;
        if (in == null || in.length <= 0) {
            return null;
        }

        String pseudo[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
            "A", "B", "C", "D", "E", "F"};
        StringBuffer out = new StringBuffer(in.length * 2);

        while (i < in.length) {
            ch = (byte) (in[i] & 0xF0); // Strip off high nibble
            ch = (byte) (ch >>> 4); // shift the bits down
            ch = (byte) (ch & 0x0F); // must do this is high order bit is on!
            out.append(pseudo[(int) ch]); // convert the nibble to a String
            // Character
            ch = (byte) (in[i] & 0x0F); // Strip off low nibble
            out.append(pseudo[(int) ch]); // convert the nibble to a String
            // Character
            i++;
        }

        String rslt = new String(out);

        return rslt;
    }

    public static int countBits(byte number) {
        if (number == 0) {
            return number;
        }
        int count = 0;
        while (number != 0) {
            number &= (number - 1);
            count++;
        }
        return count;
    }

    public static int bitsInByteArray(byte[] b) {
        return b.length * 8;
    }

    public static void delete(File index) {
        String[] entries = index.list();
        for (String s : entries) {
            System.out.println("delete");
            File currentFile = new File(index.getPath(), s);

            currentFile.delete();
        }
    }

    public static byte[] createChecksum(String filename) throws Exception {
        InputStream fis = new FileInputStream(filename);

        byte[] buffer = new byte[1024];
        MessageDigest complete = MessageDigest.getInstance("MD5");
        int numRead;

        do {
            numRead = fis.read(buffer);
            if (numRead > 0) {
                complete.update(buffer, 0, numRead);
            }
        } while (numRead != -1);

        fis.close();
        return complete.digest();
    }

    public static String getMD5Checksum(String filename) throws Exception {
        byte[] b = createChecksum(filename);
        String result = "";

        for (int i = 0; i < b.length; i++) {
            result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1);
        }
        return result;
    }

    //static boolean delete = true;
    static boolean delete = false;
//repeatedly reconstructable data deconstruction

    public static void main(String[] args) throws Exception {
        String directory = "C:/torrent/";
        String thefile = "mario";
        String filetype = "nes";
        int bytes = 128;
        int multiplier = 1;
        final JFileChooser fc = new JFileChooser();
        fc.setMultiSelectionEnabled(true);//multi file select not enabled yet needs code
        fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
        int returnVal = fc.showOpenDialog(null);
        thefile = thefile + "." + filetype;

        if (returnVal == 0) {
            File[] file = fc.getSelectedFiles();

            if (file.length == 1) {
                if (!file[0].isDirectory()) {
                    directory = file[0].getParent() + "/";
                    thefile = file[0].getName();
                } else {

                    zipFolder(file[0].getPath(), file[0].getPath() + "_nano.zip");
                    directory = file[0].getParent() + "/";
                    thefile = file[0].getName() + "_nano.zip";
                }
            } else {
                System.out.println("multi files");
                zipFolders(file, file[0].getPath() + "_nano.zip");
                directory = file[0].getParent() + "/";
                thefile = file[0].getName() + "_nano.zip";
            }
        }

        new File(directory + thefile + "_ncz\\source").mkdirs();
        String odir = directory;
        directory = directory + thefile + "_ncz/source/";
        File index = new File(directory);
        delete(index);
        System.out.println(thefile);
        System.out.println(directory);
        while (multiplier <= 2048) {
            createTorrent(multiplier * bytes, new File(directory + thefile + "." + multiplier + "x" + bytes + ".torrent"), new File(odir + thefile), "http://nanocheeze.com");
            if (bytes == 1024) {
                multiplier = multiplier * 2;
            } else {
                bytes = bytes * 2;
            }
            firstrun = false;
        }
        System.out.println("\n\n--------------\nCreating Checksums for inner zip");
        try {

            String[] entries = index.list();

            try {
                PrintWriter writer = new PrintWriter(directory + ".md5", "UTF-8");

                for (String s : entries) {

                    File currentFile = new File(index.getPath(), s);
                    String md5 = getMD5Checksum(currentFile.toString());
                    writer.println(currentFile.getName() + " - " + md5);
                    System.out.println(currentFile.toString() + " - " + md5);

                }
                writer.close();
            } catch (IOException e) {

            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(directory);
        zipFolder(directory, odir + thefile + "_ncz/" + thefile + ".ncz");

        if (!index.exists()) {
            //index.mkdir();
        } else if (delete) {
            delete(index);
            index.delete();

        }
    }
}