/*
Copyright â“’ 2016 rust-custom-derive contributors.

Licensed under the MIT license (see LICENSE or <http://opensource.org
/licenses/MIT>) or the Apache License, Version 2.0 (see LICENSE of
<http://www.apache.org/licenses/LICENSE-2.0>), at your option. All
files in the project carrying such notice may not be copied, modified,
or distributed except according to those terms.
*/
#![cfg(channel="nightly")]
#![feature(intrinsics)]
#![cfg_attr(feature="parse-generics-poc", feature(plugin))]
#![cfg_attr(feature="parse-generics-poc", plugin(parse_generics_poc))]
#[macro_use] extern crate parse_generics_shim;

#[macro_use] extern crate custom_derive;
#[macro_use] extern crate parse_macros;

use std::cmp::Ordering;

extern "rust-intrinsic" {
    pub fn discriminant_value<T>(v: &T) -> u64;
}

macro_rules! PartialOrd_mac {
    (
        () $($tail:tt)*
    ) => {
        parse_item! {
            then PartialOrd_mac! { @item },
            $($tail)*
        }
    };

    (
        @item
        enum {
            attrs: $_attrs:tt,
            vis: $_vis:tt,
            name: $name:ident,
            generics: {
                constr: [$($constr:tt)*],
                params: [$($params:tt)*],
                ltimes: $_ltimes:tt,
                tnames: [$($tnames:ident,)*],
            },
            where: {
                clause: $_clause:tt,
                preds: [$($preds:tt)*],
            },
            variants: [],
            $($_enum_tail:tt)*
        }
    ) => {
        PartialOrd_mac! {
            @inject_where
            (impl<$($constr)*> PartialOrd for $name<$($params)*>),
            where ($($tnames: PartialOrd,)* $($preds)*)
            ({
                fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
                    Some(Ordering::Equal)
                }
            })
        }
    };

    (
        @item
        enum {
            attrs: $_attrs:tt,
            vis: $_vis:tt,
            name: $name:ident,
            generics: {
                constr: [$($constr:tt)*],
                params: [$($params:tt)*],
                ltimes: $_ltimes:tt,
                tnames: [$($tnames:ident,)*],
            },
            where: {
                clause: $_clause:tt,
                preds: [$($preds:tt)*],
            },
            variants: [$var:tt,],
            $($_enum_tail:tt)*
        }
    ) => {
        PartialOrd_mac! {
            @inject_where
            (impl<$($constr)*> PartialOrd for $name<$($params)*>),
            where ($($tnames: PartialOrd,)* $($preds)*)
            ({
                fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
                    match (self, other) {
                        PartialOrd_mac!(@var_match_pat other, $name, $var) => PartialOrd_mac!(@var_match_body other, $name, $var),
                    }
                }
            })
        }
    };

    (
        @item
        enum {
            attrs: $_attrs:tt,
            vis: $_vis:tt,
            name: $name:ident,
            generics: {
                constr: [$($constr:tt)*],
                params: [$($params:tt)*],
                ltimes: $_ltimes:tt,
                tnames: [$($tnames:ident,)*],
            },
            where: {
                clause: $_clause:tt,
                preds: [$($preds:tt)*],
            },
            variants: [$($vars:tt,)*],
            $($_enum_tail:tt)*
        }
    ) => {
        PartialOrd_mac! {
            @inject_where
            (impl<$($constr)*> PartialOrd for $name<$($params)*>),
            where ($($tnames: PartialOrd,)* $($preds)*)
            ({
                fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
                    let sd = unsafe { discriminant_value(self) };
                    let od = unsafe { discriminant_value(other) };

                    if sd != od {
                        return sd.partial_cmp(&od);
                    }

                    match (self, other) {
                        $(
                            PartialOrd_mac!(@var_match_pat other, $name, $vars) => PartialOrd_mac!(@var_match_body other, $name, $vars),
                        )*
                        _ => unreachable!()
                    }
                }
            })
        }
    };

    (
        @item
        struct {
            attrs: $_attrs:tt,
            vis: $_vis:tt,
            name: $name:ident,
            generics: {
                constr: [$($constr:tt)*],
                params: [$($params:tt)*],
                ltimes: $_ltimes:tt,
                tnames: [$($tnames:ident,)*],
            },
            where: {
                clause: $_clause:tt,
                preds: [$($preds:tt)*],
            },
            kind: unitary,
            fields: [],
            $($_struct_tail:tt)*
        }
    ) => {
        PartialOrd_mac! {
            @inject_where
            (impl<$($constr)*> PartialOrd for $name<$($params)*>),
            where ($($tnames: PartialOrd,)* $($preds)*)
            ({
                fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
                    Some(Ordering::Equal)
                }
            })
        }
    };

    (
        @item
        struct {
            attrs: $_attrs:tt,
            vis: $_vis:tt,
            name: $name:ident,
            generics: {
                constr: [$($constr:tt)*],
                params: [$($params:tt)*],
                ltimes: $_ltimes:tt,
                tnames: [$($tnames:ident,)*],
            },
            where: {
                clause: $_clause:tt,
                preds: [$($preds:tt)*],
            },
            kind: tuple,
            fields: [$(
                {
                    ord: ($ford:tt, $_ford_ident:ident),
                    attrs: $_fattrs:tt,
                    vis: $_fvis:tt,
                    ty: $_fty:ty,
                },
            )*],
            $($_struct_tail:tt)*
        }
    ) => {
        PartialOrd_mac! {
            @inject_where
            (impl<$($constr)*> PartialOrd for $name<$($params)*>),
            where ($($tnames: PartialOrd,)* $($preds)*)
            ({
                fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
                    $(
                        PartialOrd_mac!(@as_expr
                            match (self.$ford).partial_cmp(&other.$ford) {
                                Some(Ordering::Equal) => (),
                                other => return other
                            }
                        );
                    )*
                    Some(Ordering::Equal)
                }
            })
        }
    };

    (
        @item
        struct {
            attrs: $_attrs:tt,
            vis: $_vis:tt,
            name: $name:ident,
            generics: {
                constr: [$($constr:tt)*],
                params: [$($params:tt)*],
                ltimes: $_ltimes:tt,
                tnames: [$($tnames:ident,)*],
            },
            where: {
                clause: $_clause:tt,
                preds: [$($preds:tt)*],
            },
            kind: record,
            fields: [$(
                {
                    ord: $_ford:tt,
                    attrs: $_fattrs:tt,
                    vis: $_fvis:tt,
                    ty: $_fty:ty,
                    name: $fname:ident,
                },
            )*],
            $($_struct_tail:tt)*
        }
    ) => {
        PartialOrd_mac! {
            @inject_where
            (impl<$($constr)*> PartialOrd for $name<$($params)*>),
            where ($($tnames: PartialOrd,)* $($preds)*)
            ({
                fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
                    $(
                        match self.$fname.partial_cmp(&other.$fname) {
                            Some(Ordering::Equal) => (),
                            other => return other
                        }
                    )*
                    Some(Ordering::Equal)
                }
            })
        }
    };

    (
        @var_match_pat
        $_other:ident,
        $name:ident,
        {
            ord: $_ord:tt,
            attrs: $_attrs:tt,
            kind: unitary,
            name: $vname:ident,
            fields: [],
            num_fields: 0,
        }
    ) => {
        (&$name::$vname, &$name::$vname)
    };

    (
        @var_match_body
        $_other:ident,
        $name:ident,
        {
            ord: $_ord:tt,
            attrs: $_attrs:tt,
            kind: unitary,
            name: $vname:ident,
            fields: [],
            num_fields: 0,
        }
    ) => {
        Some(Ordering::Equal)
    };

    (
        @var_match_pat
        $other:ident,
        $name:ident,
        {
            ord: $_ord:tt,
            attrs: $_attrs:tt,
            kind: tuple,
            name: $vname:ident,
            fields: [
                $(
                    {
                        ord: ($_ford:tt, $ford_ident:ident),
                        attrs: $_fattrs:tt,
                        vis: $_fvis:tt,
                        ty: $_fty:ty,
                    },
                )+
            ],
            num_fields: $_num_fields:tt,
        }
    ) => {
        (&$name::$vname($(ref $ford_ident,)+), $other)
    };

    (
        @var_match_body
        $other:ident,
        $name:ident,
        {
            ord: $_ord:tt,
            attrs: $_attrs:tt,
            kind: tuple,
            name: $vname:ident,
            fields: [
                $(
                    {
                        ord: ($ford:tt, $ford_ident:ident),
                        attrs: $_fattrs:tt,
                        vis: $_fvis:tt,
                        ty: $_fty:ty,
                    },
                )+
            ],
            num_fields: $_num_fields:tt,
        }
    ) => {
        {
            let lhs = ($($ford_ident,)+);
            match $other {
                &$name::$vname($(ref $ford_ident,)+) => {
                    let rhs = ($($ford_ident,)+);
                    $(
                        match PartialOrd_mac!(@as_expr (lhs.$ford).partial_cmp(&rhs.$ford)) {
                            Some(Ordering::Equal) => (),
                            other => return other
                        }
                    )+
                    Some(Ordering::Equal)
                },
                _ => unreachable!()
            }
        }
    };

    (
        @var_match_pat
        $_other:ident,
        $name:ident,
        {
            ord: $_ord:tt,
            attrs: $_attrs:tt,
            kind: record,
            name: $vname:ident,
            fields: [
                $(
                    {
                        ord: ($_ford:tt, $ford_ident:ident),
                        attrs: $_fattrs:tt,
                        vis: $_fvis:tt,
                        ty: $_fty:ty,
                        name: $fname:ident,
                    },
                )+
            ],
            num_fields: $_num_fields:tt,
        }
    ) => {
        (&$name::$vname { $(ref $fname,)+ },
            &$name::$vname { $($fname: ref $ford_ident,)+ })
    };

    (
        @var_match_body
        $_other:ident,
        $name:ident,
        {
            ord: $_ord:tt,
            attrs: $_attrs:tt,
            kind: record,
            name: $vname:ident,
            fields: [
                $(
                    {
                        ord: ($_ford:tt, $ford_ident:ident),
                        attrs: $_fattrs:tt,
                        vis: $_fvis:tt,
                        ty: $_fty:ty,
                        name: $fname:ident,
                    },
                )+
            ],
            num_fields: $_num_fields:tt,
        }
    ) => {
        {
            $(
                match $fname.partial_cmp(&$ford_ident) {
                    Some(Ordering::Equal) => (),
                    other => return other
                }
            )+
            Some(Ordering::Equal)
        }
    };

    (
        @inject_where
        ($($before:tt)*),
        where ($(,)*)
        ($($after:tt)*)
    ) => {
        PartialOrd_mac! {
            @as_item
            $($before)* $($after)*
        }
    };

    (
        @inject_where
        ($($before:tt)*),
        where ($($preds:tt)+)
        ($($after:tt)*)
    ) => {
        PartialOrd_mac! {
            @as_item
            $($before)* where $($preds)* $($after)*
        }
    };

    (@as_expr $e:expr) => { $e };
    (@as_item $i:item) => { $i };
}

