#!/usr/bin/env python3

"""
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
"""

import argparse
import ipaddress
import os
import sys
from datetime import datetime

import logbook
import pytablewriter as ptw

import typepy
from typepy import (
    Bool,
    DateTime,
    Dictionary,
    Infinity,
    Integer,
    IpAddress,
    List,
    Nan,
    NoneType,
    NullString,
    RealNumber,
    String,
)


logbook.StderrHandler(
    level=logbook.DEBUG, format_string="[{record.level_name}] {record.channel}: {record.message}"
).push_application()

METHOD_HEADER = "Method"


class ExampleWriter:
    _METHOD_LIST = ("is_type", "validate", "convert", "try_convert", "force_convert")


class ResultMatrixWriter(ExampleWriter):
    def __init__(self):
        self.typeclass = None
        self.strict_level = None
        self.headers = None
        self.value_list = None

        self.__table_writer = ptw.RstSimpleTableWriter()
        self.__table_writer._dp_extractor.type_value_map = {
            NullString(None).typecode: '``""``',
            NoneType(None).typecode: "``None``",
            Infinity(None).typecode: '``Decimal("inf")``',
            Nan(None).typecode: '``Decimal("nan")``',
        }
        self.__table_writer.value_map = {
            True: "``True``",
            False: "``False``",
            '``"127.0.0.1"``': '``ip_address("127.0.0.1")``',
        }

    def set_stream(self, output_stream):
        self.__table_writer.stream = output_stream

    def write_type_matrix(self):
        tbl_template = ":py:class:`typepy.{0:s}`: :py:attr:`~typepy.{0:s}.strict_level` = {1:d}"
        self.__table_writer.table_name = tbl_template.format(
            self.typeclass.__name__, self.strict_level
        )
        self.__table_writer.headers = self.headers
        self.__table_writer.value_matrix = self.__get_result_matrix()
        if self.typeclass.__name__ in ["Dictionary", "List"]:
            self.__table_writer.type_hints = [String for _ in self.headers]

        self.__table_writer.write_table()
        self.__table_writer.write_null_line()

    def exeute(self, method, value):
        str_convert_type = (str, ipaddress.IPv4Address, ipaddress.IPv6Address)

        try:
            result = getattr(self.typeclass(value, self.strict_level), method)()

            if method == "validate":
                result = "NOP [#f1]_"
            elif isinstance(result, str_convert_type):
                result = f'``"{result}"``'
        except TypeError:
            result = "E [#f2]_"
        except typepy.TypeConversionError:
            result = "E [#f3]_"

        return result

    def __get_result_matrix(self):
        result_matrix = []
        for method in self._METHOD_LIST:
            result_matrix.append(
                [f":py:meth:`~.type.{self.typeclass.__name__:s}.{method:s}`"]
                + [self.exeute(method, value) for value in self.value_list]
            )

        return result_matrix


