// Place your application-specific JavaScript functions and classes here
// This file is automatically included by javascript_include_tag :defaults

//things to do when the page loads
$j(document).ready(function() {
    setupToggled();
    if ($j('form#work-form')) { hideFormFields(); }
    hideHideMe();
    showShowMe();
    handlePopUps();
    attachCharacterCounters();
    setupAccordion();
    setupDropdown();
    updateCachedTokens();

    // add clear to items on the splash page in older browsers
    $j('.splash').children('div:nth-of-type(odd)').addClass('odd');

    // make Share buttons on works and own bookmarks visible
    $j('.actions').children('.share').removeClass('hidden');

    // make Approve buttons on inbox items visible
    $j('#inbox-form, .messages').find('.unreviewed').find('.review').find('a').removeClass('hidden');

    prepareDeleteLinks();
    thermometer();
    $j('body').addClass('javascript');
});

///////////////////////////////////////////////////////////////////
// Autocomplete
///////////////////////////////////////////////////////////////////

function get_token_input_options(self) {
  return {
    searchingText: self.data('autocomplete-searching-text'),
    hintText: self.data('autocomplete-hint-text'),
    noResultsText: self.data('autocomplete-no-results-text'),
    minChars: self.data('autocomplete-min-chars'),
    queryParam: "term",
    preventDuplicates: true,
    tokenLimit: self.data('autocomplete-token-limit'),
    liveParams: self.data('autocomplete-live-params'),
    makeSortable: self.data('autocomplete-sortable')
  };
}

// Look for autocomplete_options in application helper and throughout the views to
// see how to use this!
var input = $j('input.autocomplete');
if (input.livequery) {
  jQuery(function($) {
    $('input.autocomplete').livequery(function(){
      var self = $(this);
      var token_input_options = get_token_input_options(self);
      var method;
      try {
          method = $.parseJSON(self.data('autocomplete-method'));
      } catch (err) {
          method = self.data('autocomplete-method');
      }
      self.tokenInput(method, token_input_options);
    });
  });
}

///////////////////////////////////////////////////////////////////

// expand, contract, shuffle
jQuery(function($){
  $(".expand").each(function(){
    // start by hiding the list in the page
    list = $($(this).data("action-target"));
    if (!list.data("force-expand") || list.children().size() > 25 || list.data("force-contract")) {
      list.hide();
      $(this).show();
    } else {
      // show the shuffle and contract button only
      $(this).nextAll(".shuffle").show();
      $(this).next(".contract").show();
    }

    // set up click event to expand the list
    $(this).click(function(event){
      list = $($(this).data("action-target"));
      list.show();

      // show the contract & shuffle buttons and hide us
      $(this).next(".contract").show();
      $(this).nextAll(".shuffle").show();
      $(this).hide();
    });
  });

  $(".contract").each(function(){
    $(this).click(function(event){
      // hide the list when clicked
      list = $($(this).data("action-target"));
      list.hide();

      // show the expand and shuffle buttons and hide us
      $(this).prev(".expand").show();
      $(this).nextAll(".shuffle").hide();
      $(this).hide();
    });
  });

  $(".shuffle").each(function(){
    // shuffle the list's children when clicked
    $(this).click(function(event){
      list = $($(this).data("action-target"));
      list.children().shuffle();
    });
  });

  $(".expand_all").each(function(){
      target = "." + $(this).data("target-class");
      $(this).click(function(event) {
        $(this).closest(target).find(".expand").click();
     });
  });

  $(".contract_all").each(function(){
     target = "." + $(this).data("target-class");
     $(this).click(function(event) {
        $(this).closest(target).find(".contract").click();
     });
  });
});

// check all or none within the parent fieldset, optionally with a string to match on the id attribute of the checkboxes
// stored in the "data-checkbox-id-filter" attribute on the all/none links.
// allow for some flexibility by checking the next and previous fieldset if the checkboxes aren't in this one
jQuery(function($){
  $('.check_all').each(function(){
    $(this).click(function(event){
      var filter = $(this).data('checkbox-id-filter');
      var checkboxes;
      if (filter) {
        checkboxes = $(this).closest('fieldset').find('input[id*="' + filter + '"][type="checkbox"]');
      } else {
        checkboxes = $(this).closest("fieldset").find(':checkbox');
        if (checkboxes.length == 0) {
          checkboxes = $(this).closest("fieldset").next().find(':checkbox');
          if (checkboxes.length == 0) {
            checkboxes = $(this).closest("fieldset").prev().find(':checkbox');
          }
        }
      }
      checkboxes.prop('checked', true);
      event.preventDefault();
    });
  });

  $('.check_none').each(function(){
    $(this).click(function(event){
      var filter = $(this).data('checkbox-id-filter');
      var checkboxes;
      if (filter) {
        checkboxes = $(this).closest('fieldset').find('input[id*="' + filter + '"][type="checkbox"]');
      } else {
        checkboxes = $(this).closest("fieldset").find(':checkbox');
        if (checkboxes.length == 0) {
          checkboxes = $(this).closest("fieldset").next().find(':checkbox');
          if (checkboxes.length == 0) {
            checkboxes = $(this).closest("fieldset").prev().find(':checkbox');
          }
        }
      }
      checkboxes.prop('checked', false);
      event.preventDefault();
    });
  });
});

