// ==UserScript==
// @name                Copy listing
// @version             1.0
// @date                2009-01-19
// @author              Ian Malpass ( ian AT etsyhacks DOT com )
// @namespace           etsy.com
// @description         Create a new listing like an existing listing
// @include             http://www.etsy.com/view_listing.php?*
// @include             http://www.etsy.com/create_listing*
// ==/UserScript==

var cc_icon = "data:image/gif,GIF89a%10%00%10%00%A2%05%00%BB%E4%F1D%B4%DAw%C9%E4%FF%FF%FF%00%99%CC%FF%FF%FF%00%00%00%00%00%00!%F9%04%01%00%00%05%00%2C%00%00%00%00%10%00%10%00%00%037X%BA%DC%FE%8F%C8I%1B%BD%93%111%C6%E4%1E%C1%04%83%00x%A4%E9%8D%E5%94f%CB%2B%C9b%DC%CEw%AD%D04%AB%12%A9%93%AE%B0%E9%7C%8CC%0C%C6%A2%94%40%9EPH%02%00%3B";

if ( document.location.href.indexOf( 'view_listing.php' ) > -1 ) {
    // viewing a listing - check the shop and add the link if necessary
    decorateViewListing();
} else if ( document.location.href.indexOf( 'create_listing1.php' ) > -1 ) {
    // on the first page of creating a new listing
    var match = document.location.search.match( /create_from=(\d+)/ );
    if ( match ) {
        // we have a "create from" argument - we're copying a current listing
        create_listing1( match[ 1 ] );
    }
} else if ( document.location.href.indexOf( 'create_listing2.php' ) > -1 ) {
    var match = document.location.search.match( /listing_id=(\d+)/ );
    var listing_id = match[ 1 ];
    if ( document.referrer ) {
        var match = document.referrer.match( /create_from=(\d+)/ );
        if ( match ) {
            // we've come from an item creation page that's a copy of a current listing
            // map the new listing ID to the copied item's ID for future reference
            GM_setValue( listing_id + '_from', match[ 1 ] );
            // and flag that we've set values for the first page, so we don't overwrite them
            // later if we use the "previous" button
            addSetValue( listing_id, 1 );
        }
    }
    var from = GM_getValue( listing_id + '_from' );
    // if we're copying a current listing and we haven't set values for page 2 already,
    // set the values
    if ( from && ! checkSetValue( listing_id, 2 ) ) create_listing2( listing_id, from );
} else if ( document.location.href.indexOf( 'create_listing3.php' ) > -1 ) {
    var match = document.location.search.match( /listing_id=(\d+)/ );
    var listing_id = match[ 1 ];
    if ( document.referrer ) {
        var match = document.referrer.match( /create_listing3.php/ );
        if ( match ) {
            // we've updated a shipping profile - mark that we've already set this page's values
            addSetValue( listing_id, 3 );
        }
    }
    var from = GM_getValue( listing_id + '_from' );
    if ( from ) addSetValue( listing_id, 2 ); // copying a listing, so we've set values for page 2
    // if we're copying a current listing and we haven't set values for page 3 already,
    // set the values
    if ( from && ! checkSetValue( listing_id, 3 ) ) create_listing3( listing_id, from );
} else if ( document.location.href.indexOf( 'create_listing4.php' ) > -1 ) {
    // kind a placeholder - can't update images yet
    var match = document.location.search.match( /listing_id=(\d+)/ );
    var listing_id = match[ 1 ];
    var from = GM_getValue( listing_id + '_from' );
    if ( from ) addSetValue( listing_id, 3 );
}

// utility function to manage the array of "pages we've set values for for this listing"
function addSetValue( listing_id, page ) {
    var values = GM_getValue( listing_id + '_set' );
    values = ( values == null ) ? [] : eval( values );
    values[ page ] = true;
    GM_setValue( listing_id + '_set', uneval( values ) );
}    

// utility function for querying the array of "pages we've set value for for this listing"
function checkSetValue( listing_id, page ) {
    var values = GM_getValue( listing_id + '_set' );
    values = ( values == null ) ? [] : eval( values );
    return values[ page ];
}

