// Copyright 2015 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 hair::*;
use hair::cx::Cx;
use rustc_data_structures::fnv::FnvHashMap;
use rustc_const_eval as const_eval;
use rustc::hir::def::Def;
use rustc::hir::pat_util::{pat_is_resolved_const, pat_is_binding};
use rustc::ty::{self, Ty};
use rustc::mir::repr::*;
use rustc::hir::{self, PatKind};
use syntax::ast;
use syntax::codemap::Span;
use syntax::ptr::P;

/// When there are multiple patterns in a single arm, each one has its
/// own node-ids for the bindings.  References to the variables always
/// use the node-ids from the first pattern in the arm, so we just
/// remap the ids for all subsequent bindings to the first one.
///
/// Example:
/// ```
/// match foo {
///    Test1(flavor /* def 1 */) |
///    Test2(flavor /* def 2 */) if flavor /* ref 1 */.is_tasty() => { ... }
///    _ => { ... }
/// }
/// ```
struct PatCx<'patcx, 'cx: 'patcx, 'tcx: 'cx> {
    cx: &'patcx mut Cx<'cx, 'tcx>,
    binding_map: Option<&'patcx FnvHashMap<ast::Name, ast::NodeId>>,
}

impl<'cx, 'tcx> Cx<'cx, 'tcx> {
    pub fn irrefutable_pat(&mut self, pat: &hir::Pat) -> Pattern<'tcx> {
        PatCx::new(self, None).to_pattern(pat)
    }

    pub fn refutable_pat(&mut self,
                         binding_map: Option<&FnvHashMap<ast::Name, ast::NodeId>>,
                         pat: &hir::Pat)
                         -> Pattern<'tcx> {
        PatCx::new(self, binding_map).to_pattern(pat)
    }
}

impl<'patcx, 'cx, 'tcx> PatCx<'patcx, 'cx, 'tcx> {
    fn new(cx: &'patcx mut Cx<'cx, 'tcx>,
               binding_map: Option<&'patcx FnvHashMap<ast::Name, ast::NodeId>>)
               -> PatCx<'patcx, 'cx, 'tcx> {
        PatCx {
            cx: cx,
            binding_map: binding_map,
        }
    }

    fn to_pattern(&mut self, pat: &hir::Pat) -> Pattern<'tcx> {
        let mut ty = self.cx.tcx.node_id_to_type(pat.id);

        let kind = match pat.node {
            PatKind::Wild => PatternKind::Wild,

            PatKind::Lit(ref value) => {
                let value = const_eval::eval_const_expr(self.cx.tcx, value);
                PatternKind::Constant { value: value }
            }

            PatKind::Range(ref lo, ref hi) => {
                let lo = const_eval::eval_const_expr(self.cx.tcx, lo);
                let lo = Literal::Value { value: lo };
                let hi = const_eval::eval_const_expr(self.cx.tcx, hi);
                let hi = Literal::Value { value: hi };
                PatternKind::Range { lo: lo, hi: hi }
            },

            PatKind::Path(..) | PatKind::Ident(..) | PatKind::QPath(..)
                if pat_is_resolved_const(&self.cx.tcx.def_map.borrow(), pat) =>
            {
                let def = self.cx.tcx.def_map.borrow().get(&pat.id).unwrap().full_def();
                match def {
                    Def::Const(def_id) | Def::AssociatedConst(def_id) => {
                        let substs = Some(self.cx.tcx.node_id_item_substs(pat.id).substs);
                        match const_eval::lookup_const_by_id(self.cx.tcx, def_id, substs) {
                            Some((const_expr, _const_ty)) => {
                                match const_eval::const_expr_to_pat(self.cx.tcx,
                                                                    const_expr,
                                                                    pat.id,
                                                                    pat.span) {
                                    Ok(pat) =>
                                        return self.to_pattern(&pat),
                                    Err(_) =>
                                        span_bug!(
                                            pat.span, "illegal constant"),
                                }
                            }
                            None => {
                                span_bug!(
                                    pat.span,
                                    "cannot eval constant: {:?}",
                                    def_id)
                            }
                        }
                    }
                    _ =>
                        span_bug!(
                            pat.span,
                            "def not a constant: {:?}",
                            def),
                }
            }

            PatKind::Ref(ref subpattern, _) |
            PatKind::Box(ref subpattern) => {
                PatternKind::Deref { subpattern: self.to_pattern(subpattern) }
            }

            PatKind::Vec(ref prefix, ref slice, ref suffix) => {
                let ty = self.cx.tcx.node_id_to_type(pat.id);
                match ty.sty {
                    ty::TyRef(_, mt) =>
                        PatternKind::Deref {
                            subpattern: Pattern {
                                ty: mt.ty,
                                span: pat.span,
                                kind: Box::new(self.slice_or_array_pattern(pat.span, mt.ty, prefix,
                                                                           slice, suffix)),
                            },
                        },

                    ty::TySlice(..) |
                    ty::TyArray(..) =>
                        self.slice_or_array_pattern(pat.span, ty, prefix, slice, suffix),

                    ref sty =>
                        span_bug!(
                            pat.span,
                            "unexpanded type for vector pattern: {:?}",
                            sty),
                }
            }

            PatKind::Tup(ref subpatterns) => {
                let subpatterns =
                    subpatterns.iter()
                               .enumerate()
                               .map(|(i, subpattern)| FieldPattern {
                                   field: Field::new(i),
                                   pattern: self.to_pattern(subpattern),
                               })
                               .collect();

                PatternKind::Leaf { subpatterns: subpatterns }
            }

            PatKind::Ident(bm, ref ident, ref sub)
                if pat_is_binding(&self.cx.tcx.def_map.borrow(), pat) =>
            {
                let id = match self.binding_map {
                    None => pat.id,
                    Some(ref map) => map[&ident.node.name],
                };
                let var_ty = self.cx.tcx.node_id_to_type(pat.id);
                let region = match var_ty.sty {
                    ty::TyRef(&r, _) => Some(r),
                    _ => None,
                };
                let (mutability, mode) = match bm {
                    hir::BindByValue(hir::MutMutable) =>
                        (Mutability::Mut, BindingMode::ByValue),
                    hir::BindByValue(hir::MutImmutable) =>
                        (Mutability::Not, BindingMode::ByValue),
                    hir::BindByRef(hir::MutMutable) =>
                        (Mutability::Not, BindingMode::ByRef(region.unwrap(), BorrowKind::Mut)),
                    hir::BindByRef(hir::MutImmutable) =>
                        (Mutability::Not, BindingMode::ByRef(region.unwrap(), BorrowKind::Shared)),
                };

                // A ref x pattern is the same node used for x, and as such it has
                // x's type, which is &T, where we want T (the type being matched).
                if let hir::BindByRef(_) = bm {
                    if let ty::TyRef(_, mt) = ty.sty {
                        ty = mt.ty;
                    } else {
                        bug!("`ref {}` has wrong type {}", ident.node, ty);
                    }
                }

                PatternKind::Binding {
                    mutability: mutability,
                    mode: mode,
                    name: ident.node.name,
                    var: id,
                    ty: var_ty,
                    subpattern: self.to_opt_pattern(sub),
                }
            }

            PatKind::Ident(..) | PatKind::Path(..) => {
                self.variant_or_leaf(pat, vec![])
            }

            PatKind::TupleStruct(_, ref opt_subpatterns) => {
                let subpatterns =
                    opt_subpatterns.iter()
                                   .flat_map(|v| v.iter())
                                   .enumerate()
                                   .map(|(i, field)| FieldPattern {
                                       field: Field::new(i),
                                       pattern: self.to_pattern(field),
                                   })
                                   .collect();
                self.variant_or_leaf(pat, subpatterns)
            }

            PatKind::Struct(_, ref fields, _) => {
                let pat_ty = self.cx.tcx.node_id_to_type(pat.id);
                let adt_def = match pat_ty.sty {
                    ty::TyStruct(adt_def, _) | ty::TyEnum(adt_def, _) => adt_def,
                    _ => {
                        span_bug!(
                            pat.span,
                            "struct pattern not applied to struct or enum");
                    }
                };

                let def = self.cx.tcx.def_map.borrow().get(&pat.id).unwrap().full_def();
                let variant_def = adt_def.variant_of_def(def);

                let subpatterns =
                    fields.iter()
                          .map(|field| {
                              let index = variant_def.index_of_field_named(field.node.name);
                              let index = index.unwrap_or_else(|| {
                                  span_bug!(
                                      pat.span,
                                      "no field with name {:?}",
                                      field.node.name);
                              });
                              FieldPattern {
                                  field: Field::new(index),
                                  pattern: self.to_pattern(&field.node.pat),
                              }
                          })
                          .collect();

                self.variant_or_leaf(pat, subpatterns)
            }

            PatKind::QPath(..) => {
                span_bug!(pat.span, "unexpanded macro or bad constant etc");
            }
        };