// Set up open and close toggles for a given object
// Typical setup (this will leave the toggled item open for users without javascript but hide the controls from them):
// <a class="foo_open hidden">Open Foo</a>
// <div id="foo" class="toggled">
//   foo!
//   <a class="foo_close hidden">Close</a>
// </div>
//
// Notes:
// - The open button CANNOT be inside the toggled div, the close button can be (but doesn't have to be)
// - You can have multiple open and close buttons for the same div since those are labeled with classes
// - You don't have to use div and a, those are just examples. Anything you put the toggled and _open/_close classes on will work.
// - If you want the toggled item not to be visible to users without JavaScript by default, add the class "hidden" to the toggled item as well.
//   (and you can then add an alternative link for them using <noscript>)
// - Generally reserved for toggling complex elements like bookmark forms and challenge sign-ups; for simple elements like lists use setupAccordion.
function setupToggled(){
  $j('.toggled').filter(function(){
    return $j(this).closest('.userstuff').length === 0;
  }).each(function(){
    var node = $j(this);
    var open_toggles = $j('.' + node.attr('id') + "_open");
    var close_toggles = $j('.' + node.attr('id') + "_close");

    if (node.hasClass('open')) {
      close_toggles.each(function(){$j(this).show();});
      open_toggles.each(function(){$j(this).hide();});
    } else {
      node.hide();
      close_toggles.each(function(){$j(this).hide();});
      open_toggles.each(function(){$j(this).show();});
    }

    open_toggles.each(function(){
      $j(this).click(function(e){
        if ($j(this).attr('href') == '#') {e.preventDefault();}
        node.show();
        open_toggles.each(function(){$j(this).hide();});
        close_toggles.each(function(){$j(this).show();});
      });
    });

    close_toggles.each(function(){
      $j(this).click(function(e){
        if ($j(this).attr('href') == '#') {e.preventDefault();}
        node.hide();
        close_toggles.each(function(){$j(this).hide();});
        open_toggles.each(function(){$j(this).show();});
      });
    });
  });
}

function hideHideMe() {
    $j('.hideme').each(function() { $j(this).hide(); });
}

function showShowMe() {
    $j('.showme').each(function() { $j(this).show(); });
}

function handlePopUps() {
    $j("a[data_popup]").click(function(event, element) {
      if (event.stopped) return;
      window.open($j(element).attr('href'));
      event.stop();
    });
}

// used in nested form fields for deleting a nested resource
// see prompt form for example
function remove_section(link, class_of_section_to_remove) {
  $j(link).siblings(":input[type=hidden]").val("1"); // relies on the "_destroy" field being the nearest hidden field
  var section = $j(link).closest("." + class_of_section_to_remove);
  section.find(".required input, .required textarea").each(function(index) {
    var element = eval('validation_for_' + $j(this).attr('id'));
    element.disable();
  });
  section.hide();
}

// used with nested form fields for dynamically stuffing in an extra partial
// see challenge signup form and prompt form for an example
function add_section(link, nested_model_name, content) {
    // get the right new_id which should be in a div with class "last_id" at the bottom of
    // the nearest section
    var last_id = parseInt($j(link).parent().siblings('.last_id').last().html());
    var new_id = last_id + 1;
    var regexp = new RegExp("new_" + nested_model_name, "g");
    content = content.replace(regexp, new_id);
    // kludgy: show the hidden remove_section link (we don't want it showing for non-js users)
    content = content.replace('class="hidden showme"', '');
    $j(link).parent().before(content);
}

