//////////////////////////////////////////////////////////////////
// Breakup
//
// A tool for defining breakpoints and conditionally loading them.
//////////////////////////////////////////////////////////////////


// Global Variables
//
// $breakup-breakpoints: List of many named breakpoints that can be called with
//     breakup-breakpoint. Each breakpoint is a list containing the breakpoint
//     name and the media query it describes. Example:
//
//     $breakup-breakpoints: (
//       'palm' '(max-width: 480px)',
//       'lap' '(min-width: 481px) and (max-width: 1023px)',
//       'portable' '(max-width: 1023px)',
//       'desk' '(min-width: 1024px)'
//     );
//
//
// $breakup-included-blocks: A list of blocks to render in your stylesheet
//
//
// $breakup-naked: Should breakpoint/tweakpoint blocks be wrapped in an @media
//     declaration? You should set this to true within stylesheets for browsers
//     which don't support @media, such as oldIE.
//
//
// $breakup-allow-naked: List of named breakpoints and if they should be output
//     when $breakup-naked is true (by default this is false). This is separate
//     to $breakup-breakpoints because you may want to configure what
//     breakpoints are unwrapped on a per stylesheet basis.
///
$breakup-breakpoints: () !default;
$breakup-included-blocks: () !default;
$breakup-naked: false !default;
$breakup-breakpoints-allow-naked: () !default;



// Search a list of lists ($haystack) for value ($needle) at a given position
// ($offset). Returns that item in the list, or false if not found.
//
// Example:
//  list-key-search((
//    ('key1' 'value1'),
//    ('key2' 'value2')
//  ), 'key1', 1) => (key1 value1)
@function breakup-list-key-search($haystack, $needle, $offset: 1) {
    @each $haystack-item in $haystack {
        @if $needle == nth($haystack-item, $offset) {
            @return $haystack-item;
        }
    }

    @return false;
}



// Merge a list of breakpoints into an already defined $breakup-breakpoints
// list. Returns null, as it manipulates the $breakup-breakpoints list directly.
//
// $breakpoints: A list of lists in the same style as $breakup-breakpoints, that
// shall be merged into the $breakup-breakpoints list.
@function breakup-add-breakpoints($breakpoints) {
    @each $breakpoint in $breakpoints {
        // Assign to a junk variable as sass doesn't let you run a function just
        // for its side effects
        $breakup-tmp: breakup-add-breakpoint($breakpoint...);
    }
    @return null;
}



// Wrapper around index as its return value differs from Sass 3.4 upwards.
// Sass 3.2, 3.3 and libsass return false if the item is not found
// Sass 3.4 returns null if the item is not found
// This function follows the Sass 3.4 convention of returning null
//
// $list: The list to search through
// $value: The value to search for
@function breakup-index($list, $value) {
    $index-result: index($list, $value);
    @if $index-result {
        @return $index-result;
    }
    @else {
        @return null;
    }
}



// Merge a single breakpoint into an already defined $breakup-breakpoints list.
// Returns null, as it manipulates the $breakup-breakpoints list directly.
//
// $breakpoint-name:    The name of the breakpoint to add
// $value:              The media query value to add
@function breakup-add-breakpoint($breakpoint-name, $value) {
    // Check the breakpoint-name doesn't already exist.
    // If if already exists, throw a warning saying the shall be overwritten
    @if breakup-list-key-search($breakup-breakpoints, $breakpoint-name) != false {
        @warn 'Attempting to add "#{$breakpoint-name}" to $breakup-breakpoints but it already exists. Refusing to write the new value.';
        @return null;
    }

    $breakup-breakpoints: append($breakup-breakpoints, ($breakpoint-name $value));
    @return null;
}



// Wrapper around a @media block. if $breakup-naked is true then the
// content is output directly if the declaration has been marked as a fallback
// breakpoint.
//
// $declaration:    A @media declaration to wrap the content block in.
// $allow-naked:    Should this content should be rendered if the we are
//                    displaying naked content (i.e. not wrapped in a media
//                    query).
@mixin breakup-media($declaration, $allow-naked: false) {
    @if $breakup-naked != true {
        @media #{$declaration} {
            @content;
        }
    }
    @else {
        // If we are outputting naked content, only items with $allow-naked
        // shall be rendered
        @if $allow-naked == true or $allow-naked == 'allow-naked' {
            @content;
        }
    }
}



// Include a block in the page if it is included in within
// $breakup-included-blocks
//
// $block-name:     The block name to render
@mixin breakup-block($block-name) {
    @if breakup-index($breakup-included-blocks, $block-name) != null {
        @content;
    }
}



// Look up a named breakpoint from $breakup-breakpoints, and wrap it in a block
// so that it only appears if it is in the current stylesheet's
// $breakup-included-blocks.
//
// $breakpoint-name:     The breakpoint name to render
@mixin breakup-breakpoint($breakpoint-name) {
    $breakpoint: breakup-list-key-search($breakup-breakpoints, $breakpoint-name, 1);

    @if $breakpoint {
        $declaration: nth($breakpoint, 2);

        // Handle Sass treating a list containing a single item as a single item
        $allow-naked-list: $breakup-breakpoints-allow-naked;
        @if length($allow-naked-list) == 1 {
            $allow-naked-list: $breakup-breakpoints-allow-naked, null;
        }

        $allow-naked: breakup-index($allow-naked-list, $breakpoint-name) != null;


        // For breakpoints, the block name is the same as the breakpoint name
        @include breakup-block($breakpoint-name) {
            @include breakup-media($declaration, $allow-naked) {
                @content;
            }
        }
    }
    @else {
        @warn "Breakpoint '#{$breakpoint-name}' does not exist";
    }
}



// Create an unnamed tweakpoint and wrap it in a block so that it only appears
// if it is in the current stylesheet's $breakup-included-blocks.
//
// $declaration:    A media query that the content shall be wrapped in
// $block-name:     The block name to display
// $allow-naked:    Should this content should be rendered if the we are
//                    displaying naked content (i.e. not wrapped in a media
//                    query).
@mixin breakup-tweakpoint($declaration, $block-name, $allow-naked: false) {
    @include breakup-block($block-name) {
        @include breakup-media($declaration, $allow-naked) {
            @content;
        }
    }
}
