<?php

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Used to add the responsive menu options to the menu
 * settings page (/admin/structure/menu/settings).
 */
function responsive_menu_form_menu_configure_alter(&$form, &$form_state) {
  $form['responsive_menu'] = array(
    '#type' => 'fieldset',
    '#title' => t('Responsive menu'),
  );
  $form['responsive_menu']['responsive_menu_menu'] = array(
    '#type' => 'select',
    '#title' => t('Choose which Drupal menu will be rendered as a responsive menu'),
    '#default_value' => variable_get('responsive_menu_menu', 'main-menu'),
    '#options' => menu_get_menus(),
  );
  $form['responsive_menu']['responsive_menu_element'] = array(
    '#type' => 'select',
    '#title' => t('Choose the HTML element to wrap the menu block in'),
    '#default_value' => variable_get('responsive_menu_element', 'nav'),
    '#options' => array(
      'nav' => 'nav',
      'div' => 'div',
    ),
  );
  // Add breakpoint module support
  if (module_exists('breakpoints')) {
    $breakpoints = breakpoints_breakpoint_load_all();
    $queries = array();
    foreach ($breakpoints as $breakpoint) {
      $queries[$breakpoint->machine_name] = $breakpoint->name;
    }
    $form['responsive_menu']['responsive_menu_breakpoint'] = array(
      '#type' => 'select',
      '#title' => t('Choose a breakpoint to trigger the desktop format menu at'),
      '#default_value' => variable_get('responsive_menu_breakpoint', FALSE),
      '#options' => $queries,
    );
    if (empty($queries)) {
      $form['responsive_menu']['responsive_menu_breakpoint']['#disabled'] = TRUE;
      $form['responsive_menu']['responsive_menu_breakpoint']['#description'] = '<div class="description">' . t('You must configure at least one !breakpoint to see any options. Until then the select widget above is disabled.', array(
          '!breakpoint' => l('breakpoint', 'admin/config/media/breakpoints'),
        )) . '</div>';
    }
  }
  else {
    // Fallback to entering a media query string.
    $form['responsive_menu']['responsive_menu_media_query'] = array(
      '#type' => 'textfield',
      '#title' => t('Enter a media query string for the desktop format menu'),
      '#description' => t('For example: (min-width: 960px)'),
      '#default_value' => variable_get('responsive_menu_media_query', '(min-width: 960px)'),
    );
  }
  // A javascript enhancements fieldset.
  $form['responsive_menu']['js'] = array(
    '#type' => 'fieldset',
    '#title' => t('Javascript enhancements'),
  );
  // The hammer js library is optional.
  $form['responsive_menu']['js']['hammer'] = array(
    '#type' => 'checkbox',
    '#title' => t('Add swipe gestures'),
    '#description' => t('Adds the hammer.js library to enhance the mobile experience with swipe gestures to open or close the menu.'),
    '#default_value' => variable_get('responsive_menu_hammer', FALSE),
  );
  // If the libraries module isn't installed or if the hammer.min.js
  // file isn't in the correct location then disable the hammer option
  // and display an appropriate message.
  if (!function_exists('libraries_get_path') || !$hammer_path = libraries_get_path('hammerjs')) {
    $form['responsive_menu']['js']['hammer']['#disabled'] = TRUE;
    $form['responsive_menu']['js']['hammer']['#description'] .= '<br/><span class="warning">' . t('You must have the !libraries module installed and also have downloaded the !hammer file and placed it in a hammerjs directory in libraries. Until then the option below is disabled.', array(
        '!libraries' => l('libraries', 'http://drupal.org/project/libraries'),
        '!hammer' => l('hammer.min.js', 'http://hammerjs.github.io/dist/hammer.min.js'),
      )) . '</span>';
  }

  if (!module_exists('fastclick')) {
    $form['responsive_menu']['js']['fastclick'] = array(
      '#markup' => '<div class="description">' . t('The !fastclick module is highly recommended and will remove the 300ms tap delay on mobile devices.', array(
            '!fastclick' => l('Fastclick', 'https://drupal.org/project/fastclick'),
          )
        ) . '</div>',
    );
  }

  $form['#submit'][] = 'responsive_menu_admin_form_submit';
}