        Pattern {
            span: pat.span,
            ty: ty,
            kind: Box::new(kind),
        }
    }

    fn to_patterns(&mut self, pats: &[P<hir::Pat>]) -> Vec<Pattern<'tcx>> {
        pats.iter().map(|p| self.to_pattern(p)).collect()
    }

    fn to_opt_pattern(&mut self, pat: &Option<P<hir::Pat>>) -> Option<Pattern<'tcx>> {
        pat.as_ref().map(|p| self.to_pattern(p))
    }

    fn slice_or_array_pattern(&mut self,
                              span: Span,
                              ty: Ty<'tcx>,
                              prefix: &[P<hir::Pat>],
                              slice: &Option<P<hir::Pat>>,
                              suffix: &[P<hir::Pat>])
                              -> PatternKind<'tcx> {
        match ty.sty {
            ty::TySlice(..) => {
                // matching a slice or fixed-length array
                PatternKind::Slice {
                    prefix: self.to_patterns(prefix),
                    slice: self.to_opt_pattern(slice),
                    suffix: self.to_patterns(suffix),
                }
            }

            ty::TyArray(_, len) => {
                // fixed-length array
                assert!(len >= prefix.len() + suffix.len());
                PatternKind::Array {
                    prefix: self.to_patterns(prefix),
                    slice: self.to_opt_pattern(slice),
                    suffix: self.to_patterns(suffix),
                }
            }

            _ => {
                span_bug!(span, "unexpanded macro or bad constant etc");
            }
        }
    }

    fn variant_or_leaf(&mut self,
                       pat: &hir::Pat,
                       subpatterns: Vec<FieldPattern<'tcx>>)
                       -> PatternKind<'tcx> {
        let def = self.cx.tcx.def_map.borrow().get(&pat.id).unwrap().full_def();
        match def {
            Def::Variant(enum_id, variant_id) => {
                let adt_def = self.cx.tcx.lookup_adt_def(enum_id);
                if adt_def.variants.len() > 1 {
                    PatternKind::Variant {
                        adt_def: adt_def,
                        variant_index: adt_def.variant_index_with_id(variant_id),
                        subpatterns: subpatterns,
                    }
                } else {
                    PatternKind::Leaf { subpatterns: subpatterns }
                }
            }

            Def::Struct(..) | Def::TyAlias(..) => {
                PatternKind::Leaf { subpatterns: subpatterns }
            }

            _ => {
                span_bug!(pat.span, "inappropriate def for pattern: {:?}", def);
            }
        }
    }
}
