// =============================================================================
// =ALEKSI - CONVERT-LENGTH
// =============================================================================
////
//// @group aleksi-lengths
//// @author [Yoannis Jamar](https://yoannis.com)
//// @author [Chris Eppstein](http://chriseppstein.github.io/)

@import "aleksi/general/memo";
@import "aleksi/numbers/strip-unit";

// =default globals
// -----------------------------------------------------------------------------

// Base font size in pixels, if not already defined.
// Should be the same as the font-size of the body element.
$base-font-size: 16px !default;

// Root font size in pixels, if not already defined
// Should be the same as the font-size of the html element.
$root-font-size: $base-font-size !default;

// =convert-lengths( $values, $to-unit[, $from-context, $to-context ])
// -----------------------------------------------------------------------------
/// Convert all lengths in given list or map. Doesn't try to convert none-number
/// values in the list/map.
///
/// @param {list|map} $values - A css list or map with lengths to convert
/// @param {string} $to-unit - A css unit keyword, e.g. 'em', '%', 'px', etc.
/// @param {number} $from-context [$base-font-size] - The absolute length (in px) to which `$length` refers
/// @param {number} $to-context [$from-context] - the absolute length in px to which the output value will refer.
///
/// @access public
/// @since 0.2.2

@function convert-lengths($values, $to-unit, $from-context: $base-font-size, $to-context: $from-context)
{
  $res: ();
  $type: type-of($values);

  @if $type == 'list' or $type == 'arglist'
  {
    @each $value in $values
    {
      // convert length values
      @if type-of($value) == 'number' {
        $value: convert-length($value, $to-unit, $from-context, $to-context);
      }

      $res: append($res, $value);
    }

    @return $res;
  }

  @else if $type == 'map'
  {
    @each $key, $val in $values
    {
      // convert length values
      @if type-of($val) == 'number' {
        $val: convert-length($val, $to-unit, $from-context, $to-context);
      }

      $res: map-merge($res, ('#{$key}': $val));
    }

    @return $res;
  }

  @return convert-length($values, $to-unit, $from-context, $to-context);
}

// =convert-length( $length, $to-unit[, $from-context, $to-context ])
// -----------------------------------------------------------------------------
/// Convert any CSS <length> or <percentage> value to any another.
///
/// @param {number} $length - A css <length> or <percentage> value
/// @param {string} $to-unit - A css unit keyword, e.g. 'em', '%', 'px', etc.
/// @param {number} $from-context [$base-font-size] - The absolute length (in px) to which `$length` refers
/// @param {number} $to-context [$from-context] - the absolute length in px to which the output value will refer.
///
/// @return {type|...} - Description
/// @throw Description
///
/// @access public
/// @since 0.1.0

