// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use ast::{MetaItem, item, expr, and};
use codemap::span;
use ext::base::ExtCtxt;
use ext::build::AstBuilder;
use ext::deriving::generic::*;


pub fn expand_deriving_iter_bytes(cx: @ExtCtxt,
                                  span: span,
                                  mitem: @MetaItem,
                                  in_items: ~[@item]) -> ~[@item] {
    let trait_def = TraitDef {
        path: Path::new(~["std", "to_bytes", "IterBytes"]),
        additional_bounds: ~[],
        generics: LifetimeBounds::empty(),
        methods: ~[
            MethodDef {
                name: "iter_bytes",
                generics: LifetimeBounds::empty(),
                explicit_self: borrowed_explicit_self(),
                args: ~[
                    Literal(Path::new(~["bool"])),
                    Literal(Path::new(~["std", "to_bytes", "Cb"]))
                ],
                ret_ty: Literal(Path::new(~["bool"])),
                const_nonmatching: false,
                combine_substructure: iter_bytes_substructure
            }
        ]
    };

    trait_def.expand(cx, span, mitem, in_items)
}

fn iter_bytes_substructure(cx: @ExtCtxt, span: span, substr: &Substructure) -> @expr {
    let (lsb0, f)= match substr.nonself_args {
        [l, f] => (l, f),
        _ => cx.span_bug(span, "Incorrect number of arguments in `deriving(IterBytes)`")
    };
    // Build the "explicitly borrowed" stack closure, "|_buf| f(_buf)".
    let blk_arg = cx.ident_of("_buf");
    let borrowed_f =
        cx.lambda_expr_1(span, cx.expr_call(span, f, ~[cx.expr_ident(span, blk_arg)]),
                         blk_arg);

    let iter_bytes_ident = substr.method_ident;
    let call_iterbytes = |thing_expr| {
        cx.expr_method_call(span,
                            thing_expr,
                            iter_bytes_ident,
                            ~[lsb0, borrowed_f])
    };
    let mut exprs = ~[];
    let fields;
    match *substr.fields {
        Struct(ref fs) => {
            fields = fs
        }
        EnumMatching(index, ref variant, ref fs) => {
            // Determine the discriminant. We will feed this value to the byte
            // iteration function.
            let discriminant = match variant.node.disr_expr {
                Some(d)=> d,
                None => cx.expr_uint(span, index)
            };

            exprs.push(call_iterbytes(discriminant));

            fields = fs;
        }
        _ => cx.span_bug(span, "Impossible substructure in `deriving(IterBytes)`")
    }

    for &(_, field, _) in fields.iter() {
        exprs.push(call_iterbytes(field));
    }

    if exprs.len() == 0 {
        cx.span_bug(span, "#[deriving(IterBytes)] needs at least one field");
    }

    do exprs.slice(1, exprs.len()).iter().fold(exprs[0]) |prev, me| {
        cx.expr_binary(span, and, prev, *me)
    }
}
