<?php
/**
 * Form Helper
 *
 * @author Elze Kool <info@visualmedia.nl>
 */

namespace VisualM\FormBundle\Helper;

use DateTime;
use Swift_Mailer;
use Swift_Message;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormFactory;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Validator\Constraints as Assert;
use VisualM\FormBundle\Doctrine\FieldType\FormFieldType;
use VisualM\FormBundle\Entity\Form as FormEntity;
use VisualM\FormBundle\Entity\FormEntry as FormEntryEntity;
use VisualM\FormBundle\Entity\FormEntryRepository;
use VisualM\FormBundle\Entity\FormField as FormFieldEntity;


/**
 * Form Helper
 *
 * @author Elze Kool <info@visualmedia.nl>
 */
class FormHelper
{
    /**
     * Renderered Forms
     *
     * @var array<Form, FormEntity>
     */
    private $renderedForms = [];

    /**
     * RequestStack
     * @var RequestStack
     */
    private $requestStack;

    /**
     * Form Factory
     * @var FormFactory
     */
    private $formFactory;

    /**
     * Templating Engine
     * @var EngineInterface
     */
    private $templating;

    /**
     * Mailer
     * @var Swift_Mailer
     */
    private $mailer;

    /**
     * Form Entry Manager
     * @var FormEntryRepository
     */
    private $formEntryManager;

    /**
     * Sender e-mailadres
     * @var string
     */
    private $sender;

    /**
     * Constructor
     *
     * @param RequestStack        $requestStack     Request Stack
     * @param FormFactory         $formFactory      Form Factory
     * @param EngineInterface     $templating       Templating Engine
     * @param Swift_Mailer        $mailer           Mailer
     * @param FormEntryRepository $formEntryManager Form Entry Manager
     * @param string              $sender           Sender
     */
    public function __construct(
        RequestStack $requestStack,
        FormFactory $formFactory,
        EngineInterface $templating,
        Swift_Mailer $mailer,
        FormEntryRepository $formEntryManager,
        $sender
    ) {
        $this->requestStack = $requestStack;
        $this->formFactory = $formFactory;
        $this->templating = $templating;
        $this->mailer = $mailer;
        $this->formEntryManager = $formEntryManager;
        $this->sender = $sender;
    }

    /**
     * Render Form
     * 
     * @param FormEntity $formEntity FormEntity
     *
     * @return string HTML Form HTML
     */
    public function renderForm(FormEntity $formEntity)
    {
        // Create form
        $form_builder = $this->formFactory->createNamedBuilder('vm_form_' . $formEntity->getReferentie(), 'form');

        // Add Fields
        foreach($formEntity->getFields() as $form_field) {
            $this->addField($form_builder, $form_field);
        }

        // Create form, update it from request and mark it as rendered
        $form = $form_builder->getForm();
        $form->handleRequest($this->requestStack->getMasterRequest());
        $this->renderedForms[] = [ $form, $formEntity ];

        $html = $this->templating->render(sprintf('VisualMFormEntity:%d:formHtml', $formEntity->getId()), [
            'form' => $form->createView()
        ]);

        return $html;
    }

    /**
     * Handle the submit of the form
     *
     * @return null|Response Response to send or null if no response
     */
    public function handleSubmit()
    {
        foreach($this->renderedForms as $form_and_entity) {

            list($form, $form_entity) = $form_and_entity;
            /* @var $form Form */
            /* @var $form_entity FormEntity */

            if ($form->isValid()) {

                // Extract data from fields
                $data = [];
                foreach($form_entity->getFields() as $field) {
                    if ($form->has($field->getReferentie())) {
                        $value = $form->get($field->getReferentie())->getData();
                        if (is_array($value)) {
                            $data[$field->getReferentie()] = join(', ', $value);
                        } else if($value instanceof DateTime) {
                            $data[$field->getReferentie()] = $value->format('d-m-Y');
                        } else {
                            $data[$field->getReferentie()] = $value;
                        }
                    }
                }

                $this->saveEntry($data, $form_entity);
                $this->sendEmails($data, $form_entity);

                return new RedirectResponse($form_entity->getRedirectPath());
            }
            
        }

        return null;
    }

    /**
     * Save Entry to database
     *
     * @param array      $data       Data
     * @param FormEntity $formEntity Form Entity
     */
    private function saveEntry(array $data, FormEntity $formEntity)
    {
        $request = $this->requestStack->getMasterRequest();

        // Create message HTML
        $email_html = $this->templating->render(sprintf('VisualMFormEntity:%d:emailHtml', $formEntity->getId()), [
            'form' => $formEntity,
            'data' => $data
        ]);

        $form_entry = new FormEntryEntity();
        $form_entry->setForm($formEntity);
        $form_entry->setRemoteAddr($request->getClientIp());
        $form_entry->setBrowser($request->headers->get('User-Agent', ''));

        $form_entry->setLandingUrl($request->getSession()->get('userinfo.landing_page', null));
        $form_entry->setLandingReferer($request->getSession()->get('userinfo.landing_referer', null));
        $form_entry->setTimeOnSiteInSec($request->getSession()->has('userinfo.landing_time') ? (time() - $request->getSession()->get('userinfo.landing_time')) : null);
        $form_entry->setLandingUrl($request->getSession()->get('userinfo.landing_page'));
        $form_entry->setUrl($request->getSchemeAndHttpHost() . $request->getRequestUri());
        $form_entry->setRawdata($data);
        $form_entry->setEmailHtml($email_html);

        $this->formEntryManager->saveItem($form_entry);
    }

