////////////////////////////////////
// THEME / FUNCTIONS / MODULAR SCALE
////////////////////////////////////


@use 'sass:math';



// Defaults and variables

// Ratios
$double-octave    : 4                 ;
$pi               : 3.14159265359     ;
$major-twelfth    : 3                 ;
$major-eleventh   : 2.666666667       ;
$major-tenth      : 2.5               ;
$octave           : 2                 ;
$major-seventh    : 1.875             ;
$minor-seventh    : 1.777777778       ;
$major-sixth      : 1.666666667       ;
$phi              : 1.618034          ;
$golden           : $phi              ;
$minor-sixth      : 1.6               ;
$fifth            : 1.5               ;
$augmented-fourth : 1.41421           ;
$fourth           : 1.333333333       ;
$major-third      : 1.25              ;
$minor-third      : 1.2               ;
$major-second     : 1.125             ;
$minor-second     : 1.066666667       ;

// Base config
$ms-base          : 1em       !default;
$ms-ratio         : $fifth    !default;
$modularscale     : ()        !default;



// Core functions

// Parse settings starting with defaults.
// Settings should cascade down like you would expect in CSS.
// More specific overrides previous settings.

@function ms-settings($b: false, $r: false, $t: false, $m: $modularscale) {
  $base: $ms-base;
  $ratio: $ms-ratio;
  $thread: map-get($m, $t);

  // Override with user settings
  @if map-get($m, base) {
    $base: map-get($m, base);
  }
  @if map-get($m, ratio) {
    $ratio: map-get($m, ratio);
  }

  // Override with thread settings
  @if $thread {
    @if map-get($thread, base) {
      $base: map-get($thread, base);
    }
    @if map-get($thread, ratio) {
      $ratio: map-get($thread, ratio);
    }
  }

  // Override with inline settings
  @if $b {
    $base: $b;
  }
  @if $r {
    $ratio: $r;
  }

  @return $base $ratio;
}


// Sass does not have native pow() support so this needs to be added.
// Compass and other libs implement this more extensively.
// In order to keep this simple, use those when they are avalible.
// Issue for pow() support in Sass: https://github.com/sass/sass/issues/684

@function ms-pow($b,$e) {

  // Return 1 if exponent is 0
  @if $e == 0 {
    @return 1;
  }

  // If pow() exists (compass or mathsass) use that.
  @if function-exists('pow') {
    @return pow($b,$e);
  }

  // This does not support non-integer exponents,
  // Check and return an error if a non-integer exponent is passed.
  @if (floor($e) != $e) {
    @error 'Non-integer values are not supported in modularscale by default. Try using mathsass in your project to add non-integer scale support. https://github.com/terkel/mathsass'
  }

  // Seed the return.
  $ms-return: $b;

  // Multiply or divide by the specified number of times.
  @if $e > 0 {
    @for $i from 1 to $e {
      $ms-return: $ms-return * $b;
    }
  }
  @if $e < 0 {
    @for $i from $e through 0 {
      $ms-return: math.div($ms-return, $b);
    }
  }
  @return $ms-return;
}


// Stripping units is not a best practice
// This function should not be used elsewhere
// It is used here because calc() doesn't do unit logic
// AND target ratios use units as a hack to get a number.
@function ms-unitless($val) {
  @return math.div($val, ($val - $val + 1));
}


// Basic list sorting
// Would like to replace with http://sassmeister.com/gist/30e4863bd03ce0e1617c
// Unfortunately libsass has a bug with passing arguments into the min() funciton.

@function ms-sort($l) {

  // loop until the list is confirmed to be sorted
  $sorted: false;
  @while $sorted == false {

    // Start with the assumption that the lists are sorted.
    $sorted: true;

    // Loop through the list, checking each value with the one next to it.
    // Swap the values if they need to be swapped.
    // Not super fast but simple and modular scale doesn't lean hard on sorting.
    @for $i from 2 through length($l) {
      $n1: nth($l,$i - 1);
      $n2: nth($l,$i);

      // If the first value is greater than the 2nd, swap them.
      @if $n1 > $n2 {
        $l: set-nth($l, $i, $n1);
        $l: set-nth($l, $i - 1, $n2);

        // The list isn't sorted and needs to be looped through again.
        $sorted: false;
      }
    }
  }

  // Return the sorted list.
  @return $l;
}


// Convert number string to number
@function ms-to-num($n) {
  $l: str-length($n);
  $r: 0;
  $m: str-index($n,'.');
  @if $m == null {
    $m: $l + 1;
  }
  // Loop through digits and convert to numbers
  @for $i from 1 through $l {
    $v: str-slice($n,$i,$i);
    @if $v == '1' { $v: 1; }
    @else if $v == '2' { $v: 2; }
    @else if $v == '3' { $v: 3; }
    @else if $v == '4' { $v: 4; }
    @else if $v == '5' { $v: 5; }
    @else if $v == '6' { $v: 6; }
    @else if $v == '7' { $v: 7; }
    @else if $v == '8' { $v: 8; }
    @else if $v == '9' { $v: 9; }
    @else if $v == '0' { $v: 0; }
    @else { $v: null; }
    @if $v != null {
      $m: $m - 1;
      $r: $r + ms-pow(10,$m - 1) * $v;
    } @else {
      $l: $l - 1;
    }
  }
  @return $r;
}


