<commit_before>#![feature(core)]
#![feature(collections)]

#![feature(plugin)]
#![plugin(regex_macros)] extern crate regex;
extern crate git2;
extern crate core;
extern crate rustc_serialize;
extern crate docopt;

use git2::{Repository, Commit, Diff, DiffFormat, Oid};
use docopt::Docopt;
use std::collections::HashMap;
// use std::old_io::BufferedReader;
// use std::old_io::File;
use std::str;
// use regex::Regex;
use rustc_serialize::json;
use std::fmt::Display;
use std::path::Path;

// Write the Docopt usage string.
static USAGE: &'static str = "
Usage: transit [--web | <repo> [<old> <new>] | --help]

Examples:
  transit --web             Spawn a web service.
  transit $REPO             Output the results of a revwalk through a repo.
  transit $REPO $ID1 $ID2   Output the data for a pair of commits.
  transit --help            Display this message.


Output is in JSON.
";

#[derive(RustcDecodable, Debug)]
struct Args {
    flag_web: bool,
    arg_repo: Option<String>,
    arg_old: Option<String>,
    arg_new: Option<String>,
}

fn main() {
    // Parse the args above or die.
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    print!("{:?}", args);

    if args.flag_web {
        unimplemented!();
    } else if let Some(path_string) = args.arg_repo {
        // Pull up the revwalk.
        let path = Path::new(&path_string);
        let repo = Repository::discover(&path)
            .ok().expect("Unable to find repo.");
        // If we can destructures the two optional args into real things.
        if let (Some(old_string), Some(new_string)) = (args.arg_old, args.arg_new) {
            // Compare a specific commit pair.
            // Fist, get the commits. (Error checked)
            let old_id = Oid::from_str(&old_string[..])
                .ok().expect("Couldn't parse old ID");
            let old = repo.find_commit(old_id);
            let new_id = Oid::from_str(&new_string[..])
                .ok().expect("Couldn't parse new ID");
            let new = repo.find_commit(new_id);
            if old.is_ok() && new.is_ok() {
                let (old_commit, new_commit) = (old.unwrap(), new.unwrap());
                let output = find_moves(&repo, &old_commit, &new_commit).unwrap();
                make_json(vec![OutputSet {
                    old: TransitOid(old_id),
                    old_time: old_commit.time().seconds(), // Seconds from Epoch
                    new: TransitOid(new_id),
                    new_time: new_commit.time().seconds(), // Seconds from Epoch
                    outputs: output,
                }]);
            } else {
                panic!("Commit ids were not valid.");
            }
        } else {
            // Revwalk.
            let mut revwalk = repo.revwalk()
                .ok().expect("Unable to get revwalk.");
            // Setup some options.
            revwalk.simplify_first_parent(); // TODO: Maybe remove?
            let mut flags = git2::Sort::empty();
            flags.insert(git2::SORT_TIME);
            flags.insert(git2::SORT_TOPOLOGICAL);
            revwalk.set_sorting(flags);
            // Push HEAD to the revwalk.
            revwalk.push_head()
                .ok().expect("Unable to push HEAD.");
            // We sadly must collect here to use `.windows()`
            let history = revwalk.collect::<Vec<Oid>>();
            let mut output = Vec::with_capacity(history.len());
            // Walk through each pair of commits.
            for pair in history.windows(2) {
                let (old_id, new_id) = (pair[0], pair[1]);
                if let (Ok(old), Ok(new)) = (repo.find_commit(old_id), repo.find_commit(new_id)) {
                    let detected = find_moves(&repo, &old, &new).unwrap();
                    output.push(OutputSet {
                        old: TransitOid(old_id),
                        old_time: old.time().seconds(), // Seconds from Epoch
                        new: TransitOid(new_id),
                        new_time: new.time().seconds(), // Seconds from Epoch
                        outputs: detected,
                    });
                } else {
                    continue;
                }
            }
            make_json(output);
        }
    } else {
        unreachable!();
    }
}