// check the shop, and if it's our shop, add the "copy" link
function decorateViewListing () {
    var links = document.getElementsByTagName( 'a' );
    var shopPattern = /shop.php\?user_id=(\d+)/; // find shop links

    var yourShop; // what's your shop ID?
    var thisShop; // what's the current shop's ID?

    var l = 0;
    for ( 1; l < links.length; l++ ) {
        var link = links[ l ];
        if ( link.href ) {
            var match = shopPattern.exec( link.href );
            if ( match ) {
                // we have a shop link
                if ( link.title == 'Your Shop' ) {
                    // this is the link to your shop in the top nav bar
                    yourShop = match[ 1 ];
                } else if ( link.firstChild && link.firstChild.data && link.firstChild.data == 'shop' ) {
                    // this is the link to the shop in the "seller info" box
                    thisShop = match[ 1 ];
                    // if we find this, we're done
                    break;
                }
            }
        }
    }

    if ( yourShop == thisShop ) {
        // it's our item
        // find the listing ID
        var match = window.location.search.match( /listing_id=(\d+)/ );
        var listing_id = match[ 1 ];

        // search for the "report to Etsy" link
        for ( 1; l < links.length; l++ ) {
            var link = links[ l ];
            if ( link.firstChild && link.firstChild.data && link.firstChild.data == 'Report this item to Etsy' ) {
                // found it - append the edit and delete links to that cell
                reportRow = link.parentNode;
                while ( reportRow && reportRow.nodeName != 'TR' ) reportRow = reportRow.parentNode;
                // alter the report link to become the "copy item" link
                var copyRow = reportRow.cloneNode( true );
                var copyLink = copyRow.getElementsByTagName( 'a' )[ 0 ];
                copyLink.innerHTML = 'Copy this item';
                copyLink.addEventListener( 'click', stashListing, false ); // only parse the page on click
                copyLink.style.cursor = 'pointer';
                copyLink.removeAttribute( 'href' ); // going to use an onclick handler instead
                var copyImg = copyRow.getElementsByTagName( 'img' )[ 0 ];
                copyImg.src = cc_icon;
                copyImg.alt = 'copy this item';
                copyImg.width = 15;
                copyImg.height = 15;
                // attach to document, under the "report to Etsy" link
                reportRow.parentNode.insertBefore( copyRow, reportRow.nextSibling );
                break;
            }
        }
    }
}

// does the work of parsing the page and extracting the data, then redirects to "create_listing1.php"
// with a "create_from" argument. Argument is ignored by Etsy, but flags the copy to this script.

