/*
 * [IsASINInStock.java]
 *
 * Summary: Find out if a given ISBN/ASIN is in stock using AWS API. D E M O C O D E.
 *
 * Copyright: (c) 2012-2017 Roedy Green, Canadian Mind Products, http://mindprod.com
 *
 * Licence: This software may be copied and used freely for any purpose but military.
 *          http://mindprod.com/contact/nonmil.html
 *
 * Requires: JDK 1.8+
 *
 * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
 *
 * Version History:
 *  1.0 2012-03-26 initial version
 */
package com.mindprod.aws;

import com.mindprod.aws.jax.AWSECommerceService;
import com.mindprod.aws.jax.AWSECommerceServicePortType;
import com.mindprod.aws.jax.AwsHandlerResolver;
import com.mindprod.aws.jax.Item;
import com.mindprod.aws.jax.ItemLookup;
import com.mindprod.aws.jax.ItemLookupRequest;
import com.mindprod.aws.jax.Items;
import com.mindprod.aws.jax.Offer;
import com.mindprod.aws.jax.OfferListing;
import com.mindprod.aws.jax.OperationRequest;
import com.mindprod.isbn.ISBNValidate;

import javax.xml.namespace.QName;
import javax.xml.ws.Holder;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

import static java.lang.System.*;

/**
 * Find out if a given ISBN/ASIN is in stock using AWS API. D E M O C O D E.
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 1.0 2012-03-26 initial version
 * @see com.mindprod.aws.FetchBookFacts
 * @see ProbeViaAWS#isAsinInStockViaAWS(String, com.mindprod.stores.OnlineStore)
 * @since 2012-03-26
 */