// An attempt to replace the various work form toggle methods with a more generic one
function toggleFormField(element_id) {
    var ticky = $j('#' + element_id + '-show');
    if (ticky.is(':checked')) {
      $j('#' + element_id).removeClass('hidden');
    }
    else {
        $j('#' + element_id).addClass('hidden');
        if (element_id != 'chapters-options' && element_id != 'backdate-options') {
            $j('#' + element_id).find(':input[type!="hidden"]').each(function(index, d) {
                if ($j(d).attr('type') == "checkbox") {$j(d).attr('checked', false);}
                else {$j(d).val('');}
            });
        }
    }
    // We want to check this whether the ticky is checked or not
    if (element_id == 'chapters-options') {
        var item = document.getElementById('work_wip_length');
        if (item.value == 1 || item.value == '1') {item.value = '?';}
        else {item.value = 1;}
    }
}

// Hides expandable form field options if Javascript is enabled
function hideFormFields() {
  if ($j('form#work-form') != null) {
    var toHide = ['#co-authors-options', '#front-notes-options', '#end-notes-options', '#chapters-options',
      '#parent-options', '#series-options', '#backdate-options', '#override_tags-options'];

    $j.each(toHide, function(index, name) {
      if ($j(name)) {
        if (!($j(name + '-show').is(':checked'))) { $j(name).addClass('hidden'); }
      }
    });
    $j('form#work-form').className = $j('form#work-form').className;
  }
}

// Hides the extra checkbox fields in prompt form
function hideField(id) {
  $j('#' + id).toggle();
}

function attachCharacterCounters() {
    var countFn = function() {
        var counter = (function(input) {
                /* Character-counted inputs do not always have the same hierarchical relationship
                to their associated counter elements in the DOM, and some cc-inputs have
                duplicate ids. So search for the input's associated counter element first by id,
                then by checking the input's siblings, then by checking its cousins. */
                var cc = $j('.character_counter [id='+input.attr('id')+'_counter]');
                if (cc.length === 1) { return cc; } // id search, use attribute selector rather
                // than # to check for duplicate ids

                cc = input.nextAll('.character_counter').first().find('.value'); // sibling search
                if (cc.length) { return cc; }

                var parent = input.parent(); // 2 level cousin search
                for (var i = 0; i < 2; i++) {
                    cc = parent.nextAll('.character_counter').find('.value');
                    if (cc.length) { return cc; }
                    parent = parent.parent();
                }

                return $j(); // return empty jquery element if search found nothing
            })($j(this)),
            max = parseInt(counter.attr('data-maxlength'), 10),
            val = $j(this).val().replace(/\r\n/g,'\n').replace(/\r|\n/g,'\r\n'),
            remaining = max - val.length;

        counter.html(remaining).attr("aria-valuenow", remaining);
    };

    $j(document).on('keyup keydown mouseup mousedown change', '.observe_textlength', countFn);
    $j('.observe_textlength').each(countFn);
}

// add attributes that are only needed in the primary menus and when JavaScript is enabled
function setupDropdown(){
  $j('#header').find('.dropdown').attr("aria-haspopup", true);
  $j('#header').find('.dropdown, .dropdown .actions').children('a').attr({
    'class': 'dropdown-toggle',
    'data-toggle': 'dropdown',
    'data-target': '#'
  });
  $j('.dropdown').find('.menu').addClass("dropdown-menu");
}

// Accordion-style collapsible widgets
// The pane element can be shown or hidden using the expander (link)
// Apply hidden to the pane element if it shouldn't be visible when JavaScript is disabled
// Typical set up:
// <li aria-haspopup="true">
//  <a href="#">Expander</a>
//  <div class="expandable">
//    foo!
//  </div>
// </li>
function setupAccordion() {
  $j(".expandable").filter(function() {
    return $j(this).closest(".userstuff").length === 0;
  }).each(function() {
    var pane = $j(this);
    // hide the pane element if it's not hidden by default
    if ( !pane.hasClass("hidden") ) {
      pane.addClass("hidden");
    };

    // make the expander visible
    // add the default collapsed state
    // make it do the expanding and collapsing
    pane.prev().removeClass("hidden").addClass("collapsed").click(function(e) {
      var expander = $j(this);
      if (expander.attr('href') == '#') {
        e.preventDefault();
      };

      // change the classes upon clicking the expander
      expander.toggleClass("collapsed").toggleClass("expanded").next().toggleClass("hidden");
    });
  });
}

