<?php

namespace DynamicOOOS\PayPalHttp\Serializer;

use finfo;
use DynamicOOOS\PayPalHttp\HttpRequest;
use DynamicOOOS\PayPalHttp\Serializer;
use DynamicOOOS\PayPalHttp\Encoder;
use DynamicOOOS\PayPalHttp\Serializer\FormPart;
/**
 * Class Multipart
 * @package PayPalHttp\Serializer
 *
 * Serializer for multipart.
 */
class Multipart implements Serializer
{
    const LINEFEED = "\r\n";
    public function contentType()
    {
        return "/^multipart\\/.*\$/";
    }
    public function encode(HttpRequest $request)
    {
        if (!\is_array($request->body) || !$this->isAssociative($request->body)) {
            throw new \Exception("HttpRequest body must be an associative array when Content-Type is: " . $request->headers["content-type"]);
        }
        $boundary = "---------------------" . \md5(\mt_rand() . \microtime());
        $contentTypeHeader = $request->headers["content-type"];
        $request->headers["content-type"] = "{$contentTypeHeader}; boundary={$boundary}";
        $value_params = [];
        $file_params = [];
        $disallow = ["\x00", "\"", "\r", "\n"];
        $body = [];
        foreach ($request->body as $k => $v) {
            $k = \str_replace($disallow, "_", $k);
            if (\is_resource($v)) {
                $file_params[] = $this->prepareFilePart($k, $v, $boundary);
            } else {
                if ($v instanceof FormPart) {
                    $value_params[] = $this->prepareFormPart($k, $v, $boundary);
                } else {
                    $value_params[] = $this->prepareFormField($k, $v, $boundary);
                }
            }
        }
        $body = \array_merge($value_params, $file_params);
        // add boundary for each parameters
        \array_walk($body, function (&$part) use($boundary) {
            $part = "--{$boundary}" . self::LINEFEED . "{$part}";
        });
        // add final boundary
        $body[] = "--{$boundary}--";
        $body[] = "";
        return \implode(self::LINEFEED, $body);
    }
    public function decode($data)
    {
        throw new \Exception("Multipart does not support deserialization");
    }
    private function isAssociative(array $array)
    {
        return \array_values($array) !== $array;
    }
    private function prepareFormField($partName, $value, $boundary)
    {
        return \implode(self::LINEFEED, ["Content-Disposition: form-data; name=\"{$partName}\"", "", \filter_var($value)]);
    }
    private function prepareFilePart($partName, $file, $boundary)
    {
        $fileInfo = new finfo(\FILEINFO_MIME_TYPE);
        $filePath = \stream_get_meta_data($file)['uri'];
        $data = \file_get_contents($filePath);
        $mimeType = $fileInfo->buffer($data);
        $splitFilePath = \explode(\DIRECTORY_SEPARATOR, $filePath);
        $filePath = \end($splitFilePath);
        $disallow = ["\x00", "\"", "\r", "\n"];
        $filePath = \str_replace($disallow, "_", $filePath);
        return \implode(self::LINEFEED, ["Content-Disposition: form-data; name=\"{$partName}\"; filename=\"{$filePath}\"", "Content-Type: {$mimeType}", "", $data]);
    }
    private function prepareFormPart($partName, $formPart, $boundary)
    {
        $contentDisposition = "Content-Disposition: form-data; name=\"{$partName}\"";
        $partHeaders = $formPart->getHeaders();
        $formattedheaders = \array_change_key_case($partHeaders);
        if (\array_key_exists("content-type", $formattedheaders)) {
            if ($formattedheaders["content-type"] === "application/json") {
                $contentDisposition .= "; filename=\"{$partName}.json\"";
            }
            $tempRequest = new HttpRequest('/', 'POST');
            $tempRequest->headers = $formattedheaders;
            $tempRequest->body = $formPart->getValue();
            $encoder = new Encoder();
            $partValue = $encoder->serializeRequest($tempRequest);
        } else {
            $partValue = $formPart->getValue();
        }
        $finalPartHeaders = [];
        foreach ($partHeaders as $k => $v) {
            $finalPartHeaders[] = "{$k}: {$v}";
        }
        $body = \array_merge([$contentDisposition], $finalPartHeaders, [""], [$partValue]);
        return \implode(self::LINEFEED, $body);
    }
}