@function convert-length( $length, $to-unit, $from-context: $base-font-size, $to-context: $from-context )
{
  // accept null, and return as is
  @if type-of($length) == 'null' {
    @return $length;
  }

  // validate given length
  @if type-of($length) != 'number' {
    @return throw-error('convert-length(): `$length` must be `null` or a number – was `#{inspect($length)}`');
  }

  // validate context lengths
  // these must be in px so we can determine a conversion ratio for relative units
  @if unit($from-context) != 'px' {
    @return throw-error('convert-length(): `$from-context` must resolve to a value in pixels — was `#{inspect($from-context)}`');
  }

  @if unit($to-context) != 'px' {
    @return throw-error('convert-length(): `$to-context` must resolve to a value in pixels — was `#{inspect($to-context)}`');
  }

  $_memo_: 'convert-length-#{$length}-#{$to-unit}-#{$from-context}-#{$to-context}';
  @if memo-has('aleksi', $_memo_) {
    @return memo-get('aleksi', $_memo_);
  }

  // 0 is always 0, and should be unitless
  @if strip-unit($length) == 0 {
    @return memo-set('aleksi', $_memo_, 0);
  }

  // get original unit
  $from-unit: unit($length);

  // accept `null` and `'unitless'` as $to-unit for unitless conversion
  @if $to-unit == null or $to-unit == 'unitless' {
    $to-unit: '';
  }

  // optimize for cases where `from` and `to` units are the same.
  @if $from-unit == $to-unit {
    @return memo-set('aleksi', $_memo_, $length);
  }

  // use 'root-font-size' setting as context for 'rem' conversions
  @if $to-unit == 'rem' or $from-unit == 'rem'
  {
    $rfs: if(global-variable-exists('root-font-size'), $root-font-size, 16px);
    $to-context: if($from-context == $to-context, $rfs, $to-context);
  }

  // if converting to unitless
  @if $to-unit == '' {
    // convert length into context unit in order to..
    $length: convert-length($length, 'px', $from-context, $to-context);
    // calculate and return mutliplying factor between length and context
    $length: $length / $to-context;

    @return memo-set('aleksi', $_memo_, $length);
  }

  // if converting from unitless
  @if $from-unit == '' {
    // consider length as a factor for context unit
    $length: convert-length($length * $from-context, $to-unit, $from-context, $to-context);
    @return memo-set('aleksi', $_memo_, $length);
  }

  // Convert input length to pixels
  $px-length: $length;

  @if $from-unit != 'px' {
    // Convert relative units using the from-context parameter.
    @if      $from-unit == 'em'  { $px-length: $length * $from-context / 1em }
    @else if $from-unit == 'rem' { $px-length: $length * $root-font-size / 1rem }
    @else if $from-unit == '%'   { $px-length: $length * $from-context / 100% }
    @else if $from-unit == 'ex'  { $px-length: $length * $from-context / 2ex }
    // Convert absolute units using Sass' conversion table.
    @else if $from-unit == 'in'  or
             $from-unit == 'mm'  or
             $from-unit == 'cm'  or
             $from-unit == 'pt'  or
             $from-unit == 'pc'  { $px-length: 0px + $length; }
    // Certain units can't be converted.
    @else if $from-unit == 'ch'  or
             $from-unit == 'vw'  or
             $from-unit == 'vh'  or
             $from-unit == 'vmin' {
      @warn "#{$from-unit} units can't be reliably converted; Returning original value.";
      @return memo-set('aleksi', $_memo_, $length);
    }
    @else {
      @warn "#{$from-unit} is an unknown length unit. Returning original value.";
      @return memo-set('aleksi', $_memo_, $length);
    }
  }

  // convert length in pixels to the output unit
  $output-length: $px-length;
  @if $to-unit != 'px' {
    // Relative units
    @if      $to-unit == 'em'  { $output-length: $px-length * 1em / $to-context }
    @else if $to-unit == 'rem' { $output-length: $px-length * 1rem / $root-font-size }
    @else if $to-unit == '%'   { $output-length: $px-length * 100% / $to-context }
    @else if $to-unit == 'ex'  { $output-length: $px-length * 2ex / $to-context }
    // Absolute units
    @else if $to-unit == 'in'  { $output-length: 0in + $px-length }
    @else if $to-unit == 'mm'  { $output-length: 0mm + $px-length }
    @else if $to-unit == 'cm'  { $output-length: 0cm + $px-length }
    @else if $to-unit == 'pt'  { $output-length: 0pt + $px-length }
    @else if $to-unit == 'pc'  { $output-length: 0pc + $px-length }
    // Non-convertible units
    @else if $to-unit == 'ch'  or
             $to-unit == 'vw'  or
             $to-unit == 'vh'  or
             $to-unit == 'vmin' {
      @warn "`#{inspect($to-unit)}` units can't be reliably converted; Returning original value.";
      @return memo-set('aleksi', $_memo_, $length);
    }
    @else {
      @warn "`#{inspect($to-unit)}` is an unknown length unit. Returning original value.";
      @return memo-set('aleksi', $_memo_, $length);
    }
  }

  @return memo-set('aleksi', $_memo_, $output-length);
}
