package fr.gael.dhus.server.http.webapp.stub.controller;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geotools.gml2.GMLConfiguration;
import org.geotools.xml.Configuration;
import org.geotools.xml.Parser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.context.jts.ValidationRule;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;

import fr.gael.dhus.database.object.MetadataIndex;
import fr.gael.dhus.database.object.Product;
import fr.gael.dhus.database.object.User;
import fr.gael.dhus.server.http.webapp.stub.controller.stub_share.MetadataIndexData;
import fr.gael.dhus.server.http.webapp.stub.controller.stub_share.ProductData;
import fr.gael.dhus.server.http.webapp.stub.controller.stub_share.exceptions.ProductCartServiceException;
import fr.gael.dhus.service.exception.UserNotExistingException;
import fr.gael.dhus.spring.context.ApplicationContextProvider;
import fr.gael.dhus.system.config.ConfigurationManager;
import fr.gael.dhus.util.MetalinkBuilder;
import fr.gael.dhus.util.MetalinkBuilder.MetalinkFileBuilder;

@RestController
public class StubCartController
{
   private static Log logger = LogFactory.getLog(StubUserController.class);

   @Autowired
   private ConfigurationManager configurationManager;

   @RequestMapping (value = "/users/{userid}/cart/{cartid}/addproduct",
      method = RequestMethod.POST)
   public void addProductToCart(Principal principal,
      @PathVariable (value = "userid") String userid,
      @PathVariable (value = "cartid") String cartid,
      @RequestParam (value = "productId", defaultValue = "") Long pId) 
         throws ProductCartServiceException
   {
      User user = (User) ((UsernamePasswordAuthenticationToken) principal).
         getPrincipal();
      fr.gael.dhus.service.ProductCartService productCartService =
         ApplicationContextProvider.getBean(
            fr.gael.dhus.service.ProductCartService.class);

      try
      {
         productCartService.addProductToCart(user.getUUID(), pId);
      }
      catch (Exception e)
      {
         e.printStackTrace();
         throw new ProductCartServiceException(e.getMessage());
      }
   }

   @RequestMapping (value = "/users/{userid}/cart/{cartid}/removeproduct",
      method = RequestMethod.POST)
   public void removeProductFromCart(Principal principal,
      @PathVariable (value = "userid") String userid,
      @PathVariable (value = "cartid") String cartid,
      @RequestParam (value = "productId", defaultValue = "") Long pId)
         throws ProductCartServiceException
   {
      User user = (User) ((UsernamePasswordAuthenticationToken) principal).
         getPrincipal();
      fr.gael.dhus.service.ProductCartService productCartService =
         ApplicationContextProvider.getBean(
            fr.gael.dhus.service.ProductCartService.class);
      try
      {
         productCartService.removeProductFromCart(user.getUUID(), pId);
      }
      catch (Exception e)
      {
         e.printStackTrace();
         throw new ProductCartServiceException(e.getMessage());
      }
   }

   @RequestMapping (value = "/users/{userid}/cart/{cartid}/getcartids",
      method = RequestMethod.GET)
   public List<Long> getProductsIdOfCart(Principal principal,
      @PathVariable (value = "userid") String userid,
      @PathVariable (value = "cartid") String cartid)
         throws ProductCartServiceException
   {

      User user = (User) ((UsernamePasswordAuthenticationToken) principal).
         getPrincipal();
      fr.gael.dhus.service.ProductCartService productCartService =
         ApplicationContextProvider.getBean(
            fr.gael.dhus.service.ProductCartService.class);
      try
      {
         return productCartService.getProductsIdOfCart(user.getUUID());
      }
      catch (Exception e)
      {
         e.printStackTrace();
         throw new ProductCartServiceException(e.getMessage());
      }
   }