    /**
     * Send E-mails for an succesfully validated form
     *
     * @param array      $data       Data
     * @param FormEntity $formEntity Form Entity
     */
    private function sendEmails(array $data, FormEntity $formEntity)
    {
        // Create message HTML
        $email_html = $this->templating->render(sprintf('VisualMFormEntity:%d:emailHtml', $formEntity->getId()), [
            'form' => $formEntity,
            'data' => $data
        ]);

        // Create Message
        $message = $this->mailer->createMessage();
        /* @var $message Swift_Message */

        $message
            ->setSender($this->sender)
            ->setSubject($this->replaceControlValues($formEntity->getEmailOnderwerp(), $data))
            ->addTo($this->replaceControlValues($formEntity->getOntvangerEmail(), $data), $this->replaceControlValues($formEntity->getOntvangerNaam(), $data))
            ->setFrom($this->replaceControlValues($formEntity->getAfzenderEmail(), $data), $this->replaceControlValues($formEntity->getAfzenderNaam(), $data))
            ->setBody($email_html, 'text/html');

        if ($formEntity->getBccEmail() != '' AND $formEntity->getBccEmail() != null) {
            foreach(explode(';', $formEntity->getBccEmail()) as $bcc) {
                $message->addBcc(trim($bcc));
            }
        }

        $this->mailer->send($message);

        // Check if we have to send a kopie
        if ($formEntity->getKopieNaarAfzender()) {

            // Create message HTML
            $kopie_email_html = $this->templating->render(sprintf('VisualMFormEntity:%d:kopieEmailHtml', $formEntity->getId()), [
                'form' => $formEntity,
                'data' => $data
            ]);

            // Create Message
            $kopie_message = $this->mailer->createMessage();
            /* @var $kopie_message Swift_Message */

            $kopie_message
                ->setSender($this->sender)
                ->setSubject($this->replaceControlValues(($formEntity->getKopieEmailOnderwerp() != '' ? $formEntity->getKopieEmailOnderwerp() : $formEntity->getEmailOnderwerp()), $data))
                ->addTo($this->replaceControlValues($formEntity->getAfzenderEmail(), $data), $this->replaceControlValues($formEntity->getAfzenderNaam(), $data))
                ->setFrom($this->replaceControlValues($formEntity->getOntvangerEmail(), $data), $this->replaceControlValues($formEntity->getOntvangerNaam(), $data))
                ->setBody($kopie_email_html, 'text/html');

            $this->mailer->send($kopie_message);
            
        }
        
    }

