var CONFIG = { debug: false
             , nick: "#"   // set in onConnect
             , id: null    // set in onConnect
             , last_message_time: 1
             , focus: true //event listeners bound in onConnect
             , unread: 0 //updated in the message-processing loop
             , channelInfo: {}
             , channel_id: ''
             , channel_title: ''
             , page_args: {}
             , twitter: null
             , recvLock: 0
             , aConn: null
             , aConnTimeout: null
             };


function parseQuery ( query ) {
   var Params = new Object ();
   if ( ! query ) return Params; // return empty object
   var Pairs = query.split(/[;&]/);
   for ( var i = 0; i < Pairs.length; i++ ) {
      var KeyVal = Pairs[i].split('=');
      if ( ! KeyVal || KeyVal.length != 2 ) continue;
      var key = decodeURIComponent( KeyVal[0] );
      var val = decodeURIComponent( KeyVal[1] );
      val = val.replace(/\+/g, ' ');
      Params[key] = val;
   }
   return Params;
}

CONFIG.page_args = parseQuery( window.location.search.split('?')[1] || '' );

var nicks = [];

var TWITTER_IDS = {}

var TWITTER_USER_TS = {}

//updates the users link to reflect the number of active users
function updateUsersLink ( ucount ) {
  var t = (ucount || nicks.length).toString() + "人なう！";
  $("#usersLink").text(t);
}


function showEmbedCode () {
  var embed_code = '<iframe src="http://nownow.am6.jp/r/'+CONFIG.channel_id+'" width="100%" height="500" frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe><br /><a href="http://nownow.am6.jp/r/'+CONFIG.channel_id+'">'+CONFIG.channel_id+'なうなう！</a>';
  Boxy.ask('<small>下記のコードをブログに貼ってください：</small><div style="position:relative;height:100px;"><textarea style="position:absolute;top:1px;left:0;right:4px;bottom:1px;">'+util.toStaticHTML(embed_code)+'</textarea></div><div style="clear:both;"></div>', {'ok':'閉じる'}, function(v){
  }, {'title':'この部屋のブログパーツ', 'modal':true, title_color:'blue', button_color:'blue'});
}

function channelInfo(message, first_load) {
  $.extend(true, CONFIG.channelInfo, message.text);  
  
  var search_update = function() {
    if (!CONFIG.channelInfo.bitly_hash) return;
    $.ajax({
      url:'http://search.twitter.com/search.json',
      type:'GET',
      data:{q:'"http://am6.jp/'+CONFIG.channelInfo.bitly_hash+'" OR "'+CONFIG.channelInfo.bitly_hash+'"', rpp:'100'},
      dataType:'jsonp',
      success:function(d){
        if (d.results.length) {
          for (var i=d.results.length-1; i>=0; i--) {
            var tweet = d.results[i];
            if (tweet.id in TWITTER_IDS) {
              return;
            }
            TWITTER_IDS[ tweet.id ] = 1;
            addMessage(
              {sn:tweet.from_user, img:tweet.profile_image_url}
              ,tweet.text.split('\u2192')[0].split('RT')[0].split('QT')[0].split('http')[0]
              ,(tweet.created_at || "").replace(/-/g,"/").replace(/[TZ]/g," ")
              ,''
              ,{time_check:1}
            );
          }
        }
      }
    });
  };
  setInterval(search_update, 1000*60*3); //update every three minutes;
  search_update();

  
}


function parseUstreamEmbedCode(embed_code) {
  try {
    var ustream_id = embed_code.match(new RegExp('cid=([0-9]+)'))[1]
  } catch(e) {
    try {
      var ustream_id = embed_code.match(new RegExp('http://www.ustream.tv/flash/live/([0-9]+)'))[1]
    } catch(e) {
      return null;
    }
  }
  return ustream_id;
}

function loadingTwitterPleaseWait() {
  Boxy.ask('Twitterに接続中。少々お待ちください。', {}, function(v){
  }, {'title':'Twitterに接続中・・・', 'modal':true, title_color:'blue', button_color:'blue'});
}

//handles another person joining chat
function userJoin(nick, timestamp) {
  //put it in the stream
  //addMessage(nick, "joined", timestamp, "join");
  //if we already know about this user, ignore it
  for (var i = 0; i < nicks.length; i++)
    if (nicks[i] == nick) return;
  //otherwise, add the user to the list
  nicks.push(nick);
  //update the UI
  updateUsersLink();
}

//handles someone leaving
function userPart(nick, timestamp) {
  //put it in the stream
  //addMessage(nick, "left", timestamp, "part");
  //remove the user from the list
  for (var i = 0; i < nicks.length; i++) {
    if (nicks[i] == nick) {
      nicks.splice(i,1)
      break;
    }
  }
  //update the UI
  updateUsersLink();
}