   @RequestMapping (value = "/users/{userid}/carts/{cartid}",
      method = RequestMethod.GET)
   public List<ProductData> getProductsOfCart(Principal principal,
      @PathVariable (value = "userid") String userid,
      @PathVariable (value = "cartid") String cartid,
      @RequestParam (value = "offset", defaultValue = "") int start,
      @RequestParam (value = "count", defaultValue = "")  int count)
         throws ProductCartServiceException
   {
      User user = (User) ((UsernamePasswordAuthenticationToken) principal).
         getPrincipal();
      fr.gael.dhus.service.ProductCartService productCartService =
         ApplicationContextProvider.getBean(
            fr.gael.dhus.service.ProductCartService.class);
      fr.gael.dhus.service.ProductService productService =
         ApplicationContextProvider.getBean(
            fr.gael.dhus.service.ProductService.class);

      try
      {
         List<Product> products = productCartService.getProductsOfCart(
            user.getUUID(), start, count);
         
         ArrayList<ProductData> productDatas = new ArrayList<ProductData>();
         Configuration configuration = new GMLConfiguration();
      
         @SuppressWarnings ("unused")
         Parser parser = new Parser(configuration);

         if (products != null)
         {
            logger.info("products not null");
            for (Product product : products)
            {
               if (product != null)
               {
                  logger.info("product not null");
                  ProductData productData =
                     new ProductData(product.getId(), product.getUuid(),
                                     product.getIdentifier());

                  // Set the Footprint if any
                  productData.setFootprint(StubCartController.
                     convertGMLToDoubleLonLat(product.getFootPrint()));

                  ArrayList<String> summary = new ArrayList<String>();
                  ArrayList<MetadataIndexData> indexes =
                     new ArrayList<MetadataIndexData>();

                  for (MetadataIndex index :
                       productService.getIndexes(product.getId()))
                  {
                     MetadataIndexData category =
                        new MetadataIndexData(index.getCategory(), null);
                     int i = indexes.indexOf(category);
                     if (i < 0)
                     {
                        category.addChild(new MetadataIndexData(index.getName(),
                           index.getValue()));
                        indexes.add(category);
                     }
                     else
                     {
                        indexes.get(i).addChild(
                           new MetadataIndexData(index.getName(), 
                              index.getValue()));
                     }

                     if ("summary".equals(index.getCategory()))
                     {
                        summary.add(index.getName() + " : " + index.getValue());
                        Collections.sort(summary, null);
                     }

                     if ("Instrument".equalsIgnoreCase(index.getName()))
                     {
                        productData.setInstrument(index.getValue());
                     }

                     if ("Product type".equalsIgnoreCase(index.getName()))
                     {
                        productData.setProductType(index.getValue());
                     }

                  }
                  productData.setSummary(summary);
                  productData.setIndexes(indexes);

                  productData.setHasQuicklook(product.getQuicklookFlag());
                  productData.setHasThumbnail(product.getThumbnailFlag());

                  productDatas.add(productData);
               }
            }
         }
         return productDatas;
      }
      catch (Exception e)
      {
         e.printStackTrace();
         throw new ProductCartServiceException(e.getMessage());
      }
   }

   @RequestMapping (value = "/users/{userid}/cart/{cartid}/getcount",
      method = RequestMethod.GET)
   public int countProductsInCart(Principal principal,
      @PathVariable (value = "userid") String userid,
      @PathVariable (value = "cartid") String cartid)
         throws ProductCartServiceException
   {
      User user = (User)((UsernamePasswordAuthenticationToken) principal).
         getPrincipal();
      fr.gael.dhus.service.ProductCartService productCartService =
         ApplicationContextProvider.getBean(
            fr.gael.dhus.service.ProductCartService.class);

      try
      {
         return productCartService.countProductsInCart(user.getUUID());
      }
      catch (Exception e)
      {
         e.printStackTrace();
         throw new ProductCartServiceException(e.getMessage());
      }
   }

   @RequestMapping (value = "/users/{userid}/cart/{cartid}/clear",
      method = RequestMethod.POST)
   public void clearCart(Principal principal,
      @PathVariable (value = "userid") String userid,
      @PathVariable (value = "cartid") String cartid)
         throws ProductCartServiceException
   {
      User user = (User) ((UsernamePasswordAuthenticationToken) principal).
         getPrincipal();
      fr.gael.dhus.service.ProductCartService productCartService =
         ApplicationContextProvider.getBean(
            fr.gael.dhus.service.ProductCartService.class);

      try
      {
         productCartService.clearCart(user.getUUID());
      }
      catch (Exception e)
      {
         e.printStackTrace();
         throw new ProductCartServiceException(e.getMessage());
      }
   }

   @RequestMapping (value = "/users/{userid}/cart/{cartid}/download",
      method = RequestMethod.GET)
   public void cartToMetalink(Principal principal,
      @PathVariable (value = "userid") String userid,
      @PathVariable (value = "cartid") String cartid,
      HttpServletResponse res)
         throws UserNotExistingException, IOException, 
            ParserConfigurationException, TransformerException
   {
      User user = (User) ((UsernamePasswordAuthenticationToken) principal).
         getPrincipal();
      fr.gael.dhus.service.ProductCartService productCartService =
         ApplicationContextProvider.getBean(
            fr.gael.dhus.service.ProductCartService.class);

      if (productCartService.hasProducts(user.getUUID())) return;
      
      res.setContentType("application/metalink+xml");
      res.setHeader("Content-Disposition", "inline; filename=products" +
         MetalinkBuilder.FILE_EXTENSION);

      res.getWriter().println(makeMetalinkDocument(
         productCartService.getProductsOfCart(user.getUUID(), -1, -1)));
   }

