use std::comm;
use std::fmt::Show;
use std::io::ChanWriter;
use std::iter;
use std::rand;
use std::task::TaskBuilder;
use super::{Arbitrary, Gen, Shrinker, StdGen};
use tester::trap::safe;
use tester::Status::{Discard, Fail, Pass};

/// The main QuickCheck type for setting configuration and running QuickCheck.
pub struct QuickCheck<G> {
    tests: uint,
    max_tests: uint,
    gen: G,
}

impl QuickCheck<StdGen<rand::TaskRng>> {
    /// Creates a new QuickCheck value.
    ///
    /// This can be used to run QuickCheck on things that implement
    /// `Testable`. You may also adjust the configuration, such as
    /// the number of tests to run.
    ///
    /// By default, the maximum number of passed tests is set to `100`,
    /// the max number of overall tests is set to `10000` and the generator
    /// is set to a `StdGen` with a default size of `100`.
    pub fn new() -> QuickCheck<StdGen<rand::TaskRng>> {
        QuickCheck {
            tests: 100,
            max_tests: 10000,
            gen: StdGen::new(rand::task_rng(), 100),
        }
    }
}

impl<G: Gen> QuickCheck<G> {
    /// Set the number of tests to run.
    ///
    /// This actually refers to the maximum number of *passed* tests that
    /// can occur. Namely, if a test causes a failure, future testing on that
    /// property stops. Additionally, if tests are discarded, there may be
    /// fewer than `tests` passed.
    pub fn tests(mut self, tests: uint) -> QuickCheck<G> {
        self.tests = tests;
        self
    }

    /// Set the maximum number of tests to run.
    ///
    /// The number of invocations of a property will never exceed this number.
    /// This is necessary to cap the number of tests because QuickCheck
    /// properties can discard tests.
    pub fn max_tests(mut self, max_tests: uint) -> QuickCheck<G> {
        self.max_tests = max_tests;
        self
    }

    /// Set the random number generator to be used by QuickCheck.
    pub fn gen(mut self, gen: G) -> QuickCheck<G> {
        self.gen = gen;
        self
    }

    /// Tests a property and returns the result.
    ///
    /// The result returned is either the number of tests passed or a witness
    /// of failure.
    ///
    /// (If you're using Rust's unit testing infrastructure, then you'll
    /// want to use the `quickcheck` method, which will `panic!` on failure.)
    pub fn quicktest<A>(&mut self, f: A) -> Result<uint, TestResult>
                    where A: Testable {
        let mut ntests: uint = 0;
        for _ in iter::range(0, self.max_tests) {
            if ntests >= self.tests {
                break
            }
            let r = f.result(&mut self.gen);
            match r.status {
                Pass => ntests += 1,
                Discard => continue,
                Fail => return Err(r),
            }
        }
        Ok(ntests)
    }

    /// Tests a property and calls `panic!` on failure.
    ///
    /// The `panic!` message will include a (hopefully) minimal witness of
    /// failure.
    ///
    /// It is appropriate to use this method with Rust's unit testing
    /// infrastructure.
    ///
    /// Note that if the environment variable `RUST_LOG` is set to enable
    /// `info` level log messages for the `quickcheck` crate, then this will
    /// include output on how many QuickCheck tests were passed.
    ///
    /// # Example
    ///
    /// ```rust
    /// use quickcheck::QuickCheck;
    ///
    /// fn prop_reverse_reverse() {
    ///     fn revrev(xs: Vec<uint>) -> bool {
    ///         let rev: Vec<uint> = xs.clone().into_iter().rev().collect();
    ///         let revrev = rev.into_iter().rev().collect();
    ///         xs == revrev
    ///     }
    ///     QuickCheck::new().quickcheck(revrev);
    /// }
    /// ```
    pub fn quickcheck<A>(&mut self, f: A) where A: Testable {
        match self.quicktest(f) {
            Ok(ntests) => info!("(Passed {} QuickCheck tests.)", ntests),
            Err(result) => panic!(result.failed_msg()),
        }
    }
}