// Remove the /confirm_delete portion of delete links so user who have JS enabled will
// be able to delete items via hyperlink (per rails/jquery-ujs) rather than a dedicated
// form page.
function prepareDeleteLinks() {
  $j('a[href$="/confirm_delete"][data-confirm]').each(function(){
    this.href = this.href.replace(/\/confirm_delete$/, "");
    $j(this).attr("data-method", "delete");
  });

  // Removing non-default orphan_account pseuds from works
  $j('a[href$="/confirm_remove_pseud"][data-confirm]').each(function() {
    this.href = this.href.replace(/\/confirm_remove_pseud$/, "/remove_pseud");
    $j(this).attr("data-method", "put");
  });

  // For purging assignments in gift exchanges. This is only on one page and easy to
  // check, so don't worry about adding a fallback data-confirm message.
  $j('a[href$="/confirm_purge"][data-confirm]').each(function() {
    this.href = this.href.replace(/\/confirm_purge$/, "/purge");
    $j(this).attr("data-method", "post");
  });
}

/// Kudos
$j(document).ready(function() {
  $j('input#kudo_submit').on("click", function(event) {
    event.preventDefault();

    $j.ajax({
      type: 'POST',
      url: '/kudos.js',
      data: jQuery('#new_kudo').serialize(),
      error: function(jqXHR, textStatus, errorThrown) {
        var msg = 'Sorry, we were unable to save your kudos';

        // When we hit the rate limit, the response from Rack::Attack is a plain text 429.
        if (jqXHR.status == "429") {
          msg = "Sorry, you can't leave more kudos right now. Please try again in a few minutes.";
        } else {
          var data = $j.parseJSON(jqXHR.responseText);
          if (data.error_message) {
            msg = data.error_message;
          }
        }

        $j('#kudos_message').addClass('kudos_error').text(msg);
      },
      success: function(data) {
        $j('#kudos_message').addClass('notice').text('Thank you for leaving kudos!');
      }
    });
  });

  // Scroll to the top of the comments section when loading additional pages via Ajax in comment pagination.
  $j('#comments_placeholder').on('click.rails', '.pagination a[data-remote]', function(e){
    $j.scrollTo('#comments_placeholder');
  });

  // Scroll to the top of the comments section when loading comments via AJAX
  $j("#show_comments_link_top").on('click.rails', 'a[href*="show_comments"]', function(e){
    $j.scrollTo('#comments');
  });
});

// For simple forms that appear to toggle between creating and destroying records
// e.g. favorite tags, subscriptions
// <form> needs ajax-create-destroy class, data-create-value, data-destroy-value
// data-create-value: text of the button for creating, e.g. Favorite, Subscribe
// data-destroy-value: text of button for destroying, e.g. Unfavorite, Unsubscribe
// controller needs item_id and item_success_message for save success and
// item_success_message for destroy success
$j(document).ready(function() {
  $j('form.ajax-create-destroy').on("click", function(event) {
    event.preventDefault();

    var form = $j(this);
    var formAction = form.attr('action');
    var formSubmit = form.find('[type="submit"]');
    var createValue = form.data('create-value');
    var destroyValue = form.data('destroy-value');
    var flashContainer = $j('.flash');

    $j.ajax({
      type: 'POST',
      url: formAction,
      data: form.serialize(),
      dataType: 'json',
      success: function(data) {
        flashContainer.removeClass('error').empty();
        if (data.item_id) {
          flashContainer.addClass('notice').html(data.item_success_message);
          formSubmit.val(destroyValue);
          form.append('<input name="_method" type="hidden" value="delete">');
          form.attr('action', formAction + '/' + data.item_id);
        } else {
          flashContainer.addClass('notice').html(data.item_success_message);
          formSubmit.val(createValue);
          form.find('input[name="_method"]').remove();
          form.attr('action', formAction.replace(/\/\d+/, ''));
        }
      },
      error: function(xhr, textStatus, errorThrown) {
        flashContainer.empty();
        flashContainer.addClass('error notice');
        try {
          jQuery.parseJSON(xhr.responseText);
        } catch (e) {
          flashContainer.append("We're sorry! Something went wrong.");
          return;
        }
        $j.each(jQuery.parseJSON(xhr.responseText).errors, function(index, error) {
          flashContainer.append(error + " ");
        });
      }
    });
  });
});

