var Running = null;
var ConnectFailed = "Request Failed!";
var checkserver = "/cgi-bin/linkchecker/linkcheck.cgi?";
var root_key = "root=";
var check_key = "target=";
var rootSite = "";
var sitesChecked = 0;
var MaxSites  = 500;

var siteSet; //map of sites checked
var siteQueue;
var localSites; //map of local sites
var LinkMap; //keys are links, values are array of pages containing the corresponding key

var SearchTable; //reference to search table
var SearchTableHeadRow;
var SearchTableFootRow;
var SearchTableBody;

var SearchTableRowArray; //get be bothered trying to persuade javascript to get this right
var Cancelled = false;
var brokenLinks = false;

//insert DOM element after a given child
function insertAfter(parent, node, referenceNode) {
    parent.insertBefore(node, referenceNode.nextSibling);
}

function prependChild(parent, node) {
    parent.insertBefore(node, parent.firstChild);
}

//check if list contains something
function listContains( list, element )
{
  var contains = false;

  for( var x in list )
  {
    var iter = list[ x ];
    if( iter == element )
    {
      contains = true;
      break;
    }
  }

  return contains;
}

function removeSearchTable()
{
  if( SearchTable != null )
  {
    MochiKit.DOM.swapDOM(SearchTable, null);
    SearchTable = null;
  }
}

//create a table for search results
function createSearchTable()
{
  if( SearchTable != null )
  {
    removeSearchTable();
  }

  SearchTableRowArray = new Array();

  var row_display = function (row, style) {
      return MochiKit.DOM.TR(null, map(partial(TD, style), row));
  };

  SearchTableHeadRow = THEAD({'class': 'searchHead'},
          row_display(["Status", "Link", "Referrers"], {'class': 'searchHead'}));

  SearchTableBody = TBODY(null);

  SearchTableFootRow = TFOOT(null,
          row_display(["", "", ""]));

  SearchTable = MochiKit.DOM.TABLE({'class': 'searchtable'},
      SearchTableHeadRow,
      SearchTableBody,
      SearchTableFootRow );

  var myDiv = MochiKit.DOM.getElement("SearchTableDiv");

  myDiv.appendChild( SearchTable );
}




function cancelSearch()
{
  if( Running != null )
  {
    Cancelled = true;
    Running = null;
  }
}

//display new search result
function AddSearchResult( linkText, StatusText, ReferText, style, toBottom )
{
  var MaxLen = 75;
  //truncate the linkTest if required
  var dispLink = linkText;

  if( dispLink.length  > MaxLen )
  {
    dispLink = dispLink.substring( 0, MaxLen );
  }

  var link = MochiKit.DOM.A( {'href' : linkText }, dispLink );
  var row = MochiKit.DOM.TR(null, map(partial(TD, {'class': style}), [ StatusText, link, ReferText ]));

  SearchTableRowArray.push( row );

  if( toBottom )
  {
    SearchTableBody.appendChild( row );
  }
  else
  {
    prependChild( SearchTableBody, row );
  }

}

//new brokenLink
function AddNewBrokenLink( link )
{
  //get list of referrers
  var refererList = LinkMap[ link ];
  var referText = createReferList( refererList );

  AddSearchResult( link, "Broken", referText, "brokenLink", false );
}

function createReferList( referList )
{
  var ret = "";

  if( referList != null )
  {
    ret = referList.join( ", " );
  }

  return ret;
}

//add new good link
function AddNewOkLink( link )
{
  var refererList = LinkMap[ link ];

  var referText = createReferList( refererList );

  AddSearchResult( link, "OK", referText, "okLink", true );
}

//get the referrer cell for a given link
function GetReferrerCell( link )
{
  var LinkCol = 1;
  var ReferCol = 2;

  var cell = null;

  for( var i in SearchTableRowArray )
  {
    var myrow = SearchTableRowArray[ i ];

    var linkcel  = myrow.getElementsByTagName("td")[ LinkCol ];
    var refcel = myrow.getElementsByTagName("td")[ ReferCol ];

    // first item element of the childNodes list of mycel
    var linktext = linkcel.childNodes[0].href;

    if( linktext == link )
    {
      cell = refcel;
      break;
    }
  }

  return cell;
}

//go through table find referrer cells to update
function UpdateTableReferrers( linkList )
{
  for( var i in linkList )
  {
    var link = linkList[ i ];

    var cell = GetReferrerCell( link );

    if( cell != null )
    {
      var referList = LinkMap[ link ];

      var oldtext = cell.childNodes[0];
      var newText = document.createTextNode( createReferList( referList ) );

      MochiKit.DOM.swapDOM( oldtext, newText );
    }
  }
}

function Reset()
{
  rootSite = document.linkchecker.rootSite.value;
  sitesChecked = 0;
  siteSet = new Object();
  siteQueue = new Array();
  localSites = new Object();
  LinkMap = new Object();
  Cancelled = false;
  brokenLinks = false;
  createSearchTable();
}

//kick off search
function checkSite()
{
  if( Running == null )
  {
    Reset();
    sendAsyncRequest( rootSite, true );
  }
}

//set status cell
function setStatus( text )
{
  var display = text;

  var MaxLen = 75;

  if( display.length > MaxLen  )
  {
    display = display.substring( 0, MaxLen );
  }

  var x=document.getElementById("Status").rows[0].cells;
  x[0].innerHTML=display;
}

//add scanned site to list
function addScanned( site, isOK )
{
  if( isOK )
  {
    AddNewOkLink( site );
  }
  else
  {
    AddNewBrokenLink( site );
  }
}

//increment number of sites scanned
function incSites()
{
  sitesChecked = sitesChecked + 1;
  var x=document.getElementById("Status").rows[0].cells;

  var sitevar = "pages";

  if( sitesChecked == 1 )
  {
    sitevar = "page";
  }

  x[1].innerHTML= "Checked " + sitesChecked + " " + sitevar;
}