fn make_json(output: Vec<OutputSet>) {
    let out = json::as_pretty_json(&output).indent(4);
    println!("{}", out);
}

#[derive(Debug, PartialEq, Eq)]
enum FoundState {
    Added, Deleted
}

fn format_key(key: String) -> String {
    let remove_whitespace = regex!(r"\s{2,}"); // 2 or more whitespaces    // TODO Removes whitespace from a string.
    let trim = regex!(r"^[\s]+|[\s]+$");
    let result = remove_whitespace.replace_all(&key[..], "");
    trim.replace_all(&result[..], "")
}

#[derive(Debug)]
struct Found {
    filename: String,
    key: String,
    state: FoundState,
    start_position: u32,
    line_count: u32,
}

fn find_additions_and_deletions(diff: Diff) -> Vec<Found> {

    #[derive(Debug)]
    enum State {
        Other, Addition, Deletion
    }

    let mut founds: Vec<Found> = Vec::new();

    let mut state = State::Other;
    let mut added = String::new();
    let mut deleted = String::new();
    let mut old_path = String::new();
    let mut new_path = String::new();

    let mut line_count: u32 = 0;
    let mut start_position: u32 = 0;

    // Read about this function in http://alexcrichton.com/git2-rs/git2/struct.Diff.html#method.print
    // It's a bit weird, but I think it will provide the necessary information.
    diff.print(DiffFormat::Patch, |delta, maybe_hunk, line| -> bool {

        // if delta.nfiles() != 2 {
        //     // This is diff with only one side, and thus can't have a move.
        //     return true;
        // }
        // assert!(delta.nfiles() == 2, "This only works on diffs between exactly 2 files. Found {} files.", delta.nfiles());

        // Thinking:
        //  * If is not a hunk, keep going.
        //  * If it's a hunk, do regex magic.<commit_msg>We'll want state outside of find<commit_after>#![feature(core)]
#![feature(collections)]

#![feature(plugin)]
#![plugin(regex_macros)] extern crate regex;
extern crate git2;
extern crate core;
extern crate rustc_serialize;
extern crate docopt;

use git2::{Repository, Commit, Diff, DiffFormat, Oid};
use docopt::Docopt;
use std::collections::HashMap;
// use std::old_io::BufferedReader;
// use std::old_io::File;
use std::str;
// use regex::Regex;
use rustc_serialize::json;
use std::fmt::Display;
use std::path::Path;

// Write the Docopt usage string.
static USAGE: &'static str = "
Usage: transit [--web | <repo> [<old> <new>] | --help]

Examples:
  transit --web             Spawn a web service.
  transit $REPO             Output the results of a revwalk through a repo.
  transit $REPO $ID1 $ID2   Output the data for a pair of commits.
  transit --help            Display this message.


Output is in JSON.
";

#[derive(RustcDecodable, Debug)]
struct Args {
    flag_web: bool,
    arg_repo: Option<String>,
    arg_old: Option<String>,
    arg_new: Option<String>,
}

fn main() {
    // Parse the args above or die.
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    print!("{:?}", args);

    if args.flag_web {
        unimplemented!();
    } else if let Some(path_string) = args.arg_repo {
        // Pull up the revwalk.
        let path = Path::new(&path_string);
        let repo = Repository::discover(&path)
            .ok().expect("Unable to find repo.");
        // If we can destructures the two optional args into real things.
        if let (Some(old_string), Some(new_string)) = (args.arg_old, args.arg_new) {
            // Compare a specific commit pair.
            // Fist, get the commits. (Error checked)
            let old_id = Oid::from_str(&old_string[..])
                .ok().expect("Couldn't parse old ID");
            let old = repo.find_commit(old_id);
            let new_id = Oid::from_str(&new_string[..])
                .ok().expect("Couldn't parse new ID");
            let new = repo.find_commit(new_id);
            if old.is_ok() && new.is_ok() {
                let (old_commit, new_commit) = (old.unwrap(), new.unwrap());
                let output = find_moves(&repo, &old_commit, &new_commit).unwrap();
                make_json(vec![OutputSet {
                    old: TransitOid(old_id),
                    old_time: old_commit.time().seconds(), // Seconds from Epoch
                    new: TransitOid(new_id),
                    new_time: new_commit.time().seconds(), // Seconds from Epoch
                    outputs: output,
                }]);
            } else {
                panic!("Commit ids were not valid.");
            }
        } else {
            // Revwalk.
            let mut revwalk = repo.revwalk()
                .ok().expect("Unable to get revwalk.");
            // Setup some options.
            revwalk.simplify_first_parent(); // TODO: Maybe remove?
            let mut flags = git2::Sort::empty();
            flags.insert(git2::SORT_TIME);
            flags.insert(git2::SORT_TOPOLOGICAL);
            revwalk.set_sorting(flags);
            // Push HEAD to the revwalk.
            revwalk.push_head()
                .ok().expect("Unable to push HEAD.");
            // We sadly must collect here to use `.windows()`
            let history = revwalk.collect::<Vec<Oid>>();
            let mut output = Vec::with_capacity(history.len());
            // Walk through each pair of commits.
            for pair in history.windows(2) {
                let (old_id, new_id) = (pair[0], pair[1]);
                if let (Ok(old), Ok(new)) = (repo.find_commit(old_id), repo.find_commit(new_id)) {
                    let detected = find_moves(&repo, &old, &new).unwrap();
                    output.push(OutputSet {
                        old: TransitOid(old_id),
                        old_time: old.time().seconds(), // Seconds from Epoch
                        new: TransitOid(new_id),
                        new_time: new.time().seconds(), // Seconds from Epoch
                        outputs: detected,
                    });
                } else {
                    continue;
                }
            }
            make_json(output);
        }
    } else {
        unreachable!();
    }
}

fn make_json(output: Vec<OutputSet>) {
    let out = json::as_pretty_json(&output).indent(4);
    println!("{}", out);
}

#[derive(Debug, PartialEq, Eq)]
enum FoundState {
    Added, Deleted
}

fn format_key(key: String) -> String {
    let remove_whitespace = regex!(r"\s{2,}"); // 2 or more whitespaces    // TODO Removes whitespace from a string.
    let trim = regex!(r"^[\s]+|[\s]+$");
    let result = remove_whitespace.replace_all(&key[..], "");
    trim.replace_all(&result[..], "")
}

#[derive(Debug)]
struct Found {
    filename: String,
    key: String,
    state: FoundState,
    start_position: u32,
    line_count: u32,
}

#[derive(Debug)]
enum State {
    Other, Addition, Deletion
}

fn find_additions_and_deletions(diff: Diff) -> Vec<Found> {

    let mut founds: Vec<Found> = Vec::new();

    let mut state = State::Other;
    let mut added = String::new();
    let mut deleted = String::new();
    let mut old_path = String::new();
    let mut new_path = String::new();

    let mut line_count: u32 = 0;
    let mut start_position: u32 = 0;

    // Read about this function in http://alexcrichton.com/git2-rs/git2/struct.Diff.html#method.print
    // It's a bit weird, but I think it will provide the necessary information.
    diff.print(DiffFormat::Patch, |delta, maybe_hunk, line| -> bool {

        // if delta.nfiles() != 2 {
        //     // This is diff with only one side, and thus can't have a move.
        //     return true;
        // }
        // assert!(delta.nfiles() == 2, "This only works on diffs between exactly 2 files. Found {} files.", delta.nfiles());

        // Thinking:
        //  * If is not a hunk, keep going.
        //  * If it's a hunk, do regex magic.
{'fraction_non_alphanumeric': 0.13984690097950447, 'fraction_numerical': 0.0046094328751337555, 'mean_word_length': 2.0238924838227974, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '445327', 'n_tokens_mistral': 3758, 'n_tokens_neox': 3653, 'n_words': 1002}