/**
 * Submit handler for the menu settings form.
 */
function responsive_menu_admin_form_submit($form, &$form_state) {
  $values = $form_state['values'];
  variable_set('responsive_menu_menu', $values['responsive_menu_menu']);
  if (module_exists('breakpoints')) {
    $breakpoints = breakpoints_breakpoint_load_all();
    // Check if the breakpoint exists.
    if (isset($breakpoints[$values['responsive_menu_breakpoint']])) {
      // Store the breakpoint for using again in the form.
      variable_set('responsive_menu_breakpoint', $values['responsive_menu_breakpoint']);
      // Also store the actual breakpoint string for use in calling the stylesheet.
      variable_set('responsive_menu_media_query', $breakpoints[$values['responsive_menu_breakpoint']]->breakpoint);
    }
  }
  else {
    variable_set('responsive_menu_media_query', $values['responsive_menu_media_query']);
  }
  // Store the boolean value of the hammer option.
  variable_set('responsive_menu_hammer', $values['hammer']);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function responsive_menu_form_menu_overview_form_alter(&$form, &$form_state) {
  if ($form['#menu']['menu_name'] == variable_get('responsive_menu_menu', 'main-menu')) {
    foreach ($form as $key => $value) {
      if (strpos($key, 'mlid') !== FALSE) {
        if (!empty($form[$key]['#item']['p2']) && empty($form[$key]['#item']['p3'])) {
          $form[$key]['flyleft'] = array(
            '#type' => 'checkbox',
            '#title' => 'Fly left',
            '#title_display' => 'invisible',
            '#default_value' => variable_get('responsive_menu_flyleft_' . $key, FALSE)
          );
        }
      }
    }
    // Alter the theme function to use our own.
    $form['#theme'] = 'responsive_menu_overview_form';
    // Add a submit handler to save the variables for the flyout.
    $form['#submit'][] = 'responsive_menu_overview_form_submit';
  }
}

/**
 * Submit handler to store fly-out variables.
 */
function responsive_menu_overview_form_submit($form, &$form_state) {
  foreach ($form_state['values'] as $key => $value) {
    if (strpos($key, 'mlid') !== FALSE) {
      if (!empty($value['flyleft'])) {
        // Store a variable for this menu item.
        variable_set('responsive_menu_flyleft_' . $key, TRUE);
      }
      else {
        // Remove any possible old variables.
        variable_del('responsive_menu_flyleft_' . $key);
      }
    }
  }
}

/**
 * Implements hook_block_info().
 */
function responsive_menu_block_info() {
  $blocks = array();

  $blocks['toggle'] = array(
    'info' => t('Responsive menu mobile icon'),
    'cache' => DRUPAL_CACHE_PER_PAGE,
  );

  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function responsive_menu_block_view($delta = '') {
  $block = array();

  switch ($delta) {
    case 'toggle':
      return array(
        'subject' => '',
        'content' => array(
          '#markup' => '<label id="toggle-icon" for="main-nav-check" class="toggle" onclick="" title="Menu">&#x2261;</label>',
        ),
      );
  }

  return $block;
}

/**
 * Implements hook_preprocess_html().
 *
 * Adds theme wrappers to the page_top and page_bottom regions
 * so that we can wrap the whole page output in the structure
 * this module needs.
 */
function responsive_menu_preprocess_html(&$variables) {
  if (!path_is_admin(current_path())) {
    $variables['page']['page_top']['#theme_wrappers'][] = 'responsive_menu_page_top_wrapper';
    $variables['page']['page_bottom']['#theme_wrappers'][] = 'responsive_menu_page_bottom_wrapper';
  }
}

/**
 * Implements hook_preprocess_responsive_menu_page_top_wrapper().
 *
 * Prepares the menu variable for rendering in the template.
 * The top and bottom page wrappers ensure that this module's needed
 * structural elements are the first and last on the page within
 * the body tag.
 */
function responsive_menu_preprocess_responsive_menu_page_top_wrapper(&$variables) {
  $menu_name = variable_get('responsive_menu_menu', 'main-menu');
  // Ensure the anything else rendered in page_top region gets printed.
  $variables['content'] = $variables['elements']['#children'];
  // Add the menu.
  $variables['menu'] = responsive_menu_tree_build($menu_name);
}

/**
 * Implements hook_preprocess_responsive_menu_page_bottom_wrapper().
 */
function responsive_menu_preprocess_responsive_menu_page_bottom_wrapper(&$variables) {
  // Ensure the anything else rendered in page_bottom region gets printed.
  $variables['content'] = $variables['elements']['#children'];
}

/**
 * Implements hook_theme().
 */
function responsive_menu_theme($existing, $type, $theme, $path) {
  $return = array();
  $return['responsive_menu_wrapper'] = array(
    'template' => 'responsive-menu-block-wrapper',
    'variables' => array('content' => array()),
    'path' => $path . '/theme',
  );

  $return['responsive_menu_page_top_wrapper'] = array(
    'template' => 'responsive-menu-page-top-wrapper',
    'render element' => 'elements',
    'path' => $path . '/theme',
  );

  $return['responsive_menu_page_bottom_wrapper'] = array(
    'template' => 'responsive-menu-page-bottom-wrapper',
    'render element' => 'elements',
    'path' => $path . '/theme',
  );

  $return['responsive_menu_link'] = array(
    'file' => 'theme/theme.inc',
    'render element' => 'element',
  );

  $return['responsive_menu_tree'] = array(
    'file' => 'theme/theme.inc',
    'render element' => 'tree',
  );

  $return['responsive_menu_overview_form'] = array(
    'file' => 'theme/theme.inc',
    'render element' => 'form',
  );

  return $return;
}

/**
 * Preprocess variables for responsive-menu-block-wrapper.tpl.php.
 *
 * @see responsive-menu-block-wrapper.tpl.php
 */
function template_preprocess_responsive_menu_wrapper(&$variables) {
  $variables['classes_array'][] = 'responsive-menu-block';
  $variables['classes_array'][] = 'menu-name-' . variable_get('responsive_menu_menu', 'main-menu');
  $variables['element_type'] = variable_get('responsive_menu_element', 'nav');
  $variables['content']['#attached']['css'] = array(
    drupal_get_path('module', 'responsive_menu') . '/css/responsive_menu.css' => array(),
    drupal_get_path('module', 'responsive_menu') . '/css/responsive_menu_desktop.css' => array(
      'media' => 'screen and ' . variable_get('responsive_menu_media_query', '(min-width: 960px)'),
      'weight' => 100,
    ),
  );
  // Attach the hammer library for swipe events if available.
  if (variable_get('responsive_menu_hammer', FALSE)) {
    $breakpoint = variable_get('responsive_menu_media_query', '(min-width: 960px)');
    preg_match('/\d{1,4}/', $breakpoint, $matches);
    if (!empty($matches[0])) {
      $breakpoint_value = $matches[0];
      $variables['content']['#attached']['js'][] = array(
        'data' => array('responsive_menu' => array(
          'breakpoint' => $breakpoint_value,
        )),
        'type' => 'setting',
      );
    }
  }

}

/**
 * Build a menu tree based.
 */
function responsive_menu_tree_build($menu_name) {

  // Get the raw menu tree data.
  $tree = responsive_menu_tree_block_data($menu_name);

  // Create a renderable tree.
  $data = array();
  $data['subject'] = '';
  $data['content']['#content'] = responsive_menu_tree_output($tree, $menu_name);
  if (!empty($data['content']['#content'])) {
    $data['content']['#theme'] = array(
      'responsive_menu_wrapper'
    );
    $data['content']['#menu_name'] = $menu_name;
  }
  else {
    $data['content'] = '';
  }

  return $data;
}

/**
 * Returns a renderable menu tree.
 *
 * This is a copy of menu_tree_output() with parts stripped away.
 *
 * @param array $tree
 *   A data structure representing the tree as returned from menu_tree_data.
 * @param string $menu_name
 *   The menu name of the menu being rendered.
 * @return string
 *   The rendered HTML of that data structure.
 */
function responsive_menu_tree_output(&$tree, $menu_name) {
  $build = array();
  $items = array();

  // Create context if no config was provided.
  $config['delta'] = 0;
  $config['menu_name'] = $menu_name;

  // Pull out just the menu links we are going to render so that we
  // get an accurate count for the first/last classes.
  foreach ($tree as $key => &$value) {
    if ($tree[$key]['link']['access'] && !$tree[$key]['link']['hidden']) {
      $items[] = $tree[$key];
    }
  }

  $router_item = menu_get_item();
  $num_items = count($items);
  foreach ($items as $i => &$data) {
    $class = array();
    if ($i == 0) {
      $class[] = 'first';
    }
    if ($i == $num_items - 1) {
      $class[] = 'last';
    }
    // Set a class for the <li>-tag. Since $data['below'] may contain local
    // tasks, only set 'expanded' class if the link also has children within
    // the current menu.
    if ($data['link']['has_children'] && $data['below']) {
      $class[] = 'expanded';
    }
    elseif ($data['link']['has_children']) {
      $class[] = 'collapsed';
    }
    else {
      $class[] = 'leaf';
    }
    if (!empty($data['link']['leaf_has_children'])) {
      $class[] = 'has-children';
    }

    // Set a class if the link is in the active trail.
    if ($data['link']['in_active_trail']) {
      $class[] = 'active-trail';
      $data['link']['localized_options']['attributes']['class'][] = 'active-trail';
    }

    // Determine whether this item should be shown as a fly-left flyout.
    if ($data['below']) {
      // Try to load the variable for this menu item to see whether to add the class.
      if (variable_get('responsive_menu_flyleft_mlid:' . $data['link']['mlid'], FALSE)) {
        $class[] = 'fly-left';
      }
    }

    if ($data['link']['href'] == $_GET['q'] || ($data['link']['href'] == '<front>' && drupal_is_front_page())) {
      $class[] = 'active';
    }
    // Set a menu link ID class.
    $class[] = 'menu-mlid-' . $data['link']['mlid'];
    // Normally, l() compares the href of every link with $_GET['q'] and sets
    // the active class accordingly. But local tasks do not appear in menu
    // trees, so if the current path is a local task, and this link is its
    // tab root, then we have to set the class manually.
    if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != $_GET['q']) {
      $data['link']['localized_options']['attributes']['class'][] = 'active';
    }

    // Define the theme function for the menu link element.
    $element['#theme'] = array(
      'responsive_menu_link',
    );
    $element['#attributes']['class'] = $class;
    $element['#title'] = $data['link']['title'];
    $element['#href'] = $data['link']['href'];
    $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
    $element['#below'] = $data['below'] ? responsive_menu_tree_output($data['below'], $config) : $data['below'];
    $element['#original_link'] = $data['link'];
    $element['#bid'] = array('module' => 'responsive_menu', 'delta' => $config['delta']);
    // Index using the link's unique mlid.
    $build[$data['link']['mlid']] = $element;
  }
  if ($build) {
    // Make sure drupal_render() does not re-order the links.
    $build['#sorted'] = TRUE;
    // Add the theme wrapper for outer markup.
    // Allow menu-specific theme overrides.
    $build['#theme_wrappers'][] = array(
      'responsive_menu_tree',
    );
  }

  return $build;
}

/**
 * Gets the data structure representing a menu tree for the given configuration.
 *
 * @param $config
 *   See the $config param of menu_tree_build().
 */
function responsive_menu_tree_block_data($menu_name) {
  // The maximum depth the CSS menu can handle is 3.
  $max_depth = 4;

  $tree = menu_tree_all_data($menu_name, NULL, $max_depth);
  // And add the active trail data back to the full tree.
  $menu_item = current($tree);
  $tree_with_trail = menu_tree_page_data($menu_item['link']['menu_name']);

  // To traverse the original tree down the active trail, we use a pointer.
  $subtree_pointer = &$tree;

  // Find each key in the active trail.
  while ($tree_with_trail) {
    foreach ($tree_with_trail as $key => &$value) {
      if ($tree_with_trail[$key]['link']['in_active_trail'] && isset($subtree_pointer[$key])) {
        // Set the active trail info in the original tree.
        $subtree_pointer[$key]['link']['in_active_trail'] = TRUE;
        // Continue in the subtree, if it exists.
        $tree_with_trail = &$tree_with_trail[$key]['below'];
        $subtree_pointer = &$subtree_pointer[$key]['below'];
        break;
      }
      else {
        unset($tree_with_trail[$key]);
      }
    }
  }

  // Allow alteration of the tree and config before we begin operations on it.
  drupal_alter('responsive_menu_tree', $tree, $menu_name);

  // Localize the tree.
  if (module_exists('i18n_menu')) {
    $tree = i18n_menu_localize_tree($tree);
  }

  // Trim the branches that extend beyond the specified depth.
  responsive_menu_tree_depth_trim($tree, $max_depth);

  return $tree;
}

/**
 * Prune a tree so it does not extend beyond the specified depth limit.
 *
 * @param $tree
 *   array The menu tree to prune.
 * @param $depth_limit
 *   int The maximum depth of the returned tree; must be a positive integer.
 * @return
 *   void
 */
function responsive_menu_tree_depth_trim(&$tree, $depth_limit) {
  // Prevent invalid input from returning a trimmed tree.
  if ($depth_limit < 1) {
    return;
  }

  // Examine each element at this level to find any possible children.
  foreach ($tree as $key => &$value) {
    if ($tree[$key]['below']) {
      if ($depth_limit > 1) {
        responsive_menu_tree_depth_trim($tree[$key]['below'], $depth_limit - 1);
      }
      else {
        // Remove the children items.
        $tree[$key]['below'] = FALSE;
      }
    }
    if ($depth_limit == 1 && $tree[$key]['link']['has_children']) {
      // Turn off the menu styling that shows there were children.
      $tree[$key]['link']['has_children'] = FALSE;
      $tree[$key]['link']['leaf_has_children'] = TRUE;
    }
  }
}

/**
 * Implements hook_menu_preprocess_block().
 *
 * Removes the contextual links from the toggle icon block.
 */
function responsive_menu_preprocess_block(&$variables) {
  if ($variables['block']->module == 'responsive_menu' && $variables['block']->delta == 'toggle') {
    unset($variables['title_suffix']['contextual_links']);
    // Get rid of the contextual-links-region class.
    if ($key = array_search('contextual-links-region', $variables['classes_array'])) {
      unset($variables['classes_array'][$key]);
    }
  }
}

/**
 * Implements hook_modernizr_load().
 *
 * @return array
 *   The load test and results.
 */
function responsive_menu_modernizr_load() {
  $load = array();
  // Only load the javascript if the option has been selected
  // and the hammer.js library exists.
  if (variable_get('responsive_menu_hammer', FALSE)) {
    if (function_exists('libraries_get_path') && $hammer_path = libraries_get_path('hammerjs')) {
      $load[] = array(
        'test' => "Modernizr.mq('only all')",
        'yep'  => array(
          base_path() . libraries_get_path('hammerjs') . '/hammer.min.js',
          base_path() . drupal_get_path('module', 'responsive_menu') . '/js/responsive_menu.config.js',
        )
      );
    }
  }
  return $load;
}

/**
 * Implements hook_modernizr_info().
 *
 * @return array
 *   An array of tests.
 */
function responsive_menu_modernizr_info() {
  $tests = array();
  // Add the media query test.
  $tests[] = 'mq';
  return $tests;
}