   /**
    * Makes the metalink XML Document for given products.
    * 
    * @throws ParserConfigurationException
    * @throws TransformerException
    */
   private String makeMetalinkDocument(Iterable<Product> lp)
      throws ParserConfigurationException, TransformerException
   {
      MetalinkBuilder mb = new MetalinkBuilder();

      for (Product p : lp)
      {
         String product_entity =
            configurationManager.getServerConfiguration().getExternalUrl() +
               "odata/v1/Products('" + p.getUuid() + "')/$value";

         MetalinkFileBuilder fb = mb.addFile(
            new File(p.getDownload().getPath()).getName()).
               addUrl(product_entity, null, 0);

         if ( !p.getDownload().getChecksums().isEmpty())
         {
            Map<String, String> checksums = p.getDownload().getChecksums();
            for (String algo : checksums.keySet())
               fb.setHash(algo, checksums.get(algo));
         }
      }
      StringWriter sw = new StringWriter();

      Document doc = mb.build();
      Transformer transformer=TransformerFactory.newInstance().newTransformer();
      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
      transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount",
                                    "2");
      transformer.transform(new DOMSource(doc), new StreamResult(sw));
      return sw.toString();
   }

   @RequestMapping (value = "/users/{userid}/cart/{cartid}/testcart",
      method = RequestMethod.GET)
   public String signupValidate(
      @PathVariable (value = "userid") String userid,
      @PathVariable (value = "cartid") String cartid)
   {
      return "hello from cart stub test webservice";
   }

   public static Double[][][] convertGMLToDoubleLonLat(String gml)
   {
      if (gml == null || gml.trim().isEmpty()) return null;
      Configuration configuration = new GMLConfiguration();
      Parser parser = new Parser(configuration);

      Geometry footprint;
      try
      {
         footprint =
            (Geometry) parser.parse(new InputSource(new StringReader(gml)));
      }
      catch (Exception e)
      {
         logger.error("Cannot read GML coordinates: " +
            (gml == null ? gml : gml.trim()), e);
         return null;
      }

      JtsSpatialContext ctx = JtsSpatialContext.GEO;
      GeometryFactory geometryFactory = ctx.getGeometryFactory();

      List<Coordinate> sequence = new ArrayList<Coordinate>();
      for (Coordinate coord : footprint.getCoordinates())
      {
         ctx.verifyX(coord.y);
         ctx.verifyY(coord.x);
         sequence.add(new Coordinate(coord.y, coord.x));
      }

      LinearRing shell = geometryFactory.createLinearRing(sequence.toArray(
         new Coordinate[sequence.size()]));

      Polygon p = geometryFactory.createPolygon(shell, null);
      JtsSpatialContextFactory factory = new JtsSpatialContextFactory();
      ValidationRule validationRule = factory.validationRule;
      JtsGeometry jts;
      try
      {
         jts = ctx.makeShape(p, true, ctx.isAllowMultiOverlap());
         if (validationRule != ValidationRule.none) jts.validate();
      }
      catch (RuntimeException e)
      {
         // repair:
         if (validationRule == ValidationRule.repairConvexHull)
         {
            jts=ctx.makeShape(p.convexHull(), true, ctx.isAllowMultiOverlap());
         }
         else
         if (validationRule == ValidationRule.repairBuffer0)
         {
            jts=ctx.makeShape(p.buffer(0), true, ctx.isAllowMultiOverlap());
         }
         else
         {
            // TODO there are other smarter things we could do like repairing
            // inner holes and subtracting
            // from outer repaired shell; but we needn't try too hard.
            throw e;
         }
      }
      if (factory.autoIndex) jts.index();

      Double[][][] pts;
      if (jts.getGeom() instanceof MultiPolygon)
      {
         pts=new Double[((MultiPolygon)jts.getGeom()).getNumGeometries()][][];
         for (int j=0; j<((MultiPolygon)jts.getGeom()).getNumGeometries(); j++)
         {
            pts[j]=new Double[((MultiPolygon) jts.getGeom()).getGeometryN(j).
               getNumPoints()][2];
            int i = 0;
            for (Coordinate coord:((MultiPolygon) jts.getGeom()).
               getGeometryN(j).getCoordinates())
            {
               pts[j][i] = new Double[2];
               pts[j][i][0] = coord.x;
               pts[j][i][1] = coord.y;
               i++;
            }
         }
      }
      else
      {
         pts = new Double[1][jts.getGeom().getNumPoints()][2];
         int i = 0;
         for (Coordinate coord : jts.getGeom().getCoordinates())
         {
            pts[0][i] = new Double[2];
            pts[0][i][0] = coord.x;
            pts[0][i][1] = coord.y;
            i++;
         }
      }
      return pts;
   }
}