// utility functions

util = {
  urlRE: /https?:\/\/([-\w\.]+)+(:\d+)?(\/([^\s]*(\?\S+)?)?)?/g, 

  //  html sanitizer 
  toStaticHTML: function(inputHtml) {
    inputHtml = inputHtml.toString();
    return inputHtml.replace(/&/g, "&amp;")
                    .replace(/</g, "&lt;")
                    .replace(/>/g, "&gt;");
  }, 

  //pads n with zeros on the left,
  //digits is minimum length of output
  //zeroPad(3, 5); returns "005"
  //zeroPad(2, 500); returns "500"
  zeroPad: function (digits, n) {
    n = n.toString();
    while (n.length < digits) 
      n = '0' + n;
    return n;
  },

  //it is almost 8 o'clock PM here
  //timeString(new Date); returns "19:49"
  timeString: function (date) {
    var minutes = date.getMinutes().toString();
    var hours = date.getHours().toString();
    return this.zeroPad(2, hours) + "時" + this.zeroPad(2, minutes)+'分';
  },

  //does the argument only contain whitespace?
  isBlank: function(text) {
    var blank = /^\s*$/;
    return (text.match(blank) !== null);
  }
};

//used to keep the most recent messages visible
function scrollDown () {
  //$("#entry").focus();
}

//inserts an event into the stream for display
//the event may be a msg, join or part type
//from is the user, text is the body and time is the timestamp, defaulting to now
//_class is a css class to apply to the message, usefull for system events
function addMessage (from, text, time, _class, options) {
  if (text === null) {
    return;
  }
  
  options = options || {};

  if (time == null) {
    // if the time is null or undefined, use the current time.
    time = new Date();
  } else if ((time instanceof Date) === false) {
    // if it's a timestamp, interpret it
    time = new Date(time);
  }
  
//  if (from.sn) {
//    if (!(from.sn in TWITTER_USER_TS)) {
//      TWITTER_USER_TS[ from.sn ] = 0;
//    }
//    if (time.getTime() <= TWITTER_USER_TS[ from.sn ]) {
//      return; //we probably already have this in the cache!
//    }
//    
//    TWITTER_USER_TS[ from.sn ] = time.getTime();
//  }
  if (options.time_check && time.getTime()-10000 <= CONFIG.last_message_time) {
    return;
  }

  var $timeline = $("#timeline");

  //every message you see is actually a table with 3 cols:
  //  the time,
  //  the person who caused the event,
  //  and the content
  var miniMessageElement = $(document.createElement("li"));
      miniMessageElement.addClass("status");  
  if (_class) {
    miniMessageElement.addClass(_class);
  }
  
  miniMessageElement.addClass($timeline.children().length%2?'odd':'even');
  
  // sanitize
  text = util.toStaticHTML(text);

  if ($.browser.msie || $.browser.safari) {
    miniMessageElement.css('wordBreak', 'break-all');
  }
  
  //add line breaks
  if (!$.browser.msie || !$.browser.safari) {
    text = text.split('').join('&#8203;');
  }
  

//  // If the current user said this, add a special css class
//  var nick_re = new RegExp(CONFIG.nick);
//  if (nick_re.exec(text))
//    messageElement.addClass("personal");

  // replace URLs with links
//  text = text.replace(util.urlRE, '<a target="_blank" href="$&">$&</a>');

  var content = ''
  +'  <span class="thumb vcard author">                                                                       '
  +'    <a href="https://twitter.com/'+from.sn+'" class="tweet-url profile-pic url">                            '//
  +'      <img class="photo fn" height="48" src="'+from.img+'" width="48" />                                              '//
  +'    </a>                                                                                                  '
  +'  </span>                                                                                                 '
  +'  <span class="status-body">                                                                              '
  +'    <span class="status-content">                                                                         '
  +'      <strong><a href="https://twitter.com/'+from.sn+'" class="tweet-url screen-name">'+from.sn+'</a></strong>'//
  +'      <span class="entry-content">'+text+'</span>                                                                 '//
  +'    </span>                                                                                               '
  +'    <span class="meta entry-meta">                                                              '
  +'      <span class="published timestamp">' + util.timeString(time) + '</span>       '//
  +'    </span>                                                                                               '
  +'  </span>                                                                                                 '
  ;
  //minilog text
  miniMessageElement.html(content)
  miniMessageElement.find('a').attr('target', '_blank');
  $timeline.prepend(miniMessageElement);
  
}

var transmission_errors = 0;
var first_poll = true;