// For simple forms that update or destroy records and remove them from a listing
// e.g. delete from history, mark as read, delete invitation request
// <form> needs ajax-remove class
// controller needs item_success_message
$j(document).ready(function() {
  $j('form.ajax-remove').on("click", function(event) {
    event.preventDefault();

    var form = $j(this);
    var formAction = form.attr('action');
    // The record we're removing is probably in a list, but might be in a table
    if (form.closest('li.group').length !== 0) {
      formParent = form.closest('li.group');
    } else { formParent = form.closest('tr'); };
    // The admin div does not hold a flash container
    var parentContainer = formParent.closest('div:not(.admin)');
    var flashContainer = parentContainer.find('.flash');

    $j.ajax({
      type: 'POST',
      url: formAction,
      data: form.serialize(),
      dataType: 'json',
      success: function(data) {
        flashContainer.removeClass('error').empty();
        flashContainer.addClass('notice').html(data.item_success_message);
      },
      error: function(xhr, textStatus, errorThrown) {
        flashContainer.empty();
        flashContainer.addClass('error notice');
        try {
          jQuery.parseJSON(xhr.responseText);
        } catch (e) {
          flashContainer.append("We're sorry! Something went wrong.");
          return;
        }
        $j.each(jQuery.parseJSON(xhr.responseText).errors, function(index, error) {
          flashContainer.append(error + " ");
        });
      }
    });

    $j(document).ajaxSuccess(function() {
      formParent.slideUp(function() {
        $j(this).remove();
      });
    });
  });
});

// FUNDRAISING THERMOMETER adapted from http://jsfiddle.net/GeekyJohn/vQ4Xn/
function thermometer() {
  var banners = $j('.announcement').filter(function(){
                  return $j(this).closest('.userstuff').length === 0;
                });

  banners.has('.goal').each(function(){
    var banner_content = $j(this).find('blockquote');
        banner_goal_text = banner_content.find('span.goal').html();
        banner_progress_text = banner_content.find('span.progress').html();
        if ($j(this).find('span.goal').hasClass('stretch')){
          stretch = true
        } else { stretch = false }

        goal_amount = parseFloat(banner_goal_text.replace(/\.(?![0-9])|[^\.0-9]/g, ''));
        progress_amount = parseFloat(banner_progress_text.replace(/\.(?![0-9])|[^\.0-9]/g, ''));
        percentage_amount = Math.min( Math.round(progress_amount / goal_amount * 1000) / 10, 100);

    // add thermometer markup (with amounts)
    banner_content.append('<div class="thermometer-content"><div class="thermometer"><div class="track"><div class="goal"><span class="amount">' + banner_goal_text +'</span></div><div class="progress"><span class="amount">' + banner_progress_text + '</span></div></div></div></div>');

    // set the progress indicator
    // darker green for over 100% stretch goals
    // green for 100%
    // yellow-green for 85-99%
    // yellow for 30-84%
    // orange for 0-29%
    if ( stretch == true ) {
      banner_content.find('div.track').css({
        'background': '#8eb92a',
        'background-image': 'linear-gradient(to bottom, #bfd255 0%, #8eb92a 50%, #72aa00 51%, #9ecb2d 100%)'
      });
      banner_content.find('div.progress').css({
        'width': percentage_amount + '%',
        'background': '#4d7c10',
        'background-image': 'linear-gradient(to bottom, #6e992f 0%, #4d7c10 50%, #3b7000 51%, #5d8e13 100%)'
      });
    } else if (percentage_amount >= 100) {
      banner_content.find('div.progress').css({
        'width': '100%',
        'background': '#8eb92a',
        'background-image': 'linear-gradient(to bottom, #bfd255 0%, #8eb92a 50%, #72aa00 51%, #9ecb2d 100%)'
      });
    } else if (percentage_amount >= 85) {
      banner_content.find('div.progress').css({
        'width': percentage_amount + '%',
        'background': '#d2e638',
        'background-image': 'linear-gradient(to bottom, #e6f0a3 0%, #d2e638 50%, #c3d825 51%, #dbf043 100%)'
      });
    } else if (percentage_amount >= 30) {
      banner_content.find('div.progress').css({
        'width': percentage_amount + '%',
        'background': '#fccd4d',
        'background-image': 'linear-gradient(to bottom, #fceabb 0%, #fccd4d 50%, #f8b500 51%, #fbdf93 100%)'
      });
    } else {
      banner_content.find('div.progress').css({
        'width': percentage_amount + '%',
        'background': '#f17432',
        'background-image': 'linear-gradient(to bottom, #feccb1 0%, #f17432 50%, #ea5507 51%, #fb955e 100%)'
      });
    }
  });
}

function updateCachedTokens() {
  // we only do full page caching when users are logged out
  if ($j('#small_login').length > 0) {
    $j.getJSON("/token_dispenser.json", function( data ) {
      var token = data.token;
      // set token on fields
      $j('input[name=authenticity_token]').each(function(){
        $j(this).attr('value', token);
      });
      $j('meta[name=csrf-token]').attr('content', token);
      $j.event.trigger({ type: "loadedCSRF" });
    });
  } else {
    $j.event.trigger({ type: "loadedCSRF" });
  }
}