function stashListing () {
    var match = document.location.search.match( /listing_id=(\d+)/ );
    var listing_id = match[ 1 ];
    var data = {}; // listing data object

    // find the section, if there is one
    var links = document.getElementsByTagName( 'a' );
    for ( var l = 0; l < links.length; l++ ) {
        var link = links[ l ];
        if ( link.href ) {
            var match = link.href.match( /section_id=(\d+)/ );
            if ( match ) {
                // found it - store the section ID
                data.section = match[ 1 ];
                break;
            }
        }
    }

    // item title
    var h1 = document.getElementsByTagName( 'h1' );
    data.title = h1[ 0 ].innerHTML

    // bit of a blunt approach to finding the quantity and price but it copes better with
    // sold item listings this way, which have no price or quantity to store
    var dgt = getElementsByClassName( 'dark_grey_text' );
    for ( var d = 0; d < dgt.length; d++ ) {
        if ( dgt[ d ].innerHTML.match( 'in stock' ) ) {
            // found a quantity
            costTable = dgt[ d ].parentNode;
            while ( costTable && costTable.parentNode.nodeName != 'TABLE' ) costTable = costTable.parentNode;
            if ( costTable ) {
                var tds = costTable.getElementsByTagName( 'td' );
                data.price = tds[ 2 ].innerHTML; // stash the price
                data.qty = tds[ 5 ].innerHTML;
                data.qty = data.qty.substr( 0, data.qty.length - 9 ); // stash the quantity
            }
        }
    }

    // locate the table containing the rest of the listing data
    var listingTable = h1[ 0 ].parentNode;
    while ( listingTable && listingTable.parentNode.nodeName != 'TABLE' ) listingTable = listingTable.parentNode;
    if ( ! listingTable ) return;

    tds = listingTable.getElementsByTagName( 'td' );

    // description
    var nodes = tds[ 4 ].childNodes;
    data.description = "";
    // description is text and <br /> tags - store the text and replace the breaks with newlines
    for ( var c = 0; c < nodes.length; c++ ) {
        if ( nodes[ c ].nodeType == 3 ) {
            data.description += nodes[ c ].data;
        } else if ( nodes[ c ].nodeType == 1 && nodes[ c ].nodeName == 'BR' ) {
            data.description += '\n';
        }
    }

    // tags and materials
    var trs = tds[ 7 ].getElementsByTagName( 'tr' );
    var tags = trs[ 0 ];
    var materials = trs[ 2 ];

    // extract tags names from the links
    data.tags = [];
    var links = tags.getElementsByTagName( 'a' );
    for ( var l = 0; l < links.length; l++ ) {
        data.tags.push( links[ l ].innerHTML );
    }

    // extract materials from the links
    data.materials = [];
    links = materials.getElementsByTagName( 'a' );
    for ( var l = 0; l < links.length; l++ ) {
        data.materials.push( links[ l ].innerHTML );
    }

    // ships from location
    data.shipsFrom = tds[ 22 ].innerHTML.substring( 17 );

    // shipping profile data
    var shippingTd = tds[ 25 ].getElementsByTagName( 'td' );
    data.shipping = [];
    for ( var t = 0; t < shippingTd.length; t += 2 ) {
        // ships to
        var to = shippingTd[ t ].innerHTML;
        to = to.substr( 2, to.length - 3 ); // strip the extraneous ornamentation
        // extract the costs
        var costs = shippingTd[ t + 1 ].innerHTML.match( /\$\d+\.\d{2}/g );
        for ( var c = 0; c < costs.length; c++ ) {
            costs[ c ] = costs[ c ].substr( 1 );
        }
        data.shipping.push( { location: to, costs: costs } ); // stash the shipping data
    }

    // stash the data
    GM_setValue( listing_id, uneval( data ) );

    // redirect to create_listing1 with a "create_from" argument
    document.location.href = 'http://www.etsy.com/create_listing1.php?create_from=' + listing_id;
}

// add values to the first page of the listing
function create_listing1( from ) {
    // find the right form
    var form;
    for ( var f = 0; f < document.forms.length; f++ ) {
        if ( document.forms[ f ].action.indexOf( 'create_listing' ) > -1 ) {
            form = document.forms[ f ];
            break;
        }
    }
    if ( ! form ) return;

    // get the data
    var data = GM_getValue( from );
    if ( ! data ) return;
    data = eval( data );

    // set the values
    if ( data.title ) {
        form.elements.namedItem( 'title' ).value = data.title;
    }        
    if ( data.description ) {
        form.elements.namedItem( 'description' ).value = data.description;
    }        
    if ( data.materials ) {
        form.elements.namedItem( 'materials' ).value = data.materials.join( ", " );
    }        
}

// add the tags on the second page of the listing process
function create_listing2( listing_id, from ) {
    if ( ! from ) return;

    // get the data
    var data = GM_getValue( from );
    if ( ! data ) return;
    data = eval( data );

    var tags = data.tags;
    var topTag = tags.shift(); // first tag is handled differently
    topTag = topTag.replace( / /g, '_' );

    var tagLand = document.getElementById( 'tagLand' );
    if ( ! tagLand ) return;

    // deal with the first tag
    var select = tagLand.getElementsByTagName( 'select' )[ 0 ];
    for ( var c = 0; c < select.length; c++ ) {
        if ( select.options[ c ].value == topTag ) {
            // found it - select it and then call the "mainCategorySelector()" function in the main document
            select.selectedIndex = c;
            unsafeWindow.mainCategorySelector( topTag )
            break;
        }
    }
    // loop through the rest of the tags, calling the addTag() function in the main document
    for ( var t = 0; t < tags.length; t++ ) {
        unsafeWindow.addTag( tags[ t ] );
    }
}

