/**
 * @author Andrea De Angelis
 */

/*
 * Copyright 2014 - LAit SpA
 *        Realizzato da Icona Management srl su commessa LAit SpA
* Copyright 2015- LAit SpA
*                                                  Aggiornato da LAit SpA
 *
 * Concesso in licenza a norma dell'EUPL, versione 1.1 o
 * successive dell'EUPL (la "Licenza")- non appena saranno
 * approvate dalla Commissione europea;
 * Non è possibile utilizzare l'opera salvo nel rispetto della Licenza.
 * E' possibile ottenere una copia della Licenza al seguente indirizzo:
 *
 * http://ec.europa.eu/idabc/eupl
 *
 * Salvo diversamente indicato dalla legge applicabile o
 * concordato per iscritto, il software distribuito secondo
 * i termini della Licenza è distribuito "TAL QUALE",
 * SENZA GARANZIE O CONDIZIONI DI ALCUN TIPO,
 * esplicite o implicite.
 * Si veda la Licenza per la lingua specifica che disciplina
 * le autorizzazioni e le limitazioni secondo i termini della
 * Licenza.
 */
package it.laitspa.cpf.faces.sogei;

import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import it.gov.fatturapa.sdi.fatturapa.v1.*;
import it.gov.fatturapa.sdi.messaggi.v1.EsitoCommittenteType;
import it.gov.fatturapa.sdi.messaggi.v1.NotificaEsitoCommittenteType;
import it.gov.fatturapa.sdi.messaggi.v1.RiferimentoFatturaType;
import it.laitspa.cpf.faces.UploadBeanModel;
import it.laitspa.cpf.faces.streaming.StreamingBean;
import it.laitspa.cpf.faces.utils.FacesUtils;
import it.laitspa.cpf.faces.utils.SogeiArchiveParser;
import it.laitspa.cpf.faces.utils.versionefattura.VersioneFatturaPaHelper;
import it.laitspa.cpf.util.log.LogUtils;
import it.laitspa.cpf.util.misc.DateUtils;
import it.laitspa.cpf.util.misc.FormUtils;
import it.laitspa.xsd.resolver.XmlMarshaller;

import javax.faces.event.ActionEvent;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.text.MessageFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author andrea
 * 
 */