class ResultMatrixManager:
    class ExampleBool:
        HEADER = [METHOD_HEADER] + ["``True``", '``"true"``', 1]
        VALUE = [True, "true", 1]

    class ExampleString:
        HEADER = [METHOD_HEADER] + ['``"abc"``', '``""``', '``"  "``', "``None``", "``1``"]
        VALUE = ["abc", "", "  ", None, 1]

    class ExampleNumber:
        HEADER = [METHOD_HEADER] + [
            "``1``",
            "``1.0``",
            "``1.1``",
            '``"1"``',
            '``"1.0"``',
            '``"1.1"``',
            "``True``",
        ]
        VALUE = [1, 1.0, 1.1, "1", "1.0", "1.1", True]

    class ExampleInfinity:
        HEADER = [METHOD_HEADER] + ['``float("inf")``', '``"Infinity"``', "``0.1``"]
        VALUE = [float("inf"), "Infinity", 0.1]

    class ExampleNan:
        HEADER = [METHOD_HEADER] + ['``float("nan")``', '``"NaN"``', "``0.1``"]
        VALUE = [float("nan"), "NaN", 0.1]

    class ExampleDateTime:
        HEADER = [METHOD_HEADER] + [
            "``datetime(2017, 1, 23, 4, 56)``",
            '``"2017-01-22T04:56:00+0900"``',
            "``1485685623``",
            '``"1485685623"``',
        ]
        VALUE = [datetime(2017, 1, 23, 4, 56), "2017-01-22T04:56:00+0900", 1485685623, "1485685623"]

    class ExampleIpAddress:
        HEADER = [METHOD_HEADER] + [
            "``ip_address('127.0.0.1')``",
            "``'127.0.0.1'``",
            "``'::1'``",
            "``'192.168.0.256'``",
            "``None``",
        ]
        VALUE = [ipaddress.ip_address("127.0.0.1"), "127.0.0.1", "::1", "192.168.0.256", None]

    class ExampleList:
        HEADER = [METHOD_HEADER] + [
            "``[]``",
            '``["a", "b"]``',
            '``("a", "b")``',
            '``{"a": 1}``',
            '``"abc"``',
        ]
        VALUE = [[], ["a", "b"], ("a", "b"), {"a": 1}, "abc"]

    class ExampleDictionary:
        HEADER = [METHOD_HEADER] + ["``{}``", '``{"a": 1}``', '``(("a", 1), )``']
        VALUE = [{}, {"a": 1}, (("a", 1),)]

    def __init__(self):
        self.strict_level_list = None

        self.__ewriter = ResultMatrixWriter()

    def set_stream(self, output_stream):
        self.__ewriter.set_stream(output_stream)

    def write_none(self):
        self.__ewriter.typeclass = NoneType
        self.__ewriter.headers = self.ExampleString.HEADER
        self.__ewriter.value_list = self.ExampleString.VALUE
        self.__write()

    def write_ipaddress(self):
        self.__ewriter.typeclass = IpAddress
        self.__ewriter.headers = self.ExampleIpAddress.HEADER
        self.__ewriter.value_list = self.ExampleIpAddress.VALUE
        self.__write()

    def write_bool(self):
        self.__ewriter.typeclass = Bool
        self.__ewriter.headers = self.ExampleBool.HEADER
        self.__ewriter.value_list = self.ExampleBool.VALUE
        self.__write()

    def write_string(self):
        self.__ewriter.typeclass = String
        self.__ewriter.headers = self.ExampleString.HEADER
        self.__ewriter.value_list = self.ExampleString.VALUE
        self.__write()

    def write_null_string(self):
        self.__ewriter.typeclass = NullString
        self.__ewriter.headers = self.ExampleString.HEADER
        self.__ewriter.value_list = self.ExampleString.VALUE
        self.__write()

    def write_integer(self):
        self.__ewriter.typeclass = Integer
        self.__ewriter.headers = self.ExampleNumber.HEADER
        self.__ewriter.value_list = self.ExampleNumber.VALUE
        self.__write()

    def write_realnumber(self):
        self.__ewriter.typeclass = RealNumber
        self.__ewriter.headers = self.ExampleNumber.HEADER
        self.__ewriter.value_list = self.ExampleNumber.VALUE
        self.__write()

    def write_infinity(self):
        self.__ewriter.typeclass = Infinity
        self.__ewriter.headers = self.ExampleInfinity.HEADER
        self.__ewriter.value_list = self.ExampleInfinity.VALUE
        self.__write()

    def write_nan(self):
        self.__ewriter.typeclass = Nan
        self.__ewriter.headers = self.ExampleNan.HEADER
        self.__ewriter.value_list = self.ExampleNan.VALUE
        self.__write()

    def write_datetime(self):
        self.__ewriter.typeclass = DateTime
        self.__ewriter.headers = self.ExampleDateTime.HEADER
        self.__ewriter.value_list = self.ExampleDateTime.VALUE
        self.__write()

    def write_list(self):
        self.__ewriter.typeclass = List
        self.__ewriter.headers = self.ExampleList.HEADER
        self.__ewriter.value_list = self.ExampleList.VALUE
        self.__write()

    def write_dictionary(self):
        self.__ewriter.typeclass = Dictionary
        self.__ewriter.headers = self.ExampleDictionary.HEADER
        self.__ewriter.value_list = self.ExampleDictionary.VALUE
        self.__write()

    def __write(self):
        for strict_level in self.strict_level_list:
            self.__ewriter.strict_level = strict_level
            self.__ewriter.write_type_matrix()


class PathMaker:
    def __init__(self, output_dir, encoding="utf8"):
        self.__output_dir = output_dir
        self.__encoding = encoding

        if not os.path.isdir(output_dir):
            os.makedirs(output_dir)

    def open_write(self, filename):
        file_path = os.path.join(self.__output_dir, filename)
        print(file_path)

        return open(file_path, "w", encoding=self.__encoding)


def parse_option():
    parser = argparse.ArgumentParser()

    parser.add_argument("--type", choices=["reference", "example"])

    parser.add_argument(
        "-o", "--output-dir", default=".", required=True, help="default=%(default)s"
    )

    return parser.parse_args()


def make_filename(name, prefix="matrix_", suffix="_type", extension=".txt"):
    return f"{prefix:s}{name:s}{suffix:s}{extension:s}"


def write_result_matrix(output_dir_path):
    manager = ResultMatrixManager()
    opener = PathMaker(output_dir_path)

    manager.strict_level_list = (0,)
    with opener.open_write(make_filename("none")) as f:
        manager.set_stream(f)
        manager.write_none()

    manager.strict_level_list = (0, 1)
    with opener.open_write(make_filename("ipaddress")) as f:
        manager.set_stream(f)
        manager.write_ipaddress()

    with opener.open_write(make_filename("infinity")) as f:
        manager.set_stream(f)
        manager.write_infinity()

    with opener.open_write(make_filename("nan")) as f:
        manager.set_stream(f)
        manager.write_nan()

    with opener.open_write(make_filename("list")) as f:
        manager.set_stream(f)
        manager.write_list()

    with opener.open_write(make_filename("dictionary")) as f:
        manager.set_stream(f)
        manager.write_dictionary()

    with opener.open_write(make_filename("nullstring")) as f:
        manager.set_stream(f)
        manager.write_null_string()

    manager.strict_level_list = (0, 1, 2)
    with opener.open_write(make_filename("bool")) as f:
        manager.set_stream(f)
        manager.write_bool()

    with opener.open_write(make_filename("datetime")) as f:
        manager.set_stream(f)
        manager.write_datetime()

    with opener.open_write(make_filename("integer")) as f:
        manager.set_stream(f)
        manager.write_integer()

    with opener.open_write(make_filename("realnumber")) as f:
        manager.set_stream(f)
        manager.write_realnumber()

    with opener.open_write(make_filename("string")) as f:
        manager.set_stream(f)
        manager.write_string()


def write_example(output_dir_path):
    opener = PathMaker(output_dir_path)  # noqa


def main():
    options = parse_option()

    # ptw.set_log_level(logbook.DEBUG)

    write_result_matrix(options.output_dir)
    write_example(options.output_dir)

    return 0


if __name__ == "__main__":
    sys.exit(main())
