<?php
namespace ccxt;

// ----------------------------------------------------------------------------

// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

// -----------------------------------------------------------------------------
use \ccxt\Precise;
include_once PATH_TO_CCXT . '/test/exchange/base/test_shared_methods.php';

function test_market($exchange, $skipped_properties, $method, $market) {
    $format = array(
        'id' => 'btcusd',
        'symbol' => 'BTC/USD',
        'base' => 'BTC',
        'quote' => 'USD',
        'taker' => $exchange->parse_number('0.0011'),
        'maker' => $exchange->parse_number('0.0009'),
        'baseId' => 'btc',
        'quoteId' => 'usd',
        'active' => false,
        'type' => 'spot',
        'linear' => false,
        'inverse' => false,
        'spot' => false,
        'swap' => false,
        'future' => false,
        'option' => false,
        'margin' => false,
        'contract' => false,
        'contractSize' => $exchange->parse_number('0.001'),
        'expiry' => 1656057600000,
        'expiryDatetime' => '2022-06-24T08:00:00.000Z',
        'optionType' => 'put',
        'strike' => $exchange->parse_number('56000'),
        'settle' => 'XYZ',
        'settleId' => 'Xyz',
        'precision' => array(
            'price' => $exchange->parse_number('0.001'),
            'amount' => $exchange->parse_number('0.001'),
            'cost' => $exchange->parse_number('0.001'),
        ),
        'limits' => array(
            'amount' => array(
                'min' => $exchange->parse_number('0.01'),
                'max' => $exchange->parse_number('1000'),
            ),
            'price' => array(
                'min' => $exchange->parse_number('0.01'),
                'max' => $exchange->parse_number('1000'),
            ),
            'cost' => array(
                'min' => $exchange->parse_number('0.01'),
                'max' => $exchange->parse_number('1000'),
            ),
        ),
        'marginModes' => array(
            'cross' => true,
            'isolated' => false,
        ),
        'info' => array(),
    );
    // temporary: only test QUANTO markets where that prop exists (todo: add in type later)
    if (is_array($market) && array_key_exists('quanto', $market)) {
        $format['quanto'] = false; // whether the market is QUANTO or not
    }
    // define locals
    $spot = $market['spot'];
    $contract = $market['contract'];
    $swap = $market['swap'];
    $future = $market['future'];
    $option = $market['option'];
    $index = $exchange->safe_bool($market, 'index'); // todo: unify
    $is_index = ($index !== null) && $index;
    $linear = $market['linear'];
    $inverse = $market['inverse'];
    $quanto = $exchange->safe_bool($market, 'quanto'); // todo: unify
    $is_quanto = ($quanto !== null) && $quanto;
    //
    $empty_allowed_for = ['margin'];
    if (!$contract) {
        $empty_allowed_for[] = 'contractSize';
        $empty_allowed_for[] = 'linear';
        $empty_allowed_for[] = 'inverse';
        $empty_allowed_for[] = 'quanto';
        $empty_allowed_for[] = 'settle';
        $empty_allowed_for[] = 'settleId';
    }
    if (!$future && !$option) {
        $empty_allowed_for[] = 'expiry';
        $empty_allowed_for[] = 'expiryDatetime';
    }
    if (!$option) {
        $empty_allowed_for[] = 'optionType';
        $empty_allowed_for[] = 'strike';
    }
    assert_structure($exchange, $skipped_properties, $method, $market, $format, $empty_allowed_for);
    assert_symbol($exchange, $skipped_properties, $method, $market, 'symbol');
    $log_text = log_template($exchange, $method, $market);
    // check taker/maker
    // todo: check not all to be within 0-1.0
    assert_greater($exchange, $skipped_properties, $method, $market, 'taker', '-100');
    assert_less($exchange, $skipped_properties, $method, $market, 'taker', '100');
    assert_greater($exchange, $skipped_properties, $method, $market, 'maker', '-100');
    assert_less($exchange, $skipped_properties, $method, $market, 'maker', '100');
    // validate type
    $valid_types = ['spot', 'margin', 'swap', 'future', 'option', 'index', 'other'];
    assert_in_array($exchange, $skipped_properties, $method, $market, 'type', $valid_types);
    // validate subTypes
    $valid_sub_types = ['linear', 'inverse', 'quanto', null];
    assert_in_array($exchange, $skipped_properties, $method, $market, 'subType', $valid_sub_types);
    // check if 'type' is consistent
    $checked_types = ['spot', 'swap', 'future', 'option'];
    for ($i = 0; $i < count($checked_types); $i++) {
        $type = $checked_types[$i];
        if ($market[$type]) {
            assert($type === $market['type'], 'market.type (' . $market['type'] . ') not equal to "' . $type . '"' . $log_text);
        }
    }
    // check if 'subType' is consistent
    if ($swap || $future) {
        $checked_sub_types = ['linear', 'inverse'];
        for ($i = 0; $i < count($checked_sub_types); $i++) {
            $sub_type = $checked_sub_types[$i];
            if ($market[$sub_type]) {
                assert($sub_type === $market['subType'], 'market.subType (' . $market['subType'] . ') not equal to "' . $sub_type . '"' . $log_text);
            }
        }
    }
    // margin check (todo: add margin as mandatory, instead of undefined)
    if ($spot) {
        // for spot market, 'margin' can be either true/false or undefined
        assert_in_array($exchange, $skipped_properties, $method, $market, 'margin', [true, false, null]);
    } else {
        // otherwise, it must be false or undefined
        assert_in_array($exchange, $skipped_properties, $method, $market, 'margin', [false, null]);
    }
    // check mutually exclusive fields
    if ($spot) {
        assert(!$contract && $linear === null && $inverse === null && !$option && !$swap && !$future, 'for spot market, none of contract/linear/inverse/option/swap/future should be set' . $log_text);
    } else {
        // if not spot, any of the below should be true
        assert($contract && ($future || $swap || $option || $is_index), 'for non-spot markets, any of (future/swap/option/index) should be set' . $log_text);
    }
    $contract_size = $exchange->safe_string($market, 'contractSize');
    // contract fields
    if ($contract) {
        if ($is_quanto) {
            assert($linear === false, 'linear must be false when "quanto" is true' . $log_text);
            assert($inverse === false, 'inverse must be false when "quanto" is true' . $log_text);
        } else {
            // if false or undefined
            assert($inverse !== null, 'inverse must be defined when "contract" is true' . $log_text);
            assert($linear !== null, 'linear must be defined when "contract" is true' . $log_text);
            assert($linear !== $inverse, 'linear and inverse must not be the same' . $log_text);
        }
        // contract size should be defined
        assert(((is_array($skipped_properties) && array_key_exists('contractSize', $skipped_properties)) || $contract_size !== null), '"contractSize" must be defined when "contract" is true' . $log_text);
        // contract size should be above zero
        assert((is_array($skipped_properties) && array_key_exists('contractSize', $skipped_properties)) || Precise::string_gt($contract_size, '0'), '"contractSize" must be > 0 when "contract" is true' . $log_text);
        // settle should be defined
        assert((is_array($skipped_properties) && array_key_exists('settle', $skipped_properties)) || ($market['settle'] !== null && $market['settleId'] !== null), '"settle" & "settleId" must be defined when "contract" is true' . $log_text);
    } else {
        // linear & inverse needs to be undefined
        assert($linear === null && $inverse === null && $quanto === null, 'market linear and inverse (and quanto) must be undefined when "contract" is false' . $log_text);
        // contract size should be undefined
        assert($contract_size === null, '"contractSize" must be undefined when "contract" is false' . $log_text);
        // settle should be undefined
        assert(($market['settle'] === null) && ($market['settleId'] === null), '"settle" must be undefined when "contract" is false' . $log_text);
    }
    // future, swap and option should be mutually exclusive
    if ($market['future']) {
        assert(!$market['swap'] && !$market['option'] && !$is_index, 'market swap and option must be false when "future" is true' . $log_text);
    } elseif ($market['swap']) {
        assert(!$market['future'] && !$market['option'], 'market future and option must be false when "swap" is true' . $log_text);
    } elseif ($market['option']) {
        assert(!$market['future'] && !$market['swap'], 'market future and swap must be false when "option" is true' . $log_text);
    }
    // check specific fields for options & futures
    if ($option || $future) {
        // future or option markets need 'expiry' and 'expiryDatetime'
        assert($market['expiry'] !== null, '"expiry" must be defined when "future" is true' . $log_text);
        assert($market['expiryDatetime'] !== null, '"expiryDatetime" must be defined when "future" is true' . $log_text);
        // expiry datetime should be correct
        $iso_string = $exchange->iso8601($market['expiry']);
        assert($market['expiryDatetime'] === $iso_string, 'expiryDatetime ("' . $market['expiryDatetime'] . '") must be equal to expiry in iso8601 format "' . $iso_string . '"' . $log_text);
        assert_greater($exchange, $skipped_properties, $method, $market, 'expiry', '0');
        if ($option) {
            // strike should be defined
            assert($market['strike'] !== null, '"strike" must be defined when "option" is true' . $log_text);
            assert_greater($exchange, $skipped_properties, $method, $market, 'strike', '0');
            // optionType should be defined
            assert($market['optionType'] !== null, '"optionType" must be defined when "option" is true' . $log_text);
            assert_in_array($exchange, $skipped_properties, $method, $market, 'optionType', ['put', 'call']);
        } else {
            // if not option, then strike and optionType should be undefined
            assert($market['strike'] === null, '"strike" must be undefined when "option" is false' . $log_text);
            assert($market['optionType'] === null, '"optionType" must be undefined when "option" is false' . $log_text);
        }
    } elseif ($spot) {
        // otherwise, expiry needs to be undefined
        assert(($market['expiry'] === null) && ($market['expiryDatetime'] === null), '"expiry" and "expiryDatetime" must be undefined when it is not future|option market' . $log_text);
    }
    // check precisions
    $precision_keys = is_array($market['precision']) ? array_keys($market['precision']) : array();
    $precision_keys_len = count($precision_keys);
    assert($precision_keys_len >= 2, 'precision should have "amount" and "price" keys at least' . $log_text);
    for ($i = 0; $i < count($precision_keys); $i++) {
        $price_or_amount_key = $precision_keys[$i];
        // only allow very high priced markets (wher coin costs around 100k) to have a 5$ price tickSize
        $is_exclusive_pair = $market['baseId'] === 'BTC';
        $is_non_spot = !$spot; // such high precision is only allowed in contract markets
        $is_price = $price_or_amount_key === 'price';
        $is_tick_size_5 = Precise::string_eq('5', $exchange->safe_string($market['precision'], $price_or_amount_key));
        if ($is_non_spot && $is_price && $is_exclusive_pair && $is_tick_size_5) {
            continue;
        }
        if (!(is_array($skipped_properties) && array_key_exists('precision', $skipped_properties))) {
            check_precision_accuracy($exchange, $skipped_properties, $method, $market['precision'], $price_or_amount_key);
        }
    }
    $is_inactive_market = $market['active'] === false;
    // check limits
    $limits_keys = is_array($market['limits']) ? array_keys($market['limits']) : array();
    $limits_keys_length = count($limits_keys);
    assert($limits_keys_length >= 3, 'limits should have "amount", "price" and "cost" keys at least' . $log_text);
    for ($i = 0; $i < count($limits_keys); $i++) {
        $key = $limits_keys[$i];
        $limit_entry = $market['limits'][$key];
        if ($is_inactive_market) {
            continue;
        } // check limits
        if (!(is_array($skipped_properties) && array_key_exists('limits', $skipped_properties))) {
            // min >= 0
            assert_greater_or_equal($exchange, $skipped_properties, $method, $limit_entry, 'min', '0');
            // max >= 0
            assert_greater($exchange, $skipped_properties, $method, $limit_entry, 'max', '0');
            // max >= min
            $min_string = $exchange->safe_string($limit_entry, 'min');
            if ($min_string !== null) {
                assert_greater_or_equal($exchange, $skipped_properties, $method, $limit_entry, 'max', $min_string);
            }
        }
    }
    // check currencies
    assert_valid_currency_id_and_code($exchange, $skipped_properties, $method, $market, $market['baseId'], $market['base']);
    assert_valid_currency_id_and_code($exchange, $skipped_properties, $method, $market, $market['quoteId'], $market['quote']);
    assert_valid_currency_id_and_code($exchange, $skipped_properties, $method, $market, $market['settleId'], $market['settle']);
    // check ts
    assert_timestamp($exchange, $skipped_properties, $method, $market, null, 'created');
    // margin modes
    if (!(is_array($skipped_properties) && array_key_exists('marginModes', $skipped_properties))) {
        $margin_modes = $exchange->safe_dict($market, 'marginModes'); // in future, remove safeDict
        assert(is_array($margin_modes) && array_key_exists('cross', $margin_modes), 'marginModes should have "cross" key' . $log_text);
        assert(is_array($margin_modes) && array_key_exists('isolated', $margin_modes), 'marginModes should have "isolated" key' . $log_text);
        assert_in_array($exchange, $skipped_properties, $method, $margin_modes, 'cross', [true, false, null]);
        assert_in_array($exchange, $skipped_properties, $method, $margin_modes, 'isolated', [true, false, null]);
    }
}