// Find a ratio based on a target value
@function ms-target($t, $b) {
  // Convert to string
  $t: $t + '';
  // Remove base units to calulate ratio
  $b: ms-unitless(nth($b, 1));
  // Find where 'at' is in the string
  $at: str-index($t, 'at');

  // Slice the value and target out
  // and convert strings to numbers
  $v: ms-to-num(str-slice($t, 0, $at - 1));
  $t: ms-to-num(str-slice($t, $at + 2));

  // Solve the modular scale function for the ratio.
  @return ms-pow(($v / $b), (1 / $t));
}


@function ms-function($v: 0, $base: false, $ratio: false, $thread: false, $settings: $modularscale) {

  // Parse settings
  $ms-settings: ms-settings($base, $ratio, $thread, $settings);
  $base: nth($ms-settings, 1);
  $ratio: nth($ms-settings, 2);

  // Render target values from settings.
  @if unit($ratio) != '' {
    $ratio: ms-target($ratio, $base)
  }

  // Fast calc if not multi stranded
  @if(length($base) == 1) {
    @return ms-pow($ratio, $v) * $base;
  }

  // Create new base array
  $ms-bases: nth($base, 1);

  // Normalize base values
  @for $i from 2 through length($base) {
    // initial base value
    $ms-base: nth($base, $i);
    // If the base is bigger than the main base
    @if($ms-base > nth($base, 1)) {
      // divide the value until it aligns with main base.
      @while($ms-base > nth($base, 1)) {
        $ms-base: math.div($ms-base, $ratio);
      }
      $ms-base: $ms-base * $ratio;
    }
    // If the base is smaller than the main base.
    @else if ($ms-base < nth($base,1)) {
      // pump up the value until it aligns with main base.
      @while $ms-base < nth($base,1) {
        $ms-base: $ms-base * $ratio;
      }
    }
    // Push into new array
    $ms-bases: append($ms-bases, $ms-base);
  }

  // Sort array from smallest to largest.
  $ms-bases: ms-sort($ms-bases);

  // Find step to use in calculation
  $vtep: floor(calc($v / length($ms-bases)));
  // Find base to use in calculation
  $ms-base: round((math.div($v, length($ms-bases)) - $vtep) * length($ms-bases)) + 1;

  @return ms-pow($ratio, $vtep) * nth($ms-bases, $ms-base);
}


@function ms-round-px($r) {
    @if unit($r) == 'px' {
        @return round($r);
    }
    @warn "ms-round-px is no longer used by modular scale and will be removed in the 3.1.0 release.";
    @return $r;
}



// Mixins

// Generate calc() function
// based on Mike Riethmuller's Precise control over responsive typography
// http://madebymike.com.au/writing/precise-control-responsive-typography/
@function ms-fluid($val1: 1em, $val2: 1em, $break1: 0, $break2: 0) {
  $diff: ms-unitless($val2) - ms-unitless($val1);

  // v1 + (v2 - v1) * ( (100vw - b1) / b2 - b1 )
  @return calc( #{$val1} + #{ms-unitless($val2) - ms-unitless($val1)} * ( ( 100vw - #{$break1}) / #{ms-unitless($break2) - ms-unitless($break1)} ) );
}

// Main responsive mixin
@mixin ms-respond($prop, $val, $map: $modularscale, $ms-important: false) {
  $base: $ms-base;
  $ratio: $ms-ratio;

  $first-write: true;
  $last-break: null;

  $important: '';

  @if $ms-important == true {
    $important: ' !important';
  }

  // loop through all settings with a breakpoint type value
  @each $v, $s in $map {
    @if type-of($v) == number {
      @if unit($v) != '' {

        // Write out the first value without a media query.
        @if $first-write {
          #{$prop}: unquote("#{ms-function($val, $thread: $v, $settings: $map)}#{$important}");

          // Not the first write anymore, reset to false to move on.
          $first-write: false;
          $last-break: $v;
        }

        // Write intermediate breakpoints.
        @else {
          @media (min-width: $last-break) and (max-width: $v) {
            $val1: ms-function($val, $thread: $last-break, $settings: $map);
            $val2: ms-function($val, $thread: $v, $settings: $map);
            #{$prop}: unquote("#{ms-fluid($val1,$val2,$last-break,$v)}#{$important}");
          }
          $last-break: $v;
        }
      }
    }
  }

  // Write the last breakpoint.
  @if $last-break {
    @media (min-width: $last-break) {
      #{$prop}: unquote("#{ms-function($val, $thread: $last-break, $settings: $map)}#{$important}");
    }
  }
}



// Syntax sugar

// To attempt to avoid conflicts with other libraries
// all funcitons are namespaced with `ms-`.
// However, to increase usability, a shorthand function is included here.

@function ms($v: 0, $base: false, $ratio: false, $thread: false, $settings: $modularscale) {
  @return ms-function($v, $base, $ratio, $thread, $settings);
}