custom_derive! {
    #[derive(PartialEq, PartialOrd_mac)]
    enum EnumA {}
}

custom_derive! {
    #[derive(PartialEq, PartialOrd_mac)]
    enum EnumB { A }
}

custom_derive! {
    #[derive(PartialEq, PartialOrd_mac)]
    enum EnumC { A, B, C }
}

custom_derive! {
    #[derive(PartialEq, PartialOrd_mac)]
    enum EnumD { A, B(i32), C(u8, u8, u8) }
}

custom_derive! {
    #[derive(PartialEq, PartialOrd_mac)]
    enum EnumE { A { r: u8, g: u8, b: u8, } }
}

custom_derive! {
    #[derive(PartialEq, PartialOrd_mac)]
    enum EnumF<T> { A { r: T, g: T, b: T, } }
}

custom_derive! {
    #[derive(PartialEq, PartialOrd_mac)]
    struct StructA;
}

custom_derive! {
    #[derive(PartialEq, PartialOrd_mac)]
    struct StructB(i32);
}

custom_derive! {
    #[derive(PartialEq, PartialOrd_mac)]
    struct StructC(i32, u8, String);
}

custom_derive! {
    #[derive(PartialEq, PartialOrd_mac)]
    struct StructD {
        /// The red stuff.
        r: u8,
        pub g: u8,
        b: u8,
    }
}

