"""
General utility functions.
"""

import json
from pathlib import Path
from typing import Callable

from .constant import Exchange


def extract_full_symbol(full_symbol: str):
    """
    :return: (symbol, exchange)
    """
    tmp = full_symbol.split(' ')
    if len(tmp) < 4:
        return "unknwonsymbol", Exchange.SHFE
    symbol = tmp[2] + tmp[3]
    exchange_str = tmp[0]
    ex = Exchange(exchange_str)
    if ex in [Exchange.SHFE, Exchange.DCE, Exchange.INE]:
        symbol = symbol.lower()
    return symbol, ex


# from ctp symbol to full symbol
def generate_full_symbol(exchange: Exchange, symbol: str, type: str = 'F'):
    product = ''
    contractno = ''
    fullsym = symbol
    if symbol:
        if type == 'F' or type == 'O':
            for count, word in enumerate(symbol):
                if word.isdigit():
                    break
            product = symbol[:count]
            contractno = symbol[count:]
            fullsym = exchange.value + ' ' + type + ' '\
                + product.upper() + ' ' + contractno
        elif type == 'S':
            combo = symbol.split(' ', 1)[1]
            symbol1 = combo.split('&')[0]
            symbol2 = combo.split('&')[1]
            for count, word in enumerate(symbol1):
                if word.isdigit():
                    break
            product = symbol1[:count]
            contractno = symbol1[count:]
            for count, word in enumerate(symbol2):
                if word.isdigit():
                    break
            product += ('&' + symbol2[:count])
            contractno += ('&' + symbol2[count:])
            fullsym = exchange.value + ' ' + type + ' '\
                + product.upper() + ' ' + contractno
    return fullsym


def extract_vt_symbol(vt_symbol: str):
    """
    :return: (symbol, exchange)
    """
    symbol, exchange_str = vt_symbol.split('.')
    return symbol, Exchange(exchange_str)


def generate_vt_symbol(symbol: str, exchange: Exchange):
    return f'{symbol}.{exchange.value}'


def _get_trader_dir(temp_name: str):
    """
    Get path where trader is running in.
    """
    cwd = Path.cwd()
    temp_path = cwd.joinpath(temp_name)

    # If .StarQuant folder exists in current working directory,
    # then use it as trader running path.
    if temp_path.exists():
        return cwd, temp_path

    # Otherwise use home path of system.
    home_path = Path.home()
    temp_path = home_path.joinpath(temp_name)

    # Create .StarQuant folder under home path if not exist.
    if not temp_path.exists():
        temp_path.mkdir()

    return home_path, temp_path


TRADER_DIR, TEMP_DIR = _get_trader_dir(".StarQuant")


def get_file_path(filename: str):
    """
    Get path for temp file with filename.
    """
    return TEMP_DIR.joinpath(filename)


def get_folder_path(folder_name: str):
    """
    Get path for temp folder with folder name.
    """
    folder_path = TEMP_DIR.joinpath(folder_name)
    if not folder_path.exists():
        folder_path.mkdir()
    return folder_path


def get_icon_path(filepath: str, ico_name: str):
    """
    Get path for icon file with ico name.
    """
    ui_path = Path(filepath).parent
    icon_path = ui_path.joinpath("ico", ico_name)
    return str(icon_path)


def load_json(filename: str):
    """
    Load data from json file in temp path.
    """
    filepath = get_file_path(filename)

    if filepath.exists():
        with open(filepath, mode='r') as f:
            data = json.load(f)
        return data
    else:
        save_json(filename, {})
        return {}


def save_json(filename: str, data: dict):
    """
    Save data into json file in temp path.
    """
    filepath = get_file_path(filename)
    with open(filepath, mode='w+') as f:
        json.dump(data, f, indent=4)


def round_to_pricetick(price: float, pricetick: float):
    """
    Round price to price tick value.
    """
    rounded = round(price / pricetick, 0) * pricetick
    return rounded


def round_to(value: float, target: float):
    """
    Round price to price tick value.
    """
    rounded = int(round(value / target)) * target
    return rounded


def virtual(func: Callable):
    """
    mark a function as "virtual", which means that this function can be override.
    any base class should use this or @abstractmethod to decorate all functions
    that can be (re)implemented by subclasses.
    """
    return func