//process updates if we have any, request updates from the server,
// and call again with response. the last part is like recursion except the call
// is being made from the response handler, and not at some point during the
// function's execution.
function longPoll (data) {

  if (CONFIG.recvLock && !data) {
    return;
  }
  
  CONFIG.recvLock = 1;

  //process ucount
  if (data) {
    updateUsersLink( data.ucount || 1 );
  }

  //process any updates we may have
  //data will be null on the first call of longPoll
  if (data && data.messages) {
    for (var i = 0; i < data.messages.length; i++) {
      var message = data.messages[i];

      //track oldest message so we only request newer messages from server
      if (message.timestamp > CONFIG.last_message_time)
        CONFIG.last_message_time = message.timestamp;

      //dispatch new messages to their appropriate handlers
      switch (message.type) {
        case "msg":
          if(!CONFIG.focus){
            CONFIG.unread++;
          }
          setTimeout(function(m) {
            return function () {
              var class_attr = (m.nick==CONFIG.nick?'mine':'');
              addMessage((m.auth_sn?{sn:m.auth_sn, img:m.auth_img}:m.nick), m.text, m.timestamp, class_attr, {});
            };
          }($.extend(true, {}, message)), 200*i);
          break;

        case "info":
          channelInfo(message);
          break;

        case "join":
//X:DISABLED 2010-04-14          userJoin(message.nick, message.timestamp);
          break;

        case "part":
//X:DISABLED 2010-04-14          userPart(message.nick, message.timestamp);
          break;
      }
    }
    //update the document title to include unread message count if blurred
    updateTitle();

    //only after the first request for messages do we want to show who is here
    if (first_poll) {
      first_poll = false;
//X:DISABLED 2010-04-14      who();
      updateChannelInfo(1);
    }
  }
  //make another request
  
  if (CONFIG.aConn) {
    CONFIG.aConn.abort();
    CONFIG.aConn = null;
  }
  
  CONFIG.aConn = $.ajax({ cache: false
         , type: "GET"
         , url: "/recv"
         , dataType: "json"
         , data: { since: CONFIG.last_message_time, id: CONFIG.id, channel_id: CONFIG.channel_id }
         , error: function () {
             if (CONFIG.aConnTimeout) {
               clearTimeout( CONFIG.aConnTimeout );
             }
             //addMessage("", "接続リトライ中！", new Date(), "error");
             transmission_errors += 1;
             //don't flood the servers on error, wait 10 seconds before retrying
             setTimeout(longPoll, 5*1000);
             CONFIG.recvLock = 0;
           }
         , success: function (data) {
             if (CONFIG.aConnTimeout) {
               clearTimeout( CONFIG.aConnTimeout );
             }
             transmission_errors = 0;
             CONFIG.recvLock = 0;
             //if everything went well, begin another request immediately
             //the server will take a long time to respond
             //how long? well, it will wait until there is another message
             //and then it will return it to us and close the connection.
             //since the connection is closed when we get data, we longPoll again
             setTimeout(function(){
              longPoll(data);
             }, 100); //let's wait another 50ms before we try and talk to the server
           }
         });
        
    if ("status" in window) {
      window.status = "　";
    }


//    if (CONFIG.aConnTimeout) {
//      clearTimeout( CONFIG.aConnTimeout );
//    }

//    CONFIG.aConnTimeout = setTimeout(function(){
//             if (window.console) {
//              console.log("jane!");
//             }
//    
//      aConn.abort();
//    }, 1000);
    
    
}

//submit a new message to the server
function send(msg) {
  if (CONFIG.debug === false) {
    // XXX should be POST
    // XXX should add to messages immediately
    jQuery.get("/send", {id: CONFIG.id, channel_id: CONFIG.channel_id, text: msg}, function (data) { }, "json");
  }
}

//Transition the page to the state that prompts the user for a nickname
function showConnect () {
  $("#connect").show();
  $("#loading").hide();
  $("#toolbar").hide();
  $("#nickInput").focus();
}

//transition the page to the loading screen
function showLoad () {
  $("#connect").hide();
  $("#loading").show();
  $("#toolbar").hide();
}

//transition the page to the main chat view, putting the cursor in the textfield
function showChat (nick) {
  $("#toolbar").show();
  if (CONFIG.twitter) {
    $(".entry_submit").show();
    $("#entry").css('width', ($("#entry").width()-13)+'px');
  } else {
    $("#entry")[0].disabled = true;
  }

  $("#connect").hide();
  $("#loading").hide();

  scrollDown();
}

//we want to show a count of unread messages when the window does not have focus
function updateTitle(){
  return; //nop
  if (CONFIG.unread) {
    document.title = "(" + CONFIG.unread.toString() + ") なうテレ";
  } else {
    document.title = "なうテレ";
  }
}