// adds price, quantity, section, and shipping info
function create_listing3( listing_id, from ) {
    if ( ! from ) return;

    // get the data
    var data = GM_getValue( from );
    if ( ! data ) return;
    data = eval( data );

    // find the form
    var form;
    for ( var f = 0; f < document.forms.length; f++ ) {
        if ( document.forms[ f ].action.indexOf( 'create_listing' ) > -1 ) {
            form = document.forms[ f ];
            break;
        }
    }
    if ( ! form ) return;

    if ( data.price ) {
        form.elements.namedItem( 'price' ).value = data.price;
    }        
    if ( data.quantity ) {
        form.elements.namedItem( 'quantity' ).value = data.quantity;
    }

    if ( data.section ) {
        // we have a section - find it in the drop-down
        var select = form.elements.namedItem( 'section_pick' );
        for ( var o = 0; o < select.options.length; o++ ) {
            if ( select.options[ o ].value == data.section ) {
                select.selectedIndex = o;
                break;
            }
        }
    }
    if ( data.shipsFrom ) {
        // should always have a "ships from"
        // find it and select the value in the drop-down
        var select = form.elements.namedItem( 'ship_from_country' );
        for ( var o = 0; o < select.options.length; o++ ) {
            if ( select.options[ o ].innerHTML == data.shipsFrom ) {
                select.selectedIndex = o;
                break;
            }
        }
    }
    if ( data.shipping ) {
        // should always have *some* shipping data
        for ( var s = 0; s < data.shipping.length; s++ ) {
            var record = data.shipping[ s ];
            if ( s > 0 ) {
                // call the addDestination() function in the parent window to add an additional destination
                unsafeWindow.addDestination();
            }

            // all the shipping input elements are named the same, so we need to find the last one
            var select;
            for ( var e = form.elements.length - 1; e >= 0; e-- ) {
                if ( form.elements[ e ].name && form.elements[ e ].name == 'destination_country[]' ) {
                    select = form.elements[ e ];
                    break;
                }
            }
            // then find the country and select it in the list
            for ( var o = 0; o < select.options.length; o++ ) {
                if ( select.options[ o ].innerHTML == record.location ) {
                    select.selectedIndex = o;
                    break;
                }
            }

            // same rigamarole for finding the primary shipping price
            var primary;
            for ( var e = form.elements.length - 1; e >= 0; e-- ) {
                if ( form.elements[ e ].name && form.elements[ e ].name == 'destination_primary_shipping[]' ) {
                    primary = form.elements[ e ];
                    break;
                }
            }
            primary.value = record.costs[ 0 ];

            // and the secondary
            var secondary;
            for ( var e = form.elements.length - 1; e >= 0; e-- ) {
                if ( form.elements[ e ].name && form.elements[ e ].name == 'destination_secondary_shipping[]' ) {
                    secondary = form.elements[ e ];
                    break;
                }
            }
            // defaults to the same as the primary
            secondary.value = ( record.costs[ 1 ] == null ) ? record.costs[ 0 ] : record.costs[ 1 ];
        }
    }
}

// utility function to replicate getElementsByClassName() on older Firefoxes
function getElementsByClassName ( class, node ) {
    if ( node == null ) node = document;
    if ( node.getElementsByClassName ) {
        return node.getElementsByClassName( class );
    } else {
        var classElements = new Array();
        var els = node.getElementsByTagName( '*' );
        var elsLen = els.length;
        var pattern = new RegExp("(^|\\s)"+class+"(\\s|$)");
        for (i = 0, j = 0; i < elsLen; i++) {
            if ( pattern.test(els[i].className) ) {
                classElements[j] = els[i];
                j++;
            }
        }
        return classElements;
    }
}