/// Convenience function for running QuickCheck.
///
/// This is an alias for `QuickCheck::new().quickcheck(f)`.
pub fn quickcheck<A: Testable>(f: A) { QuickCheck::new().quickcheck(f) }

/// Describes the status of a single instance of a test.
///
/// All testable things must be capable of producing a `TestResult`.
#[deriving(Clone, Show)]
pub struct TestResult {
    status: Status,
    arguments: Vec<String>,
    err: String,
}

/// Whether a test has passed, failed or been discarded.
#[deriving(Clone, Show)]
enum Status { Pass, Fail, Discard }

impl TestResult {
    /// Produces a test result that indicates the current test has passed.
    pub fn passed() -> TestResult { TestResult::from_bool(true) }

    /// Produces a test result that indicates the current test has failed.
    pub fn failed() -> TestResult { TestResult::from_bool(false) }

    /// Produces a test result that indicates failure from a runtime
    /// error.
    pub fn error(msg: &str) -> TestResult {
        let mut r = TestResult::from_bool(false);
        r.err = msg.to_string();
        r
    }

    /// Produces a test result that instructs `quickcheck` to ignore it.
    /// This is useful for restricting the domain of your properties.
    /// When a test is discarded, `quickcheck` will replace it with a
    /// fresh one (up to a certain limit).
    pub fn discard() -> TestResult {
        TestResult {
            status: Discard,
            arguments: vec![],
            err: "".to_string(),
        }
    }

    /// Converts a `bool` to a `TestResult`. A `true` value indicates that
    /// the test has passed and a `false` value indicates that the test
    /// has failed.
    pub fn from_bool(b: bool) -> TestResult {
        TestResult {
            status: if b { Pass } else { Fail },
            arguments: vec![],
            err: "".to_string(),
        }
    }

    /// Tests if a "procedure" fails when executed. The test passes only if
    /// `f` generates a task failure during its execution.
    pub fn must_fail<T: Send>(f: proc(): Send -> T) -> TestResult {
        let (tx, _) = comm::channel();
        TestResult::from_bool(
            TaskBuilder::new()
                        .stdout(box ChanWriter::new(tx.clone()))
                        .stderr(box ChanWriter::new(tx))
                        .try(f)
                        .is_err())
    }

    /// Returns `true` if and only if this test result describes a failing
    /// test.
    pub fn is_failure(&self) -> bool {
        match self.status {
            Fail => true,
            Pass|Discard => false,
        }
    }

    /// Returns `true` if and only if this test result describes a failing
    /// test as a result of a run time error.
    pub fn is_error(&self) -> bool {
        self.is_failure() && self.err.len() > 0
    }

    fn failed_msg(&self) -> String {
        if self.err.len() == 0 {
            format!(
                "[quickcheck] TEST FAILED. Arguments: ({})",
                self.arguments.connect(", "))
        } else {
            format!(
                "[quickcheck] TEST FAILED (runtime error). \
                Arguments: ({})\nError: {}",
                self.arguments.connect(", "), self.err)
        }
    }
}

/// `Testable` describes types (e.g., a function) whose values can be 
/// tested.
///
/// Anything that can be tested must be capable of producing a `TestResult`
/// given a random number generator. This is trivial for types like `bool`,
/// which are just converted to either a passing or failing test result.
///
/// For functions, an implementation must generate random arguments
/// and potentially shrink those arguments if they produce a failure.
///
/// It's unlikely that you'll have to implement this trait yourself.
/// This comes with a caveat: currently, only functions with 4 parameters 
/// or fewer (both `fn` and `||` types) satisfy `Testable`. If you have
/// functions to test with more than 4 parameters, please
/// [file a bug](https://github.com/BurntSushi/quickcheck/issues) and
/// I'll hopefully add it. (As of now, it would be very difficult to
/// add your own implementation outside of `quickcheck`, since the
/// functions that do shrinking are not public.)
pub trait Testable : Send {
    fn result<G: Gen>(&self, &mut G) -> TestResult;
}

impl Testable for bool {
    fn result<G: Gen>(&self, _: &mut G) -> TestResult {
        TestResult::from_bool(*self)
    }
}