//handle the server's response to our nickname and join request
function onConnect (session) {
  if (session.error) {
    alert("error connecting: " + session.error);
    showConnect();
    return;
  }

  CONFIG.nick = session.nick;
  CONFIG.id   = session.id;
  CONFIG.twitter = session.twitter;
  
  if (session.twitter) {
    $(".user_info img").attr('src', session.twitter.profile_image_url);
    $(".user_info .user_name").text(session.twitter.screen_name);
    $(".logout").show();
  } else {
    $(".login").show();
  }

  //update the UI to show the chat
  showChat(CONFIG.nick);

  //listen for browser events so we know to update the document title
  $(window).bind("blur", function() {
    CONFIG.focus = false;
    updateTitle();
  });

  $(window).bind("focus", function() {
    CONFIG.focus = true;
    CONFIG.unread = 0;
    updateTitle();
  });
}

//add a list of present chat members to the stream
function outputUsers () {
  return; //nop
  var nick_string = nicks.length > 0 ? nicks.join(", ") : "(none)";
  addMessage("users:", nick_string, new Date(), "notice");
  return false;
}

//get a list of the users presently in the room, and add it to the stream
function who () {
  jQuery.get("/who", {channel_id: CONFIG.channel_id}, function (data, status) {
    if (status != "success") return;
    nicks = data.nicks;
    updateUsersLink();
  }, "json");
}

function updateChannelInfo (first_load) {
  jQuery.get("/info", {channel_id: CONFIG.channel_id}, function (data, status) {
    if (status != "success") return;
    channelInfo({text:data}, first_load);
  }, "json");
}

function sendEntryMsg () {
  var msg = $("#entry").val().replace("\n", "");
  if (!util.isBlank(msg)) {
    send(msg);
  }
  $("#entry").attr("value", ""); // clear the entry field.
}

function initS1 () {

  //set channel id
  if (window.location.pathname.search("/r/") == 0) {
    CONFIG.channel_id = window.location.pathname.substr(3);
  } else { //work off of referral
    var ref = document.referrer+"";
    if (ref) {
      CONFIG.channel_id = ref;
    } else {
      CONFIG.channel_id = 'tweetbox';
    }
  }
  CONFIG.channel_id = CONFIG.channel_id.split('?')[0].split('#')[0];
  try {
    CONFIG.channel_title = CONFIG.page_args['title'] || '';
  }catch(e) {
    //
  }
  var this_url = 'http://nownow.am6.jp/r/'+CONFIG.channel_id;
  $("#roomLink").attr('href',this_url).text(this_url);
  $("#roomName").html( '&nbsp;<a href="'+this_url+'" target="_blank" style="color:#eeab3d;">#'+CONFIG.channel_id+'</a>&nbsp;なうなう！<a href="/" style="font-size:60%">〈ホームに戻る〉</a>' );

  //submit new messages when the user hits enter if the message isnt blank
  $("#entry").keypress(function (e) {
    if (e.keyCode != 13 /* Return */) return;
    sendEntryMsg();
  });

  $("#usersLink").click(outputUsers);

  //try joining the chat when the user clicks the connect button
  
  var start_chat = function () {
    //lock the UI while waiting for a response
    showLoad();
    var nick = (Math.ceil(Math.random()*(new Date()*1)))+"";

    //make the actual join request to the server
    $.ajax({ cache: false
           , type: "GET" // XXX should be POST
           , dataType: "json"
           , url: "/join"
           , data: {
            channel_id: CONFIG.channel_id
            ,channel_title: CONFIG.channel_title
            ,nick: nick
          }
           , error: function () {
               alert("error connecting to server");
               window.location = '/';
               //showConnect();
             }
           , success: onConnect
           });
    return false;
  };
  
  $("#connectButton").click(start_chat);
  
  start_chat();

  // update the clock every minute
  setInterval(function () {
    var now = new Date();
    $("#currentTime").text(util.timeString(now));
  }, 1000);

  if (CONFIG.debug) {
    $("#loading").hide();
    $("#connect").hide();
    scrollDown();
    return;
  }

  // remove fixtures
  //$("#log table").remove();

  //begin listening for updates right away
  //interestingly, we don't need to join a room to get its updates
  //we just don't show the chat stream to the user until we create a session
  longPoll();

  $(window).resize(function() {
    var winwidth = $(window).width();
    $("#rightColumn")[(winwidth<550?'hide':'show')]();
  }).resize();
  //showConnect();
}
$(window).load(function(){
  setTimeout(initS1, 1500);
});

//if we can, notify the server that we're going away.
$(window).unload(function () {
  jQuery.get("/part", {id: CONFIG.id}, function (data) { }, "json");
});