//wrap up and send async, check given testurl
function sendAsyncRequest( testurl, islocal )
{
  if( Cancelled )
  {
    return;
  }

  var url = checkserver + root_key  + encodeURIComponent( document.linkchecker.rootSite.value );
  url = url + "&" + check_key  + encodeURIComponent( testurl );

  //tell user what we're up to
  setStatus( "Checking " + testurl );

  var d = MochiKit.Async.doSimpleXMLHttpRequest( url );

  var gotMetadata = function (meta) {
      ProcessResponseText( testurl, islocal, meta.responseText );
      Running = null;
  };
  var metadataFetchFailed = function (err) {
    alert( ConnectFailed );
    Running = null;
  };

  d.addCallbacks(gotMetadata, metadataFetchFailed);

  Running = d;
}

//extract a xml type tag content from a string
function parseTag( tag, text )
{
  var ret = ""
  var startTag = "<" + tag + ">";
  var endTag = "</" + tag + ">";

  var tagStart = text.indexOf( startTag  );
  var tagEnd = text.indexOf( endTag );

  if( tagStart != -1 && tagEnd != -1 )
  {
    ret = text.substring( tagStart + startTag.length, tagEnd );
  }

  return ret;
}

//parse a python stringlist into a javascript array
function parsePythonStrList( strList )
{
  //if blank shortcut
  if( strList.length == 0 || strList == "[]" )
  {
    return new Array();
  }

  //list encoded as ['string1','string2',....]
  //remove start and end braces
  var parseList = strList.substring( 1, strList.length -1 );

  //split on ,
  var scratchArray = new Array();
  scratchArray = parseList.split( ', ' );

  //need to go through each elements and remove quotes
  for(x in scratchArray)
  {
    scratchArray[ x ] = scratchArray[ x ].substring( 1, scratchArray[ x ].length - 1 );
  }

  //strip any empties
  var returnArray = new Array();
  for( x in scratchArray )
  {
    if( scratchArray[ x ].length > 0 )
    {
      returnArray.push( scratchArray[ x ] );
    }
  }

  return returnArray;
}

//get local links
function GetLocalLinks( responseText )
{
  var localLinkRaw = parseTag( "LocalLinks", responseText );
  var localArray = parsePythonStrList( localLinkRaw );
  return localArray;
}

//get global links
function GetGlobalLinks( responseText )
{
  var globalLinkRaw = parseTag( "GlobalLinks", responseText );
  var globalArray = parsePythonStrList( globalLinkRaw );
  return globalArray;
}

//add to search list if not already there
function AddToSearchList( localLinks, local )
{
  siteSet[ rootSite ] = true;
  localSites[ rootSite ] = true;

  for (var i in localLinks)
  {
    if( ! siteSet.hasOwnProperty( localLinks[ i ] ) )
    {
      siteSet[ localLinks[ i ] ] = true;
      siteQueue[ siteQueue.length ] = localLinks[ i ];

      if( local )
      {
        localSites[ localLinks[ i ] ] = true;
      }
    }
  }
}

//keep track of pages given links live on
//return list of links with updated referrer lists, i.e. not new
function UpdateLinkMap( page, linkList )
{
  var updateLinks = new Array();

  for( var i in linkList )
  {
    var link = linkList[ i ];

    //ignore self linking
    if( link != page )
    {
      var refererList;

      if( LinkMap.hasOwnProperty( link ) )
      {
        //check link isn't already in
        refererList = LinkMap[ link ];
        var add = ! listContains( refererList, page );

        if( add )
        {
          refererList.push( page );
          updateLinks.push( link );
        }
      }
      else
      {
        refererList = new Array();
        LinkMap[ link ] = refererList;
        refererList.push( page );
        updateLinks.push( link );
      }
    }
  }

  return updateLinks;
}

//handle valid responses
function ProcessResponseText( scanned, islocal, responseText )
{
  var state = parseTag( "State", responseText );
  var broken = parseTag( "Broken", responseText ) != "False";

  //make sure root site if url complete
  rootSite = parseTag( "Root", responseText );

  if( state == "OK" )
  {
    if( ! broken )
    {
      incSites();

      var updateLinks1;
      var updateLinks2;

      if( islocal ) //for a local one want to get links and add to queue
      {
        var pageLocalLinks = GetLocalLinks( responseText );
        AddToSearchList( pageLocalLinks, true );
        var globalLinks = GetGlobalLinks( responseText );
        AddToSearchList( globalLinks, false );

        //update link map
        updateLinks1 = UpdateLinkMap( scanned, pageLocalLinks );
        updateLinks2 = UpdateLinkMap( scanned, globalLinks );

      }

      addScanned( scanned, true );

      if( islocal )
      {
        UpdateTableReferrers( updateLinks1 );
        UpdateTableReferrers( updateLinks2 );
      }
    }
    else
    {
      addScanned( scanned, false );
    }
  }
  else
  {
    alert( "Server error on accessing " + scanned + ". The webmaster has been notified\n. Apologies, the application will be fixed shortly." );
    Cancelled = true;
  }

  if( state == "OK" )
  {
    if( sitesChecked >= MaxSites )
    {
      setStatus( "<span class='Finished'>Scanned max allowed sites</span>" );
    }
    else if( siteQueue.length == 0 )
    {
      setStatus( "<span class='Finished'>Scan Completed</span>" );
    }
    else
    {
      var nexturl = "";
      do
        nexturl = siteQueue.shift();
      while( nexturl.length == 0 )

      var isnextlocal = localSites.hasOwnProperty( nexturl );
      sendAsyncRequest( nexturl, isnextlocal );
    }
  }
}