impl Testable for TestResult {
    fn result<G: Gen>(&self, _: &mut G) -> TestResult { self.clone() }
}

impl<A> Testable for Result<A, String> where A: Testable {
    fn result<G: Gen>(&self, g: &mut G) -> TestResult {
        match *self {
            Ok(ref r) => r.result(g),
            Err(ref err) => TestResult::error(err.as_slice()),
        }
    }
}

impl<T> Testable for fn() -> T where T: Testable {
    fn result<G: Gen>(&self, g: &mut G) -> TestResult {
        shrink::<G, T, (), (), (), (), fn() -> T>(g, self)
    }
}

impl<A, T> Testable for fn(A) -> T where A: AShow, T: Testable {
    fn result<G: Gen>(&self, g: &mut G) -> TestResult {
        shrink::<G, T, A, (), (), (), fn(A) -> T>(g, self)
    }
}

impl<A, B, T> Testable for fn(A, B) -> T
    where A: AShow, B: AShow, T: Testable {
    fn result<G: Gen>(&self, g: &mut G) -> TestResult {
        shrink::<G, T, A, B, (), (), fn(A, B) -> T>(g, self)
    }
}

impl<A, B, C, T> Testable for fn(A, B, C) -> T
    where A: AShow, B: AShow, C: AShow, T: Testable {
    fn result<G: Gen>(&self, g: &mut G) -> TestResult {
        shrink::<G, T, A, B, C, (), fn(A, B, C) -> T>(g, self)
    }
}

impl<A, B, C, D, T,> Testable for fn(A, B, C, D) -> T
    where A: AShow, B: AShow, C: AShow, D: AShow, T: Testable {
    fn result<G: Gen>(&self, g: &mut G) -> TestResult {
        shrink::<G, T, A, B, C, D, fn(A, B, C, D) -> T>(g, self)
    }
}

trait Fun<A, B, C, D, T> {
    fn call<G>(&self, g: &mut G,
               a: Option<&A>, b: Option<&B>,
               c: Option<&C>, d: Option<&D>)
              -> TestResult
              where G: Gen;
}

macro_rules! impl_fun_call(
    ($f:expr, $g:expr, $($name:ident,)+) => ({
        let ($($name,)*) = ($($name.unwrap(),)*);
        let f = $f;
        let mut r = {
            let ($($name,)*) = ($(box $name.clone(),)*);
            safe(proc() { f($(*$name,)*) }).result($g)
        };
        if r.is_failure() {
            r.arguments = vec![$($name.to_string(),)*];
        }
        r
    });
)

impl<A, B, C, D, T> Fun<A, B, C, D, T> for fn() -> T
    where A: AShow, B: AShow, C: AShow, D: AShow, T: Testable {
    fn call<G: Gen>(&self, g: &mut G,
                    _: Option<&A>, _: Option<&B>,
                    _: Option<&C>, _: Option<&D>)
                   -> TestResult 

}

impl<A, B, C, D, T> Fun<A, B, C, D, T> for fn(A) -> T
    where A: AShow, B: AShow, C: AShow, D: AShow, T: Testable {
    fn call<G: Gen>(&self, g: &mut G,
                    a: Option<&A>, _: Option<&B>,
                    _: Option<&C>, _: Option<&D>)
                   -> TestResult {
        impl_fun_call!(*self, g, a,)
    }
}

impl<A, B, C, D, T> Fun<A, B, C, D, T> for fn(A, B) -> T
    where A: AShow, B: AShow, C: AShow, D: AShow, T: Testable {
    fn call<G: Gen>(&self, g: &mut G,
                    a: Option<&A>, b: Option<&B>,
                    _: Option<&C>, _: Option<&D>)
                   -> TestResult {
        impl_fun_call!(*self, g, a, b,)
    }
}

impl<A, B, C, D, T> Fun<A, B, C, D, T> for fn(A, B, C) -> T
    where A: AShow, B: AShow, C: AShow, D: AShow, T: Testable {
    fn call<G: Gen>(&self, g: &mut G,
                    a: Option<&A>, b: Option<&B>,
                    c: Option<&C>, _: Option<&D>)
                   -> TestResult {
        impl_fun_call!(*self, g, a, b, c,)
    }
}

