<?php

namespace Aws\Api\Serializer;

use Aws\Api\ListShape;
use Aws\Api\MapShape;
use Aws\Api\Shape;
use Aws\Api\StructureShape;
use Aws\Api\TimestampShape;

/**
 * @internal
 */
class QueryParamBuilder {
	private $methods;

	public function __invoke( StructureShape $shape, array $params ) {
		if ( ! $this->methods ) {
			$this->methods = array_fill_keys( get_class_methods( $this ), true );
		}

		$query = [];
		$this->format_structure( $shape, $params, '', $query );

		return $query;
	}

	protected function format_structure(
		StructureShape $shape,
		array $value,
		$prefix,
		&$query
	) {
		if ( $prefix ) {
			$prefix .= '.';
		}

		foreach ( $value as $k => $v ) {
			if ( $shape->hasMember( $k ) ) {
				$member = $shape->getMember( $k );
				$this->format(
					$member,
					$v,
					$prefix . $this->queryName( $member, $k ),
					$query
				);
			}
		}
	}

	protected function format( Shape $shape, $value, $prefix, array &$query ) {
		$type = 'format_' . $shape['type'];
		if ( isset( $this->methods[ $type ] ) ) {
			$this->{$type}( $shape, $value, $prefix, $query );
		} else {
			$query[ $prefix ] = (string) $value;
		}
	}

	protected function queryName( Shape $shape, $default = null ) {
		if ( null !== $shape['queryName'] ) {
			return $shape['queryName'];
		}

		if ( null !== $shape['locationName'] ) {
			return $shape['locationName'];
		}

		if ( $this->isFlat( $shape ) && ! empty( $shape['member']['locationName'] ) ) {
			return $shape['member']['locationName'];
		}

		return $default;
	}

	protected function isFlat( Shape $shape ) {
		return $shape['flattened'] === true;
	}

	protected function format_list(
		ListShape $shape,
		array $value,
		$prefix,
		&$query
	) {
		// Handle empty list serialization
		if ( ! $value ) {
			$query[ $prefix ] = '';

			return;
		}

		$items = $shape->getMember();

		if ( ! $this->isFlat( $shape ) ) {
			$locationName = $shape->getMember()['locationName'] ?: 'member';
			$prefix       .= ".$locationName";
		} elseif ( $name = $this->queryName( $items ) ) {
			$parts                        = explode( '.', $prefix );
			$parts[ count( $parts ) - 1 ] = $name;
			$prefix                       = implode( '.', $parts );
		}

		foreach ( $value as $k => $v ) {
			$this->format( $items, $v, $prefix . '.' . ( $k + 1 ), $query );
		}
	}

	protected function format_map(
		MapShape $shape,
		array $value,
		$prefix,
		array &$query
	) {
		$vals = $shape->getValue();
		$keys = $shape->getKey();

		if ( ! $this->isFlat( $shape ) ) {
			$prefix .= '.entry';
		}

		$i         = 0;
		$keyName   = '%s.%d.' . $this->queryName( $keys, 'key' );
		$valueName = '%s.%s.' . $this->queryName( $vals, 'value' );

		foreach ( $value as $k => $v ) {
			$i ++;
			$this->format( $keys, $k, sprintf( $keyName, $prefix, $i ), $query );
			$this->format( $vals, $v, sprintf( $valueName, $prefix, $i ), $query );
		}
	}

	protected function format_blob( Shape $shape, $value, $prefix, array &$query ) {
		$query[ $prefix ] = base64_encode( $value );
	}

	protected function format_timestamp(
		TimestampShape $shape,
		$value,
		$prefix,
		array &$query
	) {
		$timestampFormat  = ! empty( $shape['timestampFormat'] )
			? $shape['timestampFormat']
			: 'iso8601';
		$query[ $prefix ] = TimestampShape::format( $value, $timestampFormat );
	}

	protected function format_boolean( Shape $shape, $value, $prefix, array &$query ) {
		$query[ $prefix ] = ( $value ) ? 'true' : 'false';
	}
}