public class UBean
  extends UploadBeanModel
{

  private FatturaElettronicaType lotto;
  private String                 errori;

  private List<AllegatiType>     allegati;
  private String                 motivoRifiuto;
  private String                 identificativoSdi;
  private String                 identificativoFattura;

  private String                 extension        = null;
  private boolean                showPanel        = false;

  /**
   * 
   */
  private static final long      serialVersionUID = 1L;

  public boolean isShowPanel()
  {
    return showPanel;
  }

  public void setShowPanel(boolean showPanel)
  {
    this.showPanel = showPanel;
  }

  public String getIdentificativoSdi()
  {
    return identificativoSdi;
  }

  public void setIdentificativoSdi(String identificativoSdi)
  {
    this.identificativoSdi = identificativoSdi;
  }

  public String getMotivoRifiuto()
  {
    return motivoRifiuto;
  }

  public void setMotivoRifiuto(String motivoRifiuto)
  {
    this.motivoRifiuto = motivoRifiuto;
  }

  public List<AllegatiType> getAllegati()
  {
    return allegati;
  }

  public void setAllegati(List<AllegatiType> allegati)
  {
    this.allegati = allegati;
  }

  public FatturaElettronicaType getLotto()
  {
    return lotto;
  }

  public void setLotto(FatturaElettronicaType lotto)
  {
    this.lotto = lotto;
  }

  public String getErrori()
  {
    return errori;
  }

  public void setErrori(String errori)
  {
    this.errori = errori;
  }

  public String doDetail()
  {
    return "detail";
  }

  public void doPrepareAllegati(ActionEvent e)
  {
    String id = (String)FacesUtils.getRequestParameter("id");
    if(id == null)
      return;

    for(FatturaElettronicaBodyType bd : lotto.getFatturaElettronicaBody())
    {
      if(bd.getId().equals(id))
      {
        allegati = bd.getAllegati();
        return;
      }
    }

    allegati = null;
  }

  public void doDownloadAllegato(ActionEvent e)
  {
    String id = (String)FacesUtils.getRequestParameter("id");
    if(id == null)
      return;

    for(AllegatiType bd : allegati)
    {
      if(bd.getId().equals(id))
      {
        StreamingBean s = FacesUtils.getFacesBean(StreamingBean.class);
        s.setBackUrl(FacesUtils.getCurrentView());
        s.setContentType("application/octet-stream");
        s.setData(bd.getAttachment());
        s.setFileName(bd.getNomeAttachment());
        s.setInline(false);

        s.doCallStreamingServlet();
        break;
      }
    }
  }

  public String doProcessFile()
  {
    if (file == null || file.getFileSize() == 0)
    {
      FacesUtils.addContextErrorMessage("E' obbligatorio specificare un file.");
      return "validation_error";
    }
    byte[] data = FacesUtils.getDataFromFile(getFile());
    String fileName = FacesUtils.getNormalizedFileName(file.getFileName());
    if (fileName != null)
      fileName = fileName.toLowerCase();
    
    int idx = fileName.lastIndexOf(".");
    if(idx > 0)
      extension = fileName.substring(idx + 1);
    
    // Try to parse file.
    //
    try
    {
      SogeiArchiveParser ps = new SogeiArchiveParser(extension, data);
      List<FatturaElettronicaType> lotti = ps.parse();

      if(lotti == null || lotti.isEmpty())
      {
        FacesUtils.addContextErrorMessage("Il file caricato non rappresenta un lotto valido.");
        return "validation_error";
      }
      else if(lotti.size() > 1)
      {
        FacesUtils.addContextErrorMessage("Il file caricato contiene più lotti.");
        return "multiple_lotti";
      }

      lotto = lotti.get(0);
      
      data = getXmlFromLotto();

      // Transform to manage preview.
      //
      //
      InputStream is = new ByteArrayInputStream(data);
      StreamSource sis = new StreamSource(is);

      // Create Transformer
      //
      InputStream xsl = VersioneFatturaPaHelper.getFoglioDiStile(data);
      TransformerFactory tf = TransformerFactory.newInstance();
      StreamSource xslt = new StreamSource(xsl);

      Transformer transformer = tf.newTransformer(xslt);

      ByteArrayOutputStream bos = new ByteArrayOutputStream();

      // Result
      //
      StreamResult result = new StreamResult(bos);

      // Transform
      //
      transformer.transform(sis, result);
      bos.close();
      xsl.close();
      is.close();

      data = bos.toByteArray();

      StreamingBean s = FacesUtils.getFacesBean(StreamingBean.class);
      s.setBackUrl(FacesUtils.getCurrentView());
      s.setContentType("text/html");
      s.setData(data);
      s.setFileName("export.html");
      s.setInline(true);
    }
    catch(Exception ex)
    {
      LogUtils.error(getClass(), "Error during xml processing.", ex);
      return "error";
    }

    return "validation_ok";
  }

  private byte[] getXmlFromLotto()
    throws JAXBException, IOException
  {
    byte[] data = null;
    if (extension == null || !extension.equalsIgnoreCase("xml"))
    {
      // Unmarshalling 
      //
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      XmlMarshaller<FatturaElettronicaType> fmr = new XmlMarshaller<FatturaElettronicaType>();
      ObjectFactory obj = new ObjectFactory();
      JAXBElement<FatturaElettronicaType> jf = obj.createFatturaElettronica(lotto);
      fmr.writeDocument(jf, bos);
      bos.close();
      data = bos.toByteArray();
    }
    else
      data = FacesUtils.getDataFromFile(getFile());
    return data;
  }

  public void doDownloadPDF(ActionEvent e)
  {
    try
    {
      byte[] xml = getXmlFromLotto();
      byte[] converted = null;
      
      // Input stream from data
      //
      InputStream is = new ByteArrayInputStream(xml);
      StreamSource sis = new StreamSource(is);

      // Create Transformer
      //
      TransformerFactory tf = TransformerFactory.newInstance();

      StreamSource xslt = new StreamSource(VersioneFatturaPaHelper.getFoglioDiStilePDF(xml));

      Transformer transformer = tf.newTransformer(xslt);
      // transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");

      ByteArrayOutputStream bos = new ByteArrayOutputStream();

      // Result
      //
      StreamResult result = new StreamResult(bos);

      // Transform
      //
      transformer.transform(sis, result);
      bos.close();
      is.close();

      // HTML
      xml = bos.toByteArray();

      bos = new ByteArrayOutputStream();

      // step 1
      Document document = new Document();
      // step 2
      PdfWriter writer = PdfWriter.getInstance(document, bos);
      // step 3
      document.open();

      // step 4
      XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(xml));
      // //step 5
      document.close();

      bos.flush();
      bos.close();
      converted = bos.toByteArray();

      StreamingBean s = FacesUtils.getFacesBean(StreamingBean.class);
      s.setBackUrl(FacesUtils.getCurrentView());
      s.setContentType("application/pdf");
      s.setData(converted);
      s.setFileName("export.pdf");
      s.setInline(false);

      s.doCallStreamingServlet();
    }
    catch(Exception ex)
    {
      LogUtils.error(this.getClass(), "Error caught while insert xml instruction.", ex);
    }
  }

  public void doPrepareForSingleNotification(ActionEvent e)
  {
    identificativoFattura = (String)FacesUtils.getRequestParameter("id");
  }

  public void doPrepareForGlobalNotification(ActionEvent e)
  {
    identificativoFattura = null;
  }

  public void doDownloadNotificaPositiva(ActionEvent e)
  {
    // Identificativo SDI must be a number and not a CF or PIVA.
    //
    Pattern p = Pattern.compile("[a-zA-Z]");
    Matcher m = p.matcher(identificativoSdi);
    
    if (identificativoSdi == null || 
        FormUtils.isCodiceFiscale(identificativoSdi)
        || FormUtils.isCodicePartitaIVA(identificativoSdi)
        || m.find())
    {
      FacesUtils
      .addContextErrorMessage("Il codice identificativo SDI deve essere numerico e non può coincidere con un codice fiscale/partita iva.");
      return;
    }
    
    FatturaElettronicaBodyType body = null;
    if(identificativoFattura != null)
    {
      for(FatturaElettronicaBodyType bd : lotto.getFatturaElettronicaBody())
      {
        if(bd.getId().equals(identificativoFattura))
        {
          body = bd;
          break;
        }
      }

      if(body == null)
        return;
    }

    // Building request parameters.
    //
    String nomeFileInvio = FacesUtils.getNormalizedFileName(file.getFileName());
    int point_index = nomeFileInvio.indexOf(".");
    if(point_index > 0)
      nomeFileInvio = nomeFileInvio.substring(0, point_index);

    String label = "EC";

    String notifica_filename =
        MessageFormat.format("{0}_{1}_{2}.xml", new Object[] {
            nomeFileInvio, label, formatToAlphaNumericRandom(3) });

    byte[] data = null;
    try
    {
      data = buildNotifica(true, null, body);
    }
    catch(Exception e1)
    {
      e1.printStackTrace();
    }

    if(data == null)
    {
      FacesUtils
          .addContextErrorMessage("Errore inatteso durante la generazione del file di notifica.");
      return;
    }

    StreamingBean s = FacesUtils.getFacesBean(StreamingBean.class);
    s.setBackUrl(FacesUtils.getCurrentView());
    s.setContentType("text/xml");
    s.setData(data);
    s.setFileName(notifica_filename);
    s.setInline(false);

    s.doCallStreamingServlet();
  }

  public void doDownloadNotificaNegativa(ActionEvent e)
  {
    
    // Identificativo SDI must be a number and not a CF or PIVA.
    //
    Pattern p = Pattern.compile("[a-zA-Z]");
    Matcher m = p.matcher(identificativoSdi);
    
    if (identificativoSdi == null || 
        FormUtils.isCodiceFiscale(identificativoSdi)
        || FormUtils.isCodicePartitaIVA(identificativoSdi)
        || m.find())
    {
      FacesUtils
      .addContextErrorMessage("Il codice identificativo SDI deve essere numerico e non può coincidere con un codice fiscale/partita iva.");
      return;
    }
    
    
    FatturaElettronicaBodyType body = null;

    if(identificativoFattura != null)
    {
      for(FatturaElettronicaBodyType bd : lotto.getFatturaElettronicaBody())
      {
        if(bd.getId().equals(identificativoFattura))
        {
          body = bd;
          break;
        }
      }

      if(body == null)
        return;
    }

    // Building request parameters.
    //
    String nomeFileInvio = FacesUtils.getNormalizedFileName(file.getFileName());
    int point_index = nomeFileInvio.indexOf(".");
    if(point_index > 0)
      nomeFileInvio = nomeFileInvio.substring(0, point_index);

    String descEsito = FormUtils.makeQueryParameter(motivoRifiuto);

    String label = "EC";

    String notifica_filename =
        MessageFormat.format("{0}_{1}_{2}.xml", new Object[] {
            nomeFileInvio, label, formatToAlphaNumericRandom(3) });

    byte[] data = null;
    try
    {
      data = buildNotifica(false, descEsito, body);
    }
    catch(Exception e1)
    {
      e1.printStackTrace();
    }

    if(data == null)
    {
      FacesUtils
          .addContextErrorMessage("Errore inatteso durante la generazione del file di notifica.");
      return;
    }

    StreamingBean s = FacesUtils.getFacesBean(StreamingBean.class);
    s.setBackUrl(FacesUtils.getCurrentView());
    s.setContentType("text/xml");
    s.setData(data);
    s.setFileName(notifica_filename);
    s.setInline(false);

    s.doCallStreamingServlet();
  }

  private byte[] buildNotifica(boolean esito, String descrizione, FatturaElettronicaBodyType body)
    throws Exception
  {

    String descEsito = null;
    EsitoCommittenteType esito_type = null;
    if(esito)
      esito_type = EsitoCommittenteType.EC_01;
    else
    {
      esito_type = EsitoCommittenteType.EC_02;
      descEsito = descrizione;
    }

    BigInteger iden = new BigInteger(identificativoSdi);

    // Create Notifica to be unmarshalled into XML!
    //
    it.gov.fatturapa.sdi.messaggi.v1.ObjectFactory obf =
        new it.gov.fatturapa.sdi.messaggi.v1.ObjectFactory();
    NotificaEsitoCommittenteType xml = obf.createNotificaEsitoCommittenteType();

    xml.setIdentificativoSdI(iden);
    xml.setEsito(esito_type);
    xml.setDescrizione(descEsito);
    xml.setVersione("1.0");

    if(body != null)
    {
      DatiGeneraliDocumentoType dg = body.getDatiGenerali().getDatiGeneraliDocumento();
      Calendar gc = new GregorianCalendar();
      gc.setTime(DateUtils.toDate(dg.getData()));
      int anno = gc.get(Calendar.YEAR);

      RiferimentoFatturaType riferimento = obf.createRiferimentoFatturaType();
      riferimento.setNumeroFattura(dg.getNumero());
      riferimento.setAnnoFattura(BigInteger.valueOf(anno));
      xml.setRiferimentoFattura(riferimento);
    }

    XmlMarshaller<NotificaEsitoCommittenteType> mr =
        new XmlMarshaller<NotificaEsitoCommittenteType>();

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    JAXBElement<NotificaEsitoCommittenteType> doc = obf.createNotificaEsitoCommittente(xml);
    mr.writeDocument(doc, bos);
    bos.close();
    byte[] data_request = bos.toByteArray();
    return data_request;
  }

  private String formatToAlphaNumericRandom(int length)
  {
    int min = 1;
    int max = 46656;
    int decimalNumber = min + (int)(Math.random() * ((max - min) + 1));
    LogUtils.info(getClass(), "Current number: " + decimalNumber);

    // To convert number to alphanumeric code.
    // We need to convert base10(decimal) to base36
    //
    String strBaseDigits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    String strTempVal = "";
    int mod = 0;
    while(decimalNumber != 0)
    {
      mod = (int)(decimalNumber % 36);
      strTempVal = strBaseDigits.substring(mod, mod + 1) + strTempVal;
      decimalNumber = decimalNumber / 36;
    }
    LogUtils.info(getClass(), "alphanumeric code generated from number : " + strTempVal);

    return String.format("%3s", strTempVal).replace(' ', '0');
  }

}
