// ==UserScript==
// @name                Copy listing
// @version             2.5
// @date                2010-04-26
// @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/listing/*
// @include             http://www.etsy.com/your/item/create*
// @include             http://www.etsy.com/your/listings
// @include             http://www.etsy.com/your/listings?*
// ==/UserScript==

var cc_icon = "data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%0F%00%00%00%0F%08%06%00%00%00%3B%D6%95J%00%00%00%01sRGB%00%AE%CE%1C%E9%00%00%00%06bKGD%00%FF%00%FF%00%FF%A0%BD%A7%93%00%00%00%09pHYs%00%00%0A%F0%00%00%0A%F0%01B%AC4%98%00%00%00%07tIME%07%D9%01%15%131%0B%EC%AD%AAI%00%00%02%40IDAT(%CF%5D%93%BBkTQ%10%C6%7F%E7%DC%BB%BBww%137%0F%D4%24%C4%40D%08%0A%01%2B%11l%B4%B0%12%FD%03%14%2C-%03%A6%B1%B4R%14DA%11%ECD%C1N%D0%D2J!%A0%85%18%D4J%7C%90h%08Q%C9c%F3%D8d%F7%DE%7Bf%C6b%93%98%E4%C0%E1%CC)~%F3%7D%7C%C38%F6%9C%1B%E3%2F%0A%40U%D5%C8%F3%80%8A%11%82%92%A7%01Uk%01%E9%BDg%97%0D%C0%EF%85%E3%D8%9F%2B%25q%BD%A3V%AAw%D6%92zgWR%DF%D7%95%D4%E3BT%F7%DE%DD%8E%22%3Fx%F5%D2S%07%E0n%3Dz%15%01%A3fm%A5n%2B%9D94P%BB%3B%7C%B4%87b!%A2Z)%12y%CF%CB'%9FY%5EHi5%B3%FB%CD%F5%EC%8E%88%CE%C6%40g%1C%F9%8F%FD%07j%84%20%88(%0B%9A%F1e%E2%1BI%D1s%F2%F8%10%7D%BD5%CC%60%E8%F0~f%A6%E6%C76%1A)%C0%B5%D8%CC%A8%94%8B%5C%BCpb%DB%BA%19L%BC%FF%CA%DB%C9i~%CC%AC%90n(%E5J%C2%CF%EF%F3%24%D5%08%E7m%CCT%AF%7BS%C5Tw%80%86%9A%A1%06%1D%D5%84%FAj%C6%BB%C9_%F4%0Dws%EA%EC%08%A7%CF%1F%A3P%06%B1%94XDPUB%10%16%97%D6%115%82%82hD.P%ADD%84H%90(%90tT)%95%0B%A8%1A%22%D6%86E%043%08A%08%0AA%60p%A0%97%BE%BE%1E*%E5%02%AF'%3E%B0%BA%D4%A2%F5%DB3%3CZ%40%015%236%03%DB%FC4Si%2B%1B%ED%26%0A%95J%09%E7%1Dj%86%19%88%1A%98a%B6%A5%1C%94%90%0Bs%7F%96%C9%15ri%DFL%C0%C7%11f%BB%B3%C0%C0%01%B1%88%22*%BB%926%40%D9%F9%1A%95Z%89%83%FBj%18%9E%20%8A%A8%12%A7YN%9E%05%0C%C8%95m%CBj%20%9Bjf%C6%EC%D4_%A6%96%E7h42Z%CD%0C%EF%3D%F1F%B3E%B9%14S%2C%1582%D2%8FY%1B4%031p%B1'%13c%B6%BE8ns%FAfm-%25%F2%0E%E7%DCZ%EC%BD%B2%BA%DE%E0%E6%83%E7%C8%96%E7%CDyo%07%D9l%11%5C%98~%F8%F8%CA%A7%5D%7B%E0%1C%2B%AA%DA%BD%B4%DC%E0%3F%D9%AE%B6Br%0E%1Cn%7D%EF%12%FD%03%1D%BAs6%9BYr%C2%00%00%00%00IEND%AEB%60%82";