public class IsASINInStock
    {
    /**
     * true if debugging, and want extra output
     */
    private static final boolean DEBUGGING = false;

    /**
     * true if probe amazon.ca , false amazon.com. Surprisingly this is only referenced twice in the program.
     */
    private static final boolean IS_CANADIAN = true;

    /**
     * fetch public amazon.com-assigned associateTag from environment set awsassociatetag=xxxxx
     */
    private static final String ASSOCIATE_TAG = IS_CANADIAN ? System.getenv( "awsassociatetagca" ) : System.getenv(
            "awsassociatetag" );

    /**
     * fetch confidential amazon.com-assigned awsAccessKeyId from environment set awsaccesskeyid=xxxxx
     */
    private static final String AWS_ACCESS_KEY_ID = System.getenv( "awsaccesskeyid" );

    /**
     * fetch confidential amazon.com-assigned awsSecretAccessKey assigned by Amazon set awssecretaccesskey=xxxxx
     */
    private static final String AWS_SECRET_ACCESS_KEY = System.getenv( "awssecretaccesskey" );

    /**
     * identifies the context in which names should be interpreted in the schema.
     * Why do we need to specify this? It appears FOUR times in the wsdl file. Perhaps as a check the code
     * matches the generated Java.
     */
    private static final String NAME_SPACE_URI = "http://webservices.amazon.com/AWSECommerceService/2013-08-01";

    /**
     * name of the SOAP service
     */
    private static final String QNAME = "AWSECommerceService";

    /**
     * location of JAX schmo, locally cached. for amazon.com in the USA.  Would have to be modified for other countries.
     * Original came from http://ecs.amazonaws.com/AWSECommerceService/AWSECommerceService.wsd
     * Both Amazon.ca and Amazon.com use the same WSDL.
     */
    private static final String WSDL_LOCATION = "file:///E:/com/mindprod/aws/jax/AWSECommerceService.wsdl";

    /**
     * standard boilerplate to connect to amazon.com AWS server with SOAP
     *
     * @return port to use to send requests.
     * @throws java.net.MalformedURLException if some URLs were mis-specified in the following code.
     */
    private static AWSECommerceServicePortType getPort() throws MalformedURLException
        {
        // Set the service:
        AWSECommerceService service = new AWSECommerceService( new URL( WSDL_LOCATION ), new QName( NAME_SPACE_URI,
                QNAME ) );
        // AwsHandlerResolver does the timestamp, signing and Base64 encoding
        service.setHandlerResolver( new AwsHandlerResolver( AWS_SECRET_ACCESS_KEY ) );
        // get the corresponding service port. This looks up the endpoint server.
        return IS_CANADIAN ? service.getAWSECommerceServicePortCA() : service.getAWSECommerceServicePort();
        }

    /**
     * probe the amazon AWS api to find if a given asin is in stock.
     *
     * @param asin Amazon part number
     *
     * @return true if product is in stock.
     * @throws java.net.MalformedURLException if generated URL is malformed
     */
    private static boolean isAsinInStock( final String asin ) throws MalformedURLException
        {
        final AWSECommerceServicePortType port = getPort();
        // new ItemLookup
        final ItemLookup itemLookup = new ItemLookup();
        // new ItemLookupRequest which is part of the ItemLookup
        final ItemLookupRequest itemLookupRequest = new ItemLookupRequest();
        // odd our lookup request to the the Lookup.
        final List<ItemLookupRequest> itemLookupRequests = itemLookup.getRequest();
        itemLookupRequests.add( itemLookupRequest );
        // Set up the values of the ItemLookupRequest
        // lookup of variants only works with ASIN
        itemLookupRequest.setIdType( "ASIN" );
        itemLookupRequest.getItemId().add( asin );    // there is no setItemID method
        itemLookupRequest.setMerchantId( "All" );
        itemLookupRequest.setCondition( "All" );
        // specify info to include in response
        final List<String> responseGroup = itemLookupRequest.getResponseGroup();
        responseGroup.add( "Offers" );
        // set up Holder for the response tree
        final Holder<OperationRequest> operationRequest = new Holder<>();
        final Holder<List<Items>> items = new Holder<>();
        final String marketplaceDomain = "";  // not yet supported
        final String xmlEscaping = "Single";
        final String validate = "";
        // Probe Amazon Server, WARNING! Amazon changes the parms to this method frequently.
        // Inserts awsAccessKeyID and associateTag
        // note order validate, xmlEscaping, different from itemSearch
        port.itemLookup( marketplaceDomain, AWS_ACCESS_KEY_ID, ASSOCIATE_TAG, validate, xmlEscaping,
                itemLookupRequest, itemLookupRequests, operationRequest, items );
        // analyse results
        final List<Items> result = items.value;
        final int size = result.get( 0 ).getItem().size();
        for ( int i = 0; i < size; i++ )
            {
            final Item item = result.get( 0 ).getItem().get( i );
            for ( Offer offer : item.getOffers().getOffer() )
                {
                for ( OfferListing offerListing : offer.getOfferListing() )
                    {
                    final String availabilityType = offerListing.getAvailabilityAttributes().getAvailabilityType();
                    if ( true )
                        {
                        // might be "now" or "unknown" (temp out of stock) or "futureDate"
                        out.println( "asin = " + asin + " availabilityType = " + availabilityType );
                        }
                    if ( availabilityType.equals( "now" ) )
                        {
                        return true;
                        }
                    }
                }
            } //end loop to process results
        return false;
        }

    /**
     * convert isbn13 to asin
     *
     * @param isbn13 isbn13
     *
     * @return corresponding asin, if no match, returns null.
     * @throws java.net.MalformedURLException if generated URL is malformed.
     */
    private static String isbnToAsin( final String isbn13 ) throws MalformedURLException
        {
        // for amazon.com in USA
        // define magic configuring strings
        final AWSECommerceServicePortType port = getPort();
        // new ItemLookup
        final ItemLookup itemLookup = new ItemLookup();
        // new ItemLookupRequest which is part of the ItemLookup
        final ItemLookupRequest itemLookupRequest = new ItemLookupRequest();
        // odd our lookup request to the the Lookup.
        final List<ItemLookupRequest> itemLookupRequests = itemLookup.getRequest();
        itemLookupRequests.add( itemLookupRequest );
        // Set up the values of the ItemLookupRequest
        itemLookupRequest.setSearchIndex( "Books" );
        itemLookupRequest.setIdType( "EAN" );
        itemLookupRequest.getItemId().add( isbn13 );    // there is no setItemID method
        itemLookupRequest.setMerchantId( "All" );
        itemLookupRequest.setCondition( "All" );
        // specify info to include in response
        final List<String> responseGroup = itemLookupRequest.getResponseGroup();
        responseGroup.add( "ItemAttributes" );
        // set up Holder for the response tree
        final Holder<OperationRequest> operationrequest = new Holder<>();
        final Holder<List<Items>> items = new Holder<>();
        final String marketplaceDomain = "";  // not yet supported
        final String xmlEscaping = "Single";
        final String validate = "";
        // Probe Amazon Server, WARNING! Amazon changes the parms to this method frequently.
        // Inserts awsAccessKeyID and associateTag
        // note order validate, xmlEscaping, different from itemSearch
        port.itemLookup( marketplaceDomain, AWS_ACCESS_KEY_ID, ASSOCIATE_TAG, validate, xmlEscaping,
                itemLookupRequest, itemLookupRequests, operationrequest, items );
        // analyse results
        final List<Items> result = items.value;
        final int size = result.get( 0 ).getItem().size();
        for ( int i = 0; i < size; i++ )
            {
            final Item item = result.get( 0 ).getItem().get( i );
            final String asin = item.getASIN();
            if ( asin != null )
                {
                return asin;
                }
            } //end loop to process results
        return null;
        }

    /**
     * Probe the amazon AWS api for isbn13 or asin to find out if instock.
     * Gives extremely inaccurate results compared with going to the website.
     * Probes US website.
     *
     * @param args list of isbn13/asins to probe.
     *
     * @throws java.io.IOException if trouble reading/writing files
     */
    public static void main( String[] args ) throws IOException
        {
        if ( DEBUGGING )
            {
            out.println( "ASSOCIATE_TAG = " + ASSOCIATE_TAG );
            out.println( "AWS_ACCESS_KEY_ID = " + AWS_ACCESS_KEY_ID );
            out.println( "AWS_SECRET_ACCESS_KEY = " + AWS_SECRET_ACCESS_KEY );
            }
        for ( String arg : args )
            {
            if ( arg.length() == 13 )
                {
                final String isbn13 = ISBNValidate.tidyISBN10or13RemovingDashes( arg );
                final String asin = isbnToAsin( isbn13 );
                if ( asin == null )
                    {
                    err.println( isbn13 + " is has no valid asin" );
                    }
                else
                    {
                    final boolean inStock = isAsinInStock( asin );
                    out.println( "isbn13: " + arg + " asin: " + asin + " " + inStock );
                    }
                }
            else
                {
                final boolean inStock = isAsinInStock( arg );
                out.println( "asin: " + arg + " " + inStock );
                }
            }
        }
    }