/*! SassyBitwise - v1.1.0 - 2014-06-05 */
// Order arguments decreasing
// ---
// @param [number] $x: first value
// @param [number] $y: second value
// ---
// @return [number]
// ---
@function order-args($x, $y) {
  @if $x >= $y {
    @return $x $y;
  }
  @else {
    @return $y $x;
  }
}

// Power function
// Using Compass if defined
// ---
// @param [number] $x: first value
// @param [number] $y: second value
// ---
// @return [number]
// ---
@function power($x, $y) {
  @if function-exists('pow') == true {
    @return pow($x, $y);
  }

  $ret: 1;

  @if $y > 0 {
    @for $i from 1 through $y {
        $ret: $ret * $x;
    }
  } @else {
    @for $i from $y to 0 {
        $ret: $ret / $x;
    }
  }

  @return $ret;
}

// Returns sign of $x
// ---
// @param [number] $x: value
// ---
// @return [number]
// ---
@function sign($x) {
  @return if($x > 0, 1, if($x < 0, -1, 0));
}

// Convert from base 2 to base 10
// ---
// @param [number] $value: binary to convert
// ---
// @return [number]
// ---
@function binary-to-decimal($value) {
  $value: $value + unquote('');
  $result: if(str-slice($value, 1, 1) == "0", 0, 1);

  @for $i from 2 through str-length($value) {
    $c: str-slice($value, $i, $i);

    @if not index("0" "1", $c) {
      @warn "Not binary.";
      @return false;
    }

    $result: $result * 2;

    @if $c == "1" {
      $result: $result + 1;
    }
  }

  @return $result;
}

// Convert from base 10 to base 2
// ---
// @param [number] $value: decimal to convert
// ---
// @return [number]
// ---
@function decimal-to-binary($value) {
  $largest: largest-power-of-2($value);
  $result: 0;

  @while $largest >= 1 {
    @if $value - $largest >= 0 {
      $value: $value - $largest;
      $result: $result * 10 + 1;
    }
    @else {
      $result: $result * 10;
    }

    $largest: $largest / 2;
  }

  @return $result;
}

// Returns largest power of 2 lesser than $value
// ---
// @param [number] $value: value
// ---
// @return [number]
// ---
@function largest-power-of-2($value) {
  $result: 1;
  @while $result <= $value {
    @if $result * 2 > $value {
      @return $result;
    }
    $result: $result * 2;
  }
  @return $result;
}

// Returns binary length
// ---
// @param [number] $value: value
// ---
// @return [number]
// ---
@function binary-length($value) {
  @return str-length(decimal-to-binary($value) + unquote(""));
}

// Checks whether argument list is valid
// ---
// @param [list] $args: argument list
// ---
// @return [bool]
// ---
@function arguments-validator($args) {
  $last: null;
  $operators: (
    '&'  : 'and',
    '|'  : 'or',
    '^'  : 'xor',
    '<<' : 'shift-left',
    '>>' : 'shift-right'
  );

  @if length($args) < 2 {
    @return false;
  }

  @each $current in $args {
    @if map-has-key($operators, $current) {
      @if not $last {
        @warn "You can't start with another operator than `~`.";
        @return false;
      }
    }

    @else if $current == '~' {
      @if type-of($last) == "number" {
        @warn "Operator `~` can't follow a number.";
        @return false;
      }
    }

    @else if type-of($current) == "number" {
      @if type-of($last) == "number" {
        @warn "A number can't follow a number. They must be separated by an operator";
        @return false;
      }
    }

    @else {
      @return false;
    }

    $last: $current;
  }

  @return true;
}

// `or` bitwise operator
// $x | $y
// ---
// @param [number] $x: first value
// @param [number] $y: second value
// ---
// @return [number]
// ---
@function bw-or($x, $y) {
  $args: order-args($x, $y);
  $x: nth($args, 1);
  $y: nth($args, 2);
  $b: binary-length($x);
  $res: 0;

  @for $i from 0 through $b {
    $h: power(2, $i);
    $tx: floor($x / $h) % 2;
    $ty: floor($y / $h) % 2;
    $res: $res + $h * (($tx + $ty + $tx * $ty % 2) % 2);
  }

  @return $res;
}

// `xor` bitwise operator
// $x ^ $y
// ---
// @param [number] $x: first value
// @param [number] $y: second value
// ---
// @return [number]
// ---
@function bw-xor($x, $y) {
  $args: order-args($x, $y);
  $x: nth($args, 1);
  $y: nth($args, 2);
  $b: binary-length($x);
  $res: 0;

  @for $i from 0 through $b {
    $h: power(2, $i);
    $res: $res + $h * (((floor($x / $h) % 2) + (floor($y / $h) % 2)) % 2);
  }

  @return $res;
}

// `and` bitwise operator
// $x & $y
// ---
// @param [number] $x: first value
// @param [number] $y: second value
// ---
// @return [number]
// ---
@function bw-and($x, $y) {
  $args: order-args($x, $y);
  $x: nth($args, 1);
  $y: nth($args, 2);
  $b: binary-length($x);
  $res: 0;

  @for $i from 0 through $b {
    $h: power(2, $i);
    $res: $res + $h * (floor($x / $h) % 2) * (floor($y / $h) % 2)
  }

  @return $res;
}

// `not` bitwise operator
// ~ $x
// ---
// @param [number] $x: value
// ---
// @return [number]
// ---
@function bw-not($x) {
  @return ($x + sign($x)) * -1;
}

// `<<` bitwise operator
// $x << $y
// ---
// @param [number] $x: first value
// @param [number] $y: second value
// ---
// @return [number]
// ---
@function bw-shift-left($x, $y) {
  @return $x * power(2, $y);
}

// `>>` bitwise operator
// $x >> $y
// ---
// @param [number] $x: first value
// @param [number] $y: second value
// ---
// @return [number]
// ---
@function bw-shift-right($x, $y) {
  @return floor($x / power(2, $y));
}

// Bitwise helper function
// ---
// @param [list] $args: list of parameters
// ---
// @return [number]
// ---
@function bitwise($args) {
  @if not arguments-validator($args) {
    @return false;
  }

  $result: null;
  $operator: null;
  $not-operator: false;
  $operators: (
    '&'  : 'and',
    '|'  : 'or',
    '^'  : 'xor',
    '<<' : 'shift-left',
    '>>' : 'shift-right'
  );

  // Loop through all arguments
  @each $current in $args {

    // If current argument is an operator
    // Store it while waiting for next value
    @if map-has-key($operators, $current) {
      $operator: $current;
    }

    // If current argument is `not` bitwise operator
    // Pass $not boolean to true
    @else if $current == '~' {
      $not-operator: true;
    }

    // If current argument is not an operator
    @else {
      // If $not is true
      // Compute not first
      @if $not-operator {
        $current: bw-not($current);
        $not-operator: false;
      }

      // Then the operation
      @if $operator {
        $result: call('bw-' + map-get($operators, $operator), $result, $current);
        $operator: null;
      }

      @else {
        $result: $current;
      }
    }
  }

  @return $result;
}


// Alias for `bitwise`
// ---
@function bw($args) {
  @return bitwise($args);
}