    /**
     * Replace {Control:name} values in string
     *
     * @param string        $string Input string
     * @param array<string> $data   Data from Form
     *
     * @return string String with replacements
     */
    private function replaceControlValues($string, $data)
    {
        $replacements = [];
        foreach($data as $key => $value) {
            $replacements[sprintf('{control:%s}', $key)] = $value;
        }
        return str_replace(array_keys($replacements), array_values($replacements), $string);
    }

    
    /**
     * Add Field to FormBuilder
     *
     * @param FormBuilderInterface $formBuilder FormBuilder
     * @param FormFieldEntity      $formField   Form Field
     */
    private function addField(FormBuilderInterface $formBuilder, FormFieldEntity $formField)
    {
        $constraints = [];
        
        switch($formField->getType()) {
            case FormFieldType::HIDDEN:
                if ($formField->getRegex() != '' AND $formField->getRegex() != null) { $constraints[] = new Assert\Regex([ 'pattern' => $formField->getRegex() ]); }
                if ($formField->getVerplicht()) { $constraints[] = new Assert\NotBlank(); }

                $formBuilder->add($formField->getReferentie(), 'hidden', [  'mapped' => false, 'constraints' => $constraints ]);
                break;

            case FormFieldType::TEXT:
                if ($formField->getRegex() != '' AND $formField->getRegex() != null) { $constraints[] = new Assert\Regex([ 'pattern' => $formField->getRegex() ]); }
                if ($formField->getVerplicht()) { $constraints[] = new Assert\NotBlank(); }

                $formBuilder->add($formField->getReferentie(), 'text', [  'mapped' => false, 'required' => $formField->getVerplicht(), 'constraints' => $constraints ]);
                break;

            case FormFieldType::NUMBER:
                if ($formField->getRegex() != '' AND $formField->getRegex() != null) { $constraints[] = new Assert\Regex([ 'pattern' => $formField->getRegex() ]); }
                if ($formField->getVerplicht()) { $constraints[] = new Assert\NotBlank(); }
                $constraints[] = new Assert\Type([ 'type' => 'numeric ']);

                $formBuilder->add($formField->getReferentie(), 'number', [  'mapped' => false, 'required' => $formField->getVerplicht(), 'constraints' => $constraints ]);
                break;
            case FormFieldType::PHONE:
                if ($formField->getRegex() != '' AND $formField->getRegex() != null) { $constraints[] = new Assert\Regex([ 'pattern' => $formField->getRegex() ]); }
                if ($formField->getVerplicht()) { $constraints[] = new Assert\NotBlank(); }

                $formBuilder->add($formField->getReferentie(), 'text', [  'mapped' => false, 'required' => $formField->getVerplicht(), 'constraints' => $constraints, 'attr' => [ 'type' => 'tel' ] ]);
                break;

            case FormFieldType::EMAIL:
                if ($formField->getRegex() != '' AND $formField->getRegex() != null) { $constraints[] = new Assert\Regex([ 'pattern' => $formField->getRegex() ]); }
                if ($formField->getVerplicht()) { $constraints[] = new Assert\NotBlank(); }

                $constraints[] = new Assert\Email();
                $formBuilder->add($formField->getReferentie(), 'email', [  'mapped' => false, 'required' => $formField->getVerplicht(), 'constraints' => $constraints ]);
                break;

            case FormFieldType::DATE:
                if ($formField->getVerplicht()) { $constraints[] = new Assert\NotBlank(); }

                $formBuilder->add($formField->getReferentie(), 'date', [  'widget' => 'single_text',  'mapped' => false, 'required' => $formField->getVerplicht(), 'constraints' => $constraints ]);
                break;

            case FormFieldType::TEXTAREA:
                if ($formField->getRegex() != '' AND $formField->getRegex() != null) { $constraints[] = new Assert\Regex([ 'pattern' => $formField->getRegex() ]); }
                if ($formField->getVerplicht()) { $constraints[] = new Assert\NotBlank(); }

                $formBuilder->add($formField->getReferentie(), 'textarea', [  'mapped' => false, 'required' => $formField->getVerplicht(), 'constraints' => $constraints, 'attr' => [ 'rows' => $formField->getRijen() ] ]);
                break;

            case FormFieldType::SELECT:
                $choices = $this->createChoices($formField->getWaarden());
                if ($formField->getRegex() != '' AND $formField->getRegex() != null) { $constraints[] = new Assert\Regex([ 'pattern' => $formField->getRegex() ]); }
                if ($formField->getVerplicht()) { $constraints[] = new Assert\NotBlank(); }

                $formBuilder->add($formField->getReferentie(), 'choice', [  'mapped' => false, 'required' => $formField->getVerplicht(), 'constraints' => $constraints, 'choices' => $choices ]);
                break;

            case FormFieldType::SELECTLIST:
                $choices = $this->createChoices($formField->getWaarden());
                if ($formField->getVerplicht()) { $constraints[] = new Assert\Count([ 'min' => 1, 'max' => count($choices) ]); }
                
                $formBuilder->add($formField->getReferentie(), 'choice', [  'mapped' => false, 'required' => $formField->getVerplicht(), 'constraints' => $constraints, 'choices' => $choices, 'multiple' => true, 'expanded' => false, 'attr' => [ 'rows' => $formField->getRijen() ]  ]);
                break;

            case FormFieldType::CHECKBOXLIST:
                $choices = $this->createChoices($formField->getWaarden());
                if ($formField->getVerplicht()) { $constraints[] = new Assert\Count([ 'min' => 1, 'max' => count($choices) ]); }
                
                $formBuilder->add($formField->getReferentie(), 'choice', [  'mapped' => false, 'required' => $formField->getVerplicht(), 'constraints' => $constraints, 'choices' => $choices, 'multiple' => true, 'expanded' => true  ]);
                break;

            case FormFieldType::OPTIONLIST:
                $choices = $this->createChoices($formField->getWaarden());
                if ($formField->getRegex() != '' AND $formField->getRegex() != null) { $constraints[] = new Assert\Regex([ 'pattern' => $formField->getRegex() ]); }
                if ($formField->getVerplicht()) { $constraints[] = new Assert\NotBlank(); }
                
                $formBuilder->add($formField->getReferentie(), 'choice', [  'mapped' => false, 'required' => $formField->getVerplicht(), 'constraints' => $constraints, 'choices' => $choices, 'multiple' => false, 'expanded' => true  ]);
                break;
        }
    }

    /**
     * Create Choices from Waarden
     *
     * @param string $waarden Waarden
     *
     * @return array<string> Choices
     */
    private function createChoices($waarden)
    {
        $choices = [];
        foreach(explode("\n", $waarden) as $waarde) {
            $choices[trim($waarde)] = trim($waarde);
        }
        return $choices;
    }

    
}