impl<A, B, C, D, T> Fun<A, B, C, D, T> for fn(A, B, C, D) -> T
    where A: AShow, B: AShow, C: AShow, D: AShow, T: Testable {
    fn call<G: Gen>(&self, g: &mut G,
                    a: Option<&A>, b: Option<&B>,
                    c: Option<&C>, d: Option<&D>)
                   -> TestResult {
        impl_fun_call!(*self, g, a, b, c, d,)
    }
}

fn shrink<G, T, A, B, C, D, F>(g: &mut G, fun: &F) -> TestResult
    where G: Gen, T: Testable, A: AShow, B: AShow, C: AShow, D: AShow,
          F: Fun<A, B, C, D, T> {
    let (a, b, c, d): (A, B, C, D) = arby(g);
    let r = fun.call(g, Some(&a), Some(&b), Some(&c), Some(&d));
    match r.status {
        Pass|Discard => r,
        Fail => shrink_failure(g, (a, b, c, d).shrink(), fun).unwrap_or(r),
    }
}

fn shrink_failure<G, T, A, B, C, D, F>
                 (g: &mut G,
                  mut shrinker: Box<Shrinker<(A, B, C, D)>+'static>,
                  fun: &F)
                 -> Option<TestResult>
    where G: Gen, T: Testable, A: AShow, B: AShow, C: AShow, D: AShow,
          F: Fun<A, B, C, D, T> {
    for (a, b, c, d) in shrinker {
        let r = fun.call(g, Some(&a), Some(&b), Some(&c), Some(&d));
        match r.status {
            // The shrunk value does not witness a failure, so
            // throw it away.
            Pass|Discard => continue,

            // The shrunk value *does* witness a failure, so keep trying
            // to shrink it.
            Fail => {
                let shrunk = shrink_failure(g, (a, b, c, d).shrink(), fun);

                // If we couldn't witness a failure on any shrunk value,
                // then return the failure we already have.
                return Some(shrunk.unwrap_or(r))
            },
        }
    }
    None
}

#[cfg(quickfail)]
mod trap {
    pub fn safe<T: Send>(fun: proc() -> T) -> Result<T, String> {
        Ok(fun())
    }
}

#[cfg(not(quickfail))]
mod trap {
    use std::comm::channel;
    use std::io::{ChanReader, ChanWriter};
    use std::task::TaskBuilder;

    // This is my bright idea for capturing runtime errors caused by a
    // test. Actually, it looks like rustc uses a similar approach.
    // The problem is, this is used for *each* test case passed to a
    // property, whereas rustc does it once for each test.
    //
    // I'm not entirely sure there's much of an alternative either.
    // We could launch a single task and pass arguments over a channel,
    // but the task would need to be restarted if it failed due to a
    // runtime error. Since these are rare, it'd probably be more efficient
    // then this approach, but it would also be more complex.
    //
    // Moreover, this feature seems to prevent an implementation of
    // Testable for a stack closure type. *sigh*
    pub fn safe<T: Send>(fun: proc():Send -> T) -> Result<T, String> {
        let (send, recv) = channel();
        let stdout = ChanWriter::new(send.clone());
        let stderr = ChanWriter::new(send);
        let mut reader = ChanReader::new(recv);

        let t = TaskBuilder::new()
                            .named("safefn")
                            .stdout(box stdout)
                            .stderr(box stderr);
        match t.try(fun) {
            Ok(v) => Ok(v),
            Err(_) => {
                let s = reader.read_to_string().unwrap();
                Err(s.as_slice().trim().into_string())
            }
        }
    }
}

/// Convenient aliases.
trait AShow : Arbitrary + Show {}
impl<A: Arbitrary + Show> AShow for A {}
fn arby<A: Arbitrary, G: Gen>(g: &mut G) -> A { Arbitrary::arbitrary(g) }

{
        let f = *self;
        safe(proc() { f() }).result(g)
    }