package com.salesmanager.core.business.modules.cms.product.infinispan;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.commons.io.IOUtils;
import org.infinispan.tree.Fqn;
import org.infinispan.tree.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.salesmanager.core.business.constants.Constants;
import com.salesmanager.core.business.exception.ServiceException;
import com.salesmanager.core.business.modules.cms.impl.CacheManager;
import com.salesmanager.core.business.modules.cms.product.ProductImageGet;
import com.salesmanager.core.business.modules.cms.product.ProductImagePut;
import com.salesmanager.core.business.modules.cms.product.ProductImageRemove;
import com.salesmanager.core.model.catalog.product.Product;
import com.salesmanager.core.model.catalog.product.file.ProductImageSize;
import com.salesmanager.core.model.catalog.product.image.ProductImage;
import com.salesmanager.core.model.content.FileContentType;
import com.salesmanager.core.model.content.ImageContentFile;
import com.salesmanager.core.model.content.OutputContentFile;
import com.salesmanager.core.model.merchant.MerchantStore;

/**
 * Manager for storing in retrieving image files from the CMS This is a layer on top of Infinispan
 * https://docs.jboss.org/author/display/ISPN/Tree+API+Module
 * 
 * Manages
 * - Product images
 * @author Carl Samson
 */
public class CmsImageFileManagerImpl
    implements ProductImagePut, ProductImageGet, ProductImageRemove
{

    private static final Logger LOGGER = LoggerFactory.getLogger( CmsImageFileManagerImpl.class );

    private static CmsImageFileManagerImpl fileManager = null;
    
    private final static String ROOT_NAME = "product-merchant";
    
    private final static String SMALL = "SMALL";
    private final static String LARGE = "LARGE";
    
    private String rootName = ROOT_NAME;

    private CacheManager cacheManager;

    /**
     * Requires to stop the engine when image servlet un-deploys
     */
    public void stopFileManager()
    {

        try
        {
        	cacheManager.getManager().stop();
            LOGGER.info( "Stopping CMS" );
        }
        catch ( Exception e )
        {
            LOGGER.error( "Error while stopping CmsImageFileManager", e );
        }
    }

    public static CmsImageFileManagerImpl getInstance()
    {

        if ( fileManager == null )
        {
            fileManager = new CmsImageFileManagerImpl();
        }

        return fileManager;

    }

    private CmsImageFileManagerImpl()
    {

    }

    /**
     * root -productFiles -merchant-id PRODUCT-ID(key) -> CacheAttribute(value) - image 1 - image 2 - image 3
     */

    @Override
    public void addProductImage( ProductImage productImage,
    		ImageContentFile contentImage )
        throws ServiceException
    {

        if ( cacheManager.getTreeCache() == null )
        {
            throw new ServiceException( "CmsImageFileManagerInfinispan has a null cacheManager.getTreeCache()" );
        }

        try
        {

            // node
        	StringBuilder nodePath = new StringBuilder();
        	nodePath.append(productImage.getProduct().getMerchantStore().getCode()).append(Constants.SLASH).append(productImage.getProduct().getSku()).append(Constants.SLASH);

            
        	if(contentImage.getFileContentType().name().equals(FileContentType.PRODUCT.name())) {
        		nodePath.append(SMALL);
        	} else if(contentImage.getFileContentType().name().equals(FileContentType.PRODUCTLG.name())) {
        		nodePath.append(LARGE);
        	}
        	
        	Node<String, Object> productNode = this.getNode(nodePath.toString());

            
            InputStream isFile = contentImage.getFile();
            
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            IOUtils.copy( isFile, output );
            

            // object for a given product containing all images
            productNode.put(contentImage.getFileName(), output.toByteArray());
            


        }
        catch ( Exception e )
        {

            throw new ServiceException( e );

        }

    }

    @Override
    public OutputContentFile getProductImage( ProductImage productImage )
        throws ServiceException
    {

       return getProductImage(productImage.getProduct().getMerchantStore().getCode(),productImage.getProduct().getSku(),productImage.getProductImage());

    }


    public List<OutputContentFile> getImages( MerchantStore store, FileContentType imageContentType )
        throws ServiceException
    {

         return getImages(store.getCode(),imageContentType);

    }

    @Override
    public List<OutputContentFile> getImages( Product product )
        throws ServiceException
    {

        if ( cacheManager.getTreeCache() == null )
        {
            throw new ServiceException( "CmsImageFileManagerInfinispan has a null cacheManager.getTreeCache()" );
        }

        List<OutputContentFile> images = new ArrayList<OutputContentFile>();
        

        try
        {


        	FileNameMap fileNameMap = URLConnection.getFileNameMap();
        	StringBuilder nodePath = new StringBuilder();
        	nodePath.append(product.getMerchantStore().getCode());

            Node<String, Object> merchantNode = this.getNode(nodePath.toString());

            if ( merchantNode == null )
            {
                return null;
            }
            
            
            for(String key : merchantNode.getKeys()) {
            	
                byte[] imageBytes = (byte[])merchantNode.get( key );

                OutputContentFile contentImage = new OutputContentFile();

                InputStream input = new ByteArrayInputStream( imageBytes );
                ByteArrayOutputStream output = new ByteArrayOutputStream();
                IOUtils.copy( input, output );

                String contentType = fileNameMap.getContentTypeFor( key );

                contentImage.setFile( output );
                contentImage.setMimeType( contentType );
                contentImage.setFileName( key );

                images.add( contentImage );
            	
            	
            }
            
            
        }

        catch ( Exception e )
        {
            throw new ServiceException( e );
        }
        finally
        {

        }

        return images;
    }



	@SuppressWarnings("unchecked")
	@Override
    public void removeImages( final String merchantStoreCode )
        throws ServiceException
    {
        if ( cacheManager.getTreeCache() == null )
        {
            throw new ServiceException( "CmsImageFileManagerInfinispan has a null cacheManager.getTreeCache()" );
        }

        try
        {


			final StringBuilder merchantPath = new StringBuilder();
	        merchantPath.append( getRootName()).append(merchantStoreCode );
	        cacheManager.getTreeCache().getRoot().remove(merchantPath.toString());
			


        }
        catch ( Exception e )
        {
            throw new ServiceException( e );
        }
        finally
        {

        }

    }


    @Override
    public void removeProductImage( ProductImage productImage )
        throws ServiceException
    {

        if ( cacheManager.getTreeCache() == null )
        {
            throw new ServiceException( "CmsImageFileManagerInfinispan has a null cacheManager.getTreeCache()" );
        }

        try
        {

        	
        	StringBuilder nodePath = new StringBuilder();
        	nodePath.append(productImage.getProduct().getMerchantStore().getCode()).append(Constants.SLASH).append(productImage.getProduct().getSku());
        	
        	
        	Node<String, Object> productNode = this.getNode(nodePath.toString());
        	productNode.remove(productImage.getProductImage());
        	

            
            

        }
        catch ( Exception e )
        {
            throw new ServiceException( e );
        }
        finally
        {

        }

    }

    @Override
    public void removeProductImages( Product product )
        throws ServiceException
    {

        if ( cacheManager.getTreeCache() == null )
        {
            throw new ServiceException( "CmsImageFileManagerInfinispan has a null cacheManager.getTreeCache()" );
        }

        try
        {

        	
        	StringBuilder nodePath = new StringBuilder();
        	nodePath.append(product.getMerchantStore().getCode());
        	
        	
        	Node<String, Object> merchantNode = this.getNode(nodePath.toString());
        	
        	merchantNode.remove(product.getSku());
        	

            

        }
        catch ( Exception e )
        {
            throw new ServiceException( e );
        }
        finally
        {

        }

    }


    @Override
	public List<OutputContentFile> getImages(final String merchantStoreCode,
			FileContentType imageContentType) throws ServiceException {
        if ( cacheManager.getTreeCache() == null )
        {
            throw new ServiceException( "CmsImageFileManagerInfinispan has a null cacheManager.getTreeCache()" );
        }
        List<OutputContentFile> images = new ArrayList<OutputContentFile>();
        FileNameMap fileNameMap = URLConnection.getFileNameMap();

        try
        {

        	
        	StringBuilder nodePath = new StringBuilder();
        	nodePath.append(merchantStoreCode);
        	
        	
        	Node<String, Object> merchantNode = this.getNode(nodePath.toString());
        	
        	Set<Node<String,Object>> childs = merchantNode.getChildren();
        	
        	Iterator<Node<String,Object>> iterator = childs.iterator();
        	//TODO image sizes
        	while(iterator.hasNext()) {
        		
        		Node<String,Object> node = iterator.next();
        		
                for(String key : node.getKeys()) {
                	

                    byte[] imageBytes = (byte[])merchantNode.get( key );

                    OutputContentFile contentImage = new OutputContentFile();

                    InputStream input = new ByteArrayInputStream( imageBytes );
                    ByteArrayOutputStream output = new ByteArrayOutputStream();
                    IOUtils.copy( input, output );

                    String contentType = fileNameMap.getContentTypeFor( key );

                    contentImage.setFile( output );
                    contentImage.setMimeType( contentType );
                    contentImage.setFileName( key );

                    images.add( contentImage );
                	
                	
                }
        		
        	}
        	
          


        }
        catch ( Exception e )
        {
            throw new ServiceException( e );
        }
        finally
        {

        }

        return images;
	}

	@Override
	public OutputContentFile getProductImage(String merchantStoreCode,
			String productCode, String imageName) throws ServiceException {
		return getProductImage(merchantStoreCode, productCode, imageName, ProductImageSize.SMALL.name());
	}
	
	@Override
	public OutputContentFile getProductImage(String merchantStoreCode,
			String productCode, String imageName, ProductImageSize size)
			throws ServiceException {
		return getProductImage(merchantStoreCode, productCode, imageName, size.name());
	}
	
	private OutputContentFile getProductImage(String merchantStoreCode,
			String productCode, String imageName, String size) throws ServiceException {
		
        if ( cacheManager.getTreeCache() == null )
        {
            throw new ServiceException( "CmsImageFileManagerInfinispan has a null cacheManager.getTreeCache()" );
        }
        InputStream input = null;
        OutputContentFile contentImage = new OutputContentFile();
        try
        {
        	
        	FileNameMap fileNameMap = URLConnection.getFileNameMap();
        	
        	//SMALL by default
        	StringBuilder nodePath = new StringBuilder();
        	nodePath.append(merchantStoreCode).append(Constants.SLASH).append(productCode).append(Constants.SLASH).append(size);
        	
        	Node<String,Object> productNode = this.getNode(nodePath.toString());
        	

            byte[] imageBytes = (byte[])productNode.get( imageName );
            
            if(imageBytes==null) {
            	LOGGER.warn("Image " + imageName + " does not exist");
            	return null;//no post processing will occur
            }

            input = new ByteArrayInputStream( imageBytes );
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            IOUtils.copy( input, output );

            String contentType = fileNameMap.getContentTypeFor( imageName );

            contentImage.setFile( output );
            contentImage.setMimeType( contentType );
            contentImage.setFileName( imageName );



        }
        catch ( Exception e )
        {
            throw new ServiceException( e );
        }
        finally
        {
            if ( input != null )
            {
                try
                {
                    input.close();
                }
                catch ( Exception ignore )
                {
                }
            }
        }

        return contentImage;
		
	}

	
	@SuppressWarnings("unchecked")
	private Node<String, Object> getNode( final String node )
    {
        LOGGER.debug( "Fetching node for store {} from Infinispan", node );
        final StringBuilder merchantPath = new StringBuilder();
        merchantPath.append( getRootName() ).append(node);

        Fqn contentFilesFqn = Fqn.fromString(merchantPath.toString()); 

		Node<String,Object> nd = cacheManager.getTreeCache().getRoot().getChild(contentFilesFqn); 
        
        if(nd==null) {

            cacheManager.getTreeCache().getRoot().addChild(contentFilesFqn);
            nd = cacheManager.getTreeCache().getRoot().getChild(contentFilesFqn); 

        }
        
        return nd;

    }

	public CacheManager getCacheManager() {
		return cacheManager;
	}

	public void setCacheManager(CacheManager cacheManager) {
		this.cacheManager = cacheManager;
	}

	public void setRootName(String rootName) {
		this.rootName = rootName;
	}

	public String getRootName() {
		return rootName;
	}



}
