import {hasCornerRadius, rectangle} from '../path/shapes';
import boundStroke from '../bound/boundStroke';
import {intersectRect} from '../util/intersect';
import {visit, pickVisit} from '../util/visit';
import {clipGroup} from '../util/canvas/clip';
import stroke from '../util/canvas/stroke';
import fill from '../util/canvas/fill';
import {hitPath} from '../util/canvas/pick';
import clip from '../util/svg/clip';
import {translateItem} from '../util/svg/transform';

function offset(item) {
  var sw = (sw = item.strokeWidth) != null ? sw : 1;
  return item.strokeOffset != null ? item.strokeOffset
    : item.stroke && sw > 0.5 && sw < 1.5 ? 0.5 - Math.abs(sw - 1)
    : 0;
}

function attr(emit, item) {
  emit('transform', translateItem(item));
}

function background(emit, item) {
  var off = offset(item);
  emit('class', 'background');
  emit('d', rectangle(null, item, off, off));
}

function foreground(emit, item, renderer) {
  var url = item.clip ? clip(renderer, item, item) : null;
  emit('clip-path', url);
}

function bound(bounds, group) {
  if (!group.clip && group.items) {
    var items = group.items;
    for (var j=0, m=items.length; j<m; ++j) {
      bounds.union(items[j].bounds);
    }
  }

  if ((group.clip || group.width || group.height) && !group.noBound) {
    bounds.add(0, 0).add(group.width || 0, group.height || 0);
  }

  boundStroke(bounds, group);

  return bounds.translate(group.x || 0, group.y || 0);
}

function backgroundPath(context, group) {
  var off = offset(group);
  context.beginPath();
  rectangle(context, group, off, off);
}

var hitBackground = hitPath(backgroundPath);

function draw(context, scene, bounds) {
  var renderer = this;

  visit(scene, function(group) {
    var gx = group.x || 0,
        gy = group.y || 0,
        opacity;

    // setup graphics context
    context.save();
    context.translate(gx, gy);

    // draw group background
    if (group.stroke || group.fill) {
      opacity = group.opacity == null ? 1 : group.opacity;
      if (opacity > 0) {
        backgroundPath(context, group);
        if (group.fill && fill(context, group, opacity)) {
          context.fill();
        }
        if (group.stroke && stroke(context, group, opacity)) {
          context.stroke();
        }
      }
    }

    // set clip and bounds
    if (group.clip) clipGroup(context, group);
    if (bounds) bounds.translate(-gx, -gy);

    // draw group contents
    visit(group, function(item) {
      renderer.draw(context, item, bounds);
    });

    // restore graphics context
    if (bounds) bounds.translate(gx, gy);
    context.restore();
  });
}

function pick(context, scene, x, y, gx, gy) {
  if (scene.bounds && !scene.bounds.contains(gx, gy) || !scene.items) {
    return null;
  }

  var handler = this,
      cx = x * context.pixelRatio,
      cy = y * context.pixelRatio;

  return pickVisit(scene, function(group) {
    var hit, dx, dy, dw, dh, b, c;

    // first hit test bounding box
    b = group.bounds;
    if (b && !b.contains(gx, gy)) return;

    // passed bounds check, test rectangular clip
    dx = group.x || 0;
    dy = group.y || 0;
    dw = dx + (group.width || 0);
    dh = dy + (group.height || 0);
    c = group.clip;
    if (c && (gx < dx || gx > dw || gy < dx || gy > dh)) return;

    // adjust coordinate system
    context.save();
    context.translate(dx, dy);
    dx = gx - dx;
    dy = gy - dy;

    // test background for rounded corner clip
    if (c && hasCornerRadius(group) && !hitBackground(context, group, cx, cy)) {
      context.restore();
      return;
    }

    // hit test against contained marks
    hit = pickVisit(group, function(mark) {
      return pickMark(mark, dx, dy)
        ? handler.pick(mark, x, y, dx, dy)
        : null;
    });

    // hit test against group background
    if (!hit && scene.interactive !== false
        && (group.fill || group.stroke)
        && hitBackground(context, group, cx, cy)) {
      hit = group;
    }

    // restore state and return
    context.restore();
    return hit || null;
  });
}

function pickMark(mark, x, y) {
  return (mark.interactive !== false || mark.marktype === 'group')
    && mark.bounds && mark.bounds.contains(x, y);
}

export default {
  type:       'group',
  tag:        'g',
  nested:     false,
  attr:       attr,
  bound:      bound,
  draw:       draw,
  pick:       pick,
  isect:      intersectRect,
  background: background,
  foreground: foreground
};