if ( document.location.href.indexOf( 'listing/' ) > -1 ) {
    // viewing a listing - check the shop and add the link if necessary
    decorateViewListing();
} else if ( document.location.href.indexOf( 'your/listings' ) > -1 ) {
    // in shop management - add copy links
    decorateYourShop();
} else if ( document.location.href.indexOf( 'your/item/create' ) > -1 ) {
    if ( document.location.search.indexOf( 'step=sort' ) > -1 ) {
        var match = document.location.href.match( /create\/(\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 );
            } else {
                match = document.referrer.match( /step=listing/ );
                if ( match ) {
                    // turns out we don't save if we go back, so don't setvalue here at the moment
                    //addSetValue( listing_id, 3 );
                }
            }
        }
        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.search.indexOf( 'step=listing' ) > -1 ) {
        var match = document.location.href.match( /create\/(\d+)/ );
        var listing_id = match[ 1 ];
        if ( document.referrer ) {
            var match = document.referrer.match( /step=listing/ );
            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.search.indexOf( 'step=images' ) > -1 ) {
        // kind a placeholder - can't update images yet
        var match = document.location.href.match( /create\/(\d+)/ );
        var listing_id = match[ 1 ];
        var from = GM_getValue( listing_id + '_from' );
        if ( from ) addSetValue( listing_id, 3 );
    } else {
        // 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 ] );
        }
    }
}

// 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 = /(profile|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[ 2 ];
                } else if ( link.innerHTML == 'profile' ) {
                    // this is the link to the profile in the "seller info" box
                    thisShop = match[ 2 ];
                    // if we find this, we're done
                    break;
                }
            }
        }
    }

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

        var ul = getElementsByClassName( 'page-tools' )[ 0 ];
        var li = document.createElement( 'li' );
        var li = document.createElement( 'li' );
        var img = document.createElement( 'span' );
        img.style.backgroundImage = "none";
        img.className = 'icon';
        img.innerHTML = '<img src="' + cc_icon + '" width="15" height="15" />';
        li.appendChild( img );
        var link = document.createElement( 'a' );
        link.innerHTML = 'Copy this item';
        li.appendChild( link );
        ul.appendChild( li );
        link.addEventListener( 'click', stashListing, false ); // only parse the page on click
        link.style.cursor = 'pointer';
        link.style.color = '#0192B5';
    }
}