custom_derive! {
    #[derive(Clone, PartialEq, PartialOrd_mac)]
    struct StructE<T> {
        /// The red stuff.
        r: T,
        pub g: T,
        b: T,
    }
}

#[test]
fn test_partial_ord() {
    if false { let _x: EnumA = panic!(); _x.partial_cmp(&_x); }
    { let x = EnumB::A; x.partial_cmp(&x); }
    { let x = EnumC::A; x.partial_cmp(&x); }
    { let x = EnumC::B; x.partial_cmp(&x); }
    { let x = EnumC::C; x.partial_cmp(&x); }
    { let x = EnumD::A; x.partial_cmp(&x); }
    { let x = EnumD::B(42); x.partial_cmp(&x); }
    { let x = EnumD::C(1, 2, 3); x.partial_cmp(&x); }
    { let x = EnumE::A { r: 1, g: 2, b: 3 }; x.partial_cmp(&x); }
    { let x = EnumF::A { r: 1, g: 2, b: 3 }; x.partial_cmp(&x); }
    { let x = StructA; x.partial_cmp(&x); }
    { let x = StructB(42); x.partial_cmp(&x); }
    { let x = StructC(42, 2, String::from("hi!")); x.partial_cmp(&x); }
    { let x = StructD { r: 1, g: 2, b: 3 }; x.partial_cmp(&x); }
    { let x = StructE { r: 1, g: 2, b: 3 }; x.partial_cmp(&x); }
}