// 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.href.match( /listing\/(\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.getElementById( 'item-title' ).getElementsByTagName( 'h1' );
    data.title = h1[ 0 ].innerHTML

    // price and quantity
    var stock = getElementsByClassName( 'item-stock' )[ 0 ];
    if ( stock.innerHTML != 'sold out' ) {
        data.qty = stock.innerHTML.match( /(\d+)/ )[ 1 ];
        var price = getElementsByClassName( 'item-amount' )[ 1 ];
        data.price = extractPrice( price );
    }

    // description
    data.description = toText( document.getElementById( 'item-description' ) );

    // tags and materials
    var content = getElementsByClassName( 'item-data-content' );

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

    // ships from location
    data.shipsFrom = getElementsByClassName( 'ships-from' )[ 0 ].innerHTML.substring( 17 );

    // shipping profile data
    data.shipping = [];
    var ship_tos = getElementsByClassName( 'ship-to' );
    var ship_costs = getElementsByClassName( 'ship-cost' );
    var ship_withs = getElementsByClassName( 'ship-with' );
    for ( var a = 1; a < ship_tos.length; a++ ) {
        var ship_to = ship_tos[ a ].innerHTML.replace( /^\s*/, '' );
        ship_to = ship_to.replace( /\s*$/, '' );
        var costs = [ extractPrice( ship_costs[ a ] ), extractPrice( ship_withs[ a ] ) ];
        data.shipping.push( { location: ship_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/your/item/create?create_from=' + listing_id;
}

// description is text and <br /> tags - store the text and replace the breaks with newlines
// made it recursive to cope with changes made by add-ons like Linkification
function toText ( node ) {
    var nodes = node.childNodes;
    var description = "";
    for ( var c = 0; c < nodes.length; c++ ) {
        if ( nodes[ c ].nodeType == 3 ) {
            if ( ! nodes[ c ].data.match( /^\s*$/ ) ) {
                var str = nodes[ c ].data.replace( /^\s*/, '' );
                str = str.replace( /\s*$/, '' );
                description += str;
            }
        } else if ( nodes[ c ].nodeType == 1 ) {
            if ( nodes[ c ].nodeName == 'BR' ) {
                description += '\n';
            } else if ( nodes[ c ].nodeName != 'H2' && nodes[ c ].nodeName != 'DIV' && nodes[ c ].nodeName != 'P' ) {
                description += toText( nodes[ c ] );
            }
        }
    }
    return description;
}

// add "copy" links under the "edit" links on the page
function decorateYourShop () {
    var actions = getElementsByClassName( 'list-actions' );
    for ( var a = 0; a < actions.length; a++ ) {
        var ul = actions[ a ].getElementsByTagName( 'ul' )[ 0 ];
        if ( ul == null ) continue;
        var link = ul.getElementsByTagName( 'a' )[ 0 ];
        var listing_id = link.href.match( /edit\/(\d+)/ )[ 1 ];
        // create the "copy" link
        var copy = document.createElement( 'li' );
        copy.innerHTML = '<a href="/your/item/create?create_from=' + listing_id + '">Copy</a>';
        ul.appendChild( copy );

        // have to clear any cached listing data - might be out of date
        var copyLink = copy.getElementsByTagName( 'a' )[ 0 ];
        copyLink.addEventListener( 'click', genClear( listing_id ), false );
    }
}

// closure-generator for clearing cached listing data
function genClear ( listing_id ) {
    return function () { GM_setValue( listing_id, '' ) };
}

// add values to the first page of the listing
function create_listing1( from ) {
    // find the right form
    var form = document.getElementById( 'item-form-info' );
    if ( ! form ) return;

    // get the data
    var data = GM_getValue( from );
    if ( ! data ) {
        // no data - need to get it from the API
        toggleLoading( form );
        getData( from );
        return;
    }
    data = eval( data );

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

// request listing data from the API via etsyhacks.com
function getData ( listing_id ) {
    GM_xmlhttpRequest( {
        method: 'GET',
        url: 'http://gm.etsyhacks.com/listing/' + listing_id,
        onload: function ( response ) { storeData( response, listing_id ) }
    } );
}

// stash the data that's returned
function storeData ( response, listing_id ) {
    if ( response.status == 200 ) {
        try {
            var data = eval( '(' + response.responseText + ')' );
            // stash the data
            GM_setValue( listing_id, uneval( data ) );
            // remove the "loading" icon
            toggleLoading();
            // populate the page
            create_listing1( listing_id );
        } catch ( e ) {
        }
    }
}

// show or hide a "loading" message
function toggleLoading ( form ) {
    var loading = document.getElementById( 'eh_loadingData' );
    if ( loading == null ) {
        var div = document.createElement( 'div' );
        var parentNode = document.getElementById( 'item-form-info' ).getElementsByTagName( 'div' )[ 0 ];
        parentNode.insertBefore( div, parentNode.firstChild );
        div.innerHTML = '<img src="/images/ajax-loader.gif" width="16" height="16" style="float: left; margin-right: 8px; margin-bottom: 8px;" /> <strong>Loading listing data</strong><br /><br />';
        div.id = 'eh_loadingData';
    } else {
        loading.style.display = 'none';
    }
}    

// 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;
    for ( var t = 0; t < tags.length; t++ ) tags[ t ] = tags[ t ].replace( / /g, '_' );
    var topTag = tags.shift(); // first tag is handled differently
    select = document.getElementById( 'item-tag-top' );
    var form = select.form;
    for ( var c = 0; c < select.length; c++ ) {
        if ( select.options[ c ].value == topTag ) {
            select.selectedIndex = c;
            break;
        }
    }
    var newTag = getElementsByClassName( 'new', form )[ 0 ];
    ol = document.getElementById( 'item-tags' );
    var lis = ol.getElementsByTagName( 'li' );
    for ( var t in tags ) {
        if ( lis.length > 14 ) break;
        var nodes = unsafeWindow.Etsy.template('tag_template', {category_raw: 'parent:' + tags[ t ], category: tags[ t ]})
        nodes = nodes.replace( /^\s+/, '' );
        var div = document.createElement( 'div' );
        div.innerHTML = nodes;
        ol.insertBefore( div.firstChild, newTag );
    }
}

// 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 = document.getElementById( 'item-form-listing' );


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

    if ( data.section ) {
        // we have a section - find it in the drop-down
        var select = form.elements.namedItem( 'item[section]' );
        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 ) {
        var select = form.elements.namedItem( 'ship_to_country' );
        var button = form.elements.namedItem( 'add_ship_to_country' );
        var reg_select = form.elements.namedItem( 'ship_to_region' );
        var reg_button = reg_select.nextSibling;
        while ( reg_button.nodeName != 'INPUT' ) reg_button = reg_button.nextSibling;
        var evt = document.createEvent( 'MouseEvents' );
        evt.initMouseEvent( 'click', true, true, null, null, null, null, null, null, null, null, null, null, null, null );

        var shown_regions = false;
        // should always have *some* shipping data
        SHIP_TO: for ( var s = 0; s < data.shipping.length; s++ ) {
            record = data.shipping[ s ];
            if ( record.costs[ 1 ] == null ) record.costs[ 1 ] = record.costs[ 0 ];
            if ( record.location == 'Everywhere Else' ) {
                document.getElementById( 'everywhere-shipping' ).checked = true;
                form.elements.namedItem( 'everywhere_shipping[primary]' ).value = record.costs[ 0 ];
                form.elements.namedItem( 'everywhere_shipping[secondary]' ).value = record.costs[ 1 ] ;
            } else {
                for ( var opt = 0; opt < select.options.length; opt++ ) {
                    var option = select.options[ opt ];
                    if ( option.text == record.location ) {
                        select.selectedIndex = opt;
                        button.dispatchEvent( evt );
                        form.elements.namedItem( 'country_shipping[' + option.value + '][primary]' ).value = record.costs[ 0 ];
                        form.elements.namedItem( 'country_shipping[' + option.value + '][secondary]' ).value = ( record.costs[ 1 ] == null ) ? record.costs[ 0 ] : record.costs[ 1 ];
                        continue SHIP_TO;
                    }
                }
                for ( var opt = 0; opt < reg_select.options.length; opt++ ) {
                    var option = reg_select.options[ opt ];
                    if ( option.text == record.location ) {
                        if ( ! shown_regions ) {
                            getElementsByClassName( 'regional-options' )[ 0 ].dispatchEvent( evt );
                            shown_options = true;
                        }
                        reg_select.selectedIndex = opt;
                        reg_button.dispatchEvent( evt );
                        form.elements.namedItem( 'region_shipping[' + option.value + '][primary]' ).value = record.costs[ 0 ];
                        form.elements.namedItem( 'region_shipping[' + option.value + '][secondary]' ).value = ( record.costs[ 1 ] == null ) ? record.costs[ 0 ] : record.costs[ 1 ];
                        continue SHIP_TO;
                    }
                }
            }
        }
    }
}

function extractPrice( node ) {
    var price = node.innerHTML.match( /[\d\.]+/ )[ 0 ];
    return price;
}

// 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;
    }
}

