import configparser
import os
import subprocess
import textwrap

import dulwich.repo
from pbr import packaging
import sphinx
from sphinx.ext import extlinks
from sphinx.util import logging

from . import version
from openstackdocstheme import paths

_series = None
_project = None
_giturl = 'https://opendev.org/{}/src/{}'
_html_context_data = None

logger = logging.getLogger(__name__)

def _has_stable_branches():
    try:
        repo = dulwich.repo.Repo.discover()
    except dulwich.repo.NotGitRepository:
        return False

    refs = repo.get_refs()
    for ref in refs.keys():
        ref = ref.decode('utf-8')
        if ref.startswith('refs/remotes/origin/stable'):
            return True

    return False

def _get_other_versions(app):
    if not app.config.html_theme_options.get('show_other_versions', False):
        return []

    all_series = []
    try:
        repo = dulwich.repo.Repo.discover()
    except dulwich.repo.NotGitRepository:
        return []

    refs = repo.get_refs()
    for ref in refs.keys():
        ref = ref.decode('utf-8')
        if ref.startswith('refs/remotes/origin/stable'):
            series = ref.rpartition('/')[-1]
            all_series.append(series)
        elif ref.startswith('refs/tags/') and ref.endswith('-eol'):
            series = ref.rpartition('/')[-1][:-4]
            all_series.append(series)
    all_series.sort()

    # NOTE(dhellmann): Given when this feature was implemented, we
    # assume that the earliest version we can link to is for
    # mitaka. Projects that have older docs online can set the option
    # to indicate another start point. Projects that come later should
    # automatically include everything they actually have available
    # because the start point is not present in the list.
    earliest_desired = app.config.html_theme_options.get(
        'earliest_published_series', 'mitaka')
    if earliest_desired and earliest_desired in all_series:
        interesting_series = all_series[all_series.index(earliest_desired):]
    else:
        interesting_series = all_series

    # Reverse the list because we want the most recent to appear at
    # the top of the dropdown. Add the "latest" release to the
    # front of the list.
    interesting_series.append("latest")
    interesting_series.reverse()
    return interesting_series

def _get_doc_path(app):
    # Handle 'doc/{docType}/source' paths
    doc_parts = os.path.abspath(app.srcdir).split(os.sep)[-3:]
    if doc_parts[0] == 'doc' and doc_parts[2] == 'source':
        return '/'.join(doc_parts)

    # Handle '{docType}/source' paths
    doc_parts = os.path.abspath(app.srcdir).split(os.sep)[-2:]
    if doc_parts[1] == 'source':
        return '/'.join(doc_parts)

    logger.info(
        "[openstackdocstheme] cannot identify project's root directory."
    )
    return

def _html_page_context(app, pagename, templatename, context, doctree):
    global _html_context_data
    if _html_context_data is None:
        logger.debug('[openstackdocstheme] building html context')

        if app.config.repository_name is not None:
            logger.info(
                "[openstackdocstheme] "
                "the 'repository_name' config option has been deprecated and "
                "replaced by the 'openstackdocs_repo_name' option; support "
                "for the former will be dropped in a future release")
            app.config.openstackdocs_repo_name = app.config.repository_name

        if app.config.use_storyboard is not None:
            logger.info(
                "[openstackdocstheme] "
                "the 'use_storyboard' config option has been deprecated and "
                "replaced by the 'openstackdocs_use_storyboard' option; "
                "support for the former will be dropped in a future release")
            app.config.openstackdocs_use_storyboard = app.config.use_storyboard

        if app.config.bug_project is not None:
            logger.info(
                "[openstackdocstheme] "
                "the 'bug_project' config option has been deprecated and "
                "replaced by the 'openstackdocs_bug_project' option; support "
                "for the former will be dropped in a future release")
            app.config.openstackdocs_bug_project = app.config.bug_project

        if app.config.bug_tag is not None:
            logger.info(
                "[openstackdocstheme] "
                "the 'bug_tag' config option has been deprecated and "
                "replaced by the 'openstackdocs_bug_tag' option; support "
                "for the former will be dropped in a future release")
            app.config.openstackdocs_bug_project = app.config.bug_project

        _html_context_data = {}
        try:
            _html_context_data['gitsha'] = subprocess.check_output(
                ['git', 'rev-parse', 'HEAD'],
            ).decode('utf-8').strip()
        except Exception:
            logger.warning(
                '[openstackdocstheme] cannot get gitsha from git repository'
            )
            _html_context_data['gitsha'] = 'unknown'

        doc_path = _get_doc_path(app)
        repo_name = app.config.openstackdocs_repo_name
        _html_context_data['repository_name'] = repo_name
        logger.debug('[openstackdocstheme] repository_name %r', repo_name)
        if repo_name and doc_path:
            _html_context_data['giturl'] = _giturl.format(repo_name, doc_path)
            logger.debug(
                '[openstackdocstheme] giturl %r', _html_context_data['giturl'],
            )

        use_storyboard = app.config.openstackdocs_use_storyboard
        _html_context_data['use_storyboard'] = use_storyboard

        bug_project = app.config.openstackdocs_bug_project
        if bug_project:
            logger.debug(
                '[openstackdocstheme] bug_project (from user) %r', bug_project,
            )
        elif use_storyboard:
            bug_project = repo_name
            logger.debug(
                '[openstackdocstheme] bug_project (use_storyboard set) %r',
                bug_project,
            )

        if bug_project:
            _html_context_data['bug_project'] = bug_project

        # Previously storyboard showed numbers that were used, keep
        # for old conf.py files:
        if bug_project and bug_project.isdigit():
            logger.debug(
                '[openstackdocstheme] bug_project looks like a number, '
                'setting use_storyboard'
            )
            _html_context_data['use_storyboard'] = True

        bug_tag = app.config.openstackdocs_bug_tag
        if bug_tag:
            _html_context_data['bug_tag'] = bug_tag
            logger.debug('[openstackdocstheme] bug_tag %r', bug_tag)

        _html_context_data['pdf_link'] = app.config.openstackdocs_pdf_link
        logger.debug(
            '[openstackdocstheme] pdf_link %r', _html_context_data['pdf_link'],
        )

        if app.config.openstackdocs_pdf_filename:
            _html_context_data['pdf_filename'] = (
                app.config.openstackdocs_pdf_filename)
        else:
            short_repo_name = repo_name.split('/')[-1]
            _html_context_data['pdf_filename'] = f'doc-{short_repo_name}.pdf'

        if _html_context_data['pdf_link']:
            logger.debug(
                '[openstackdocstheme] pdf_filename %r',
                _html_context_data['pdf_filename'],
            )

        _html_context_data['series'] = _get_series_name()
        logger.debug(
            '[openstackdocstheme] series %r', _html_context_data['series'],
        )

        # Do not show the badge in these cases:
        # - display_badge is false
        # - repo has no stable/ branches
        # - directory is named api-guide, api-ref, or releasenotes
        if not app.config.html_theme_options.get('display_badge', True):
            _html_context_data['display_badge'] = False
            logger.debug(
                '[openstackdocstheme] display_badge False (configured by user)'
            )
        elif _has_stable_branches():
            doc_parts = os.path.abspath(app.srcdir).split(os.sep)[-2:]
            if doc_parts[0] in ('api-guide', 'api-ref', 'releasenotes'):
                _html_context_data['display_badge'] = False
                logger.debug(
                    '[openstackdocstheme] display_badge False (doc name '
                    'contains %r)',
                    doc_parts[0],
                )
            else:
                _html_context_data['display_badge'] = True
                logger.debug(
                    '[openstackdocstheme] display_badge True (stable branches)'
                )
        else:
            _html_context_data['display_badge'] = False
            logger.debug(
                '[openstackdocstheme] display_badge False (no stable branches)'
            )

    context.update(_html_context_data)
    context['other_versions'] = _get_other_versions(app)
    logger.debug(
        '[openstackdocstheme] other_versions %s', context['other_versions'],
    )

def _get_series_name():
    "Return string name of release series, or 'latest'"
    global _series
    if _series is None:
        try:
            git_root_dir = subprocess.check_output(
                ['git', 'rev-parse', '--show-toplevel'],
            ).decode('utf-8').strip()
        except Exception:
            logger.info(
                '[openstackdocstheme] cannot find git top directory, '
                'assuming "."'
            )
            git_root_dir = '.'

        parser = configparser.ConfigParser()
        in_file = os.path.join(git_root_dir, '.gitreview')
        parsed = parser.read(in_file)
        if not parsed:
            logger.info('[openstackdocstheme] no %s found', in_file)

        try:
            branch = parser.get('gerrit', 'defaultbranch')
        except configparser.Error:
            _series = 'latest'
        else:
            _series = branch.rpartition('/')[-1]

    return _series

def _setup_link_roles(app):
    series = _get_series_name()
    for project_name in app.config.openstackdocs_projects:
        url = 'https://docs.openstack.org/{}/{}/%s'.format(
            project_name, series)
        role_name = '{}-doc'.format(project_name)
        logger.debug(
            '[openstackdocstheme] adding role %s to link to %s',
            role_name,
            url,
        )

        if sphinx.version_info >= (4, 0, 0):
            role = extlinks.make_link_role(project_name, url, project_name)
        else:
            role = extlinks.make_link_role(url, project_name)

        app.add_role(role_name, role)

def _find_setup_cfg(srcdir):
    """Find the 'setup.cfg' file, if it exists.

    This assumes we're using 'doc/source' for documentation, but also allows
    for single level 'doc' paths.
    """
    # TODO(stephenfin): Are we sure that this will always exist, e.g. for
    # an sdist or wheel? Perhaps we should check for 'PKG-INFO' or
    # 'METADATA' files, a la 'pbr.packaging._get_version_from_pkg_metadata'
    for path in [
            os.path.join(srcdir, os.pardir, 'setup.cfg'),
            os.path.join(srcdir, os.pardir, os.pardir, 'setup.cfg')]:
        if os.path.exists(path):
            return path

    return None

def _get_project_name(srcdir):
    """Return string name of project name, or None.

    This assumes every project is using 'pbr' and, therefore, the metadata can
    be extracted from 'setup.cfg'.

    We don't rely on distutils/setuptools as we don't want to actually install
    the package simply to build docs.
    """
    global _project
    if _project is None:
        parser = configparser.ConfigParser()

        path = _find_setup_cfg(srcdir)
        if not path or not parser.read(path):
            logger.info(
                '[openstackdocstheme] could not find a setup.cfg to extract '
                'project name from'
            )
            return None

        try:
            # for project name we use the name in setup.cfg, but if the
            # length is longer then 32 we use summary. Otherwise the
            # menu rendering looks broken
            project = parser.get('metadata', 'name')
            if len(project.split()) == 1 and len(project) > 32:
                project = parser.get('metadata', 'summary')
        except configparser.Error:
            logger.info(
                '[openstackdocstheme] could not extract project metadata from '
                'setup.cfg'
            )
            return None
        _project = project
    return _project

def _config_inited(app, config):

    if config.openstack_projects is not None:
        logger.info(
            "[openstackdocstheme] "
            "the 'openstack_projects' config option has been deprecated and "
            "replaced by the 'openstackdocs_projects' option; support "
            "for the former will be dropped in a future release"
        )
        config.openstackdocs_projects = config.openstack_projects

    # we only override configuration if the theme has been configured, meaning
    # users are using these features
    if config.html_theme not in ['openstackdocs', 'starlingxdocs']:
        return

    if config.openstackdocs_auto_name:
        project_name = _get_project_name(app.srcdir)

        if config.project and project_name:
            logger.info(
                "[openstackdocstheme] "
                "overriding configured project name (%s) with name extracted "
                "from the package (%s); you can disable this behavior with "
                "the 'openstackdocs_auto_name' option",
                config.project, project_name,
            )

        if project_name:
            config.project = project_name

    config.html_last_updated_fmt = '%Y-%m-%d %H:%M'

    if config.openstackdocs_auto_version is False:
        logger.debug(
            '[openstackdocstheme] auto-versioning disabled (configured by '
            'user)'
        )
        auto_version = False
    elif config.openstackdocs_auto_version is True:
        logger.debug(
            '[openstackdocstheme] auto-versioning enabled (configured by user)'
        )
        auto_version = True
    else:  # None
        doc_parts = os.path.abspath(app.srcdir).split(os.sep)[-2:]
        if doc_parts[0] in ('api-guide', 'api-ref', 'releasenotes'):
            logger.debug(
                f'[openstackdocstheme] auto-versioning disabled (doc name '
                f'contains {doc_parts[0]}'
            )
            auto_version = False
        else:
            logger.debug(
                '[openstackdocstheme] auto-versioning enabled (default)'
            )
            auto_version = True

    if auto_version:
        real_project_name = _get_project_name(app.srcdir)
        try:
            project_version = packaging.get_version(real_project_name)
        except Exception:
            project_version = ''

        if not project_version:
            logger.warning(
                '[openstackdocstheme] could not extract version from '
                'project; defaulting to unversioned'
            )

        config.version = project_version
        config.release = project_version

def _builder_inited(app):

    theme_dir = paths.get_html_theme_path()
    logger.info('[openstackdocstheme] using theme from %s', theme_dir)

    _setup_link_roles(app)

    # we only override configuration if the theme has been configured, meaning
    # users are using these features
    if app.config.html_theme not in ['openstackdocs', 'starlingxdocs']:
        return

    # Override default setting
    app.config.latex_engine = 'xelatex'

    theme_logo = paths.get_theme_logo_path(app.config.html_theme)
    pdf_theme_path = paths.get_pdf_theme_path(app.config.html_theme)
    latex_elements = {
        'papersize': 'a4paper',
        'pointsize': '11pt',
        'figure_align': 'H',
        'classoptions': ',openany',
    }

    if app.config.latex_elements:
        latex_elements.update(app.config.latex_elements)

    preamble = textwrap.dedent(
        r"""
        \usepackage{%s}
        \\newcommand{\openstacklogo}{%s}
        """
    ) % (pdf_theme_path, theme_logo)

    if 'preamble' in latex_elements:
        preamble += latex_elements['preamble']

    latex_elements['preamble'] = preamble

    app.config.latex_elements = latex_elements

def setup(app):
    logger.info(
        '[openstackdocstheme] version: %s',
        version.version_info.version_string(),
    )
    logger.debug('[openstackdocstheme] connecting events')

    # extensions
    app.connect('config-inited', _config_inited)
    app.connect('builder-inited', _builder_inited)
    app.connect('html-page-context', _html_page_context)

    # config options
    app.add_config_value('openstackdocs_repo_name', '', 'env')
    app.add_config_value('openstackdocs_bug_project', '', 'env')
    app.add_config_value('openstackdocs_bug_tag', '', 'env')
    app.add_config_value('openstackdocs_projects', [], 'env')
    app.add_config_value('openstackdocs_use_storyboard', False, 'env')
    app.add_config_value('openstackdocs_auto_version', None, 'env')
    app.add_config_value('openstackdocs_auto_name', True, 'env')
    app.add_config_value('openstackdocs_pdf_link', False, 'env')
    app.add_config_value('openstackdocs_pdf_filename', None, 'env')

    # legacy config options
    app.add_config_value('repository_name', None, 'env')
    app.add_config_value('bug_project', None, 'env')
    app.add_config_value('bug_tag', None, 'env')
    app.add_config_value('openstack_projects', None, 'env')
    app.add_config_value('use_storyboard', None, 'env')

    # themes
    app.add_html_theme(
        'openstackdocs',
        os.path.abspath(os.path.dirname(__file__)) + '/theme/openstackdocs',
    )
    app.add_html_theme(
        'starlingxdocs',
        os.path.abspath(os.path.dirname(__file__)) + '/theme/starlingxdocs',
    )

    return {
        'parallel_read_safe': True,
        'parallel_write_safe': True,
        'version': version.version_info.version_string(),
    }


__author__ = 'krishnab'

"""
    create grid and polygons module
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    This module contains functions to create a spatial grid, attach points
    to the grid, and identify adjacency between the polygons in the grid.

    :copyright: Krishna Bhogaonker, 2014
    :license: license_name, see LICENSE for more details
"""

## Import appropriate modules
import numpy as np
import unittest

def create_grid(xmin, xmax, ymin, ymax, inc):
    """
    This function will create a numpy array for a grid that contains the
    geometry info for each pixel and the event counts per pixel.

    parameters:

    xmin: float - the minimum x coord for the grid.

    xmax: float - the maximum x coord for the grid.

    ymin: float - the minimum y coord for the grid.

    ymax: float - the maximum y coord for the grid.

    inc: float - the grid size x, y increment. This is a
    square grid so the increment is the same in x, y
    direction

    returns:

    numpy array with a column for.

    leftbx - float - the left bottom x-coordinate of the pixel

    leftby - float - the left bottom y-coordinate of the pixel

    rightbx - float - the right bottom x-coordinate of the pixel

    rightby - float - the right bottom y-coordinate of the pixel

    lefttx - float - the left top x-coordinate of the pixel

    leftty - float - the left top y-coordinate of the pixel

    righttx - float - the right top x-coordinate of the pixel

    rightty - float - the right top y-coordinate of the pixel

    index - int - the pixel index.

    count - int - the event count for the pixel

    centroidx - float - the x-coordinate of the pixel centroid

    centroidy - float - the y-coordinate of the pixel centroid

    """

    # Create a meshgrid for faster vectorized function processing
    x = np.arange(xmin, xmax, inc)
    y = np.arange(ymin, ymax, inc)
    xy = np.meshgrid(x, y)
    xx, yy = np.meshgrid(x,y)
    # create the results grid
    count_grid = np.zeros(shape=(len(x) * len(y), 12), dtype=np.float64)

    # set the original left coordinates equal to the first column of the result
    # grid.

    count_grid[:,0] = count_grid[:,4] = xx.flat[:]
    count_grid[:,1] = count_grid[:,3] = yy.flat[:]

    # set the right bottom coordinate based upon the increment
    count_grid[:,2] = count_grid[:,6] = (xx + inc).flat[:]
    count_grid[:,5] = count_grid[:,7] = (yy + inc).flat[:]

    # create index for pixels
    count_grid[:,8] = np.arange(0,len(x)*len(y),1)

    # generate the centroisds for each pixel

    count_grid[:, 10] = count_grid[:,0] + np.asarray([float(inc)/2.])
    count_grid[:, 11] = count_grid[:,1] + np.asarray([float(inc)/2.])
    # return count_grid with full results.
    return count_grid

def get_counts_per_pixel(data, increment):
    """
    This function will take a set of event data, create a grid, and aggregate
    the event counts by grid pixel.

    parameters:

    data: numpy.array - a numpy array with data

    increment: float - the x and y increment for the grid

    data shape:

    data[0] - float - x-coordinate
    data[1] - float - y-coordinate

    returns:

    numpy array from the create_grid function. But, the count column
    will be filled in.

    """

    # First create the grid to fill

    # first get the range of the grid

    xmin = np.amin(data[:,0])
    xmax = np.amax(data[:,0])
    ymin = np.amin(data[:,1])
    ymax = np.amax(data[:,1])

    g = create_grid(xmin, xmax, ymin, ymax, increment)

    # Sort the data along the x column (axis = 0)
    data.sort(axis = 0)

    # Count the number of records that fall into each bin and save those
    # numbers to the 9th column of the grid array.

    for i in xrange(g.shape[0] + 1):
        g[i, 9] = sum(np.logical_and(np.logical_and(data[:,0] > g[i,0], data[:,0]< g[i,2]),
                       np.logical_and(data[:,1] > g[i,1],data[:,1] < g[i,5])))

    return g

class TestSequenceFunctions(unittest.TestCase):

    def test1(self):
        self.t = create_grid(1,5,1,5,1)
        self.assertEqual(self.t.shape, (16,12))
    def test2(self):
        self.t = create_grid(1,5,1,5,1)
        self.assertEqual(self.t[1][0], 2.0)

if __name__ == '__main__':
    unittest.main()


import pytest

from flex.constants import (
    STRING,
    INTEGER,
    BOOLEAN,
)
from flex.error_messages import MESSAGES
from flex.exceptions import ValidationError
from flex.loading.definitions.schema import schema_validator

from tests.utils import (
    assert_path_not_in_errors,
    assert_message_in_errors,
    assert_message_not_in_errors,
)

def test_default_is_not_required():
    try:
        schema_validator({})
    except ValidationError as err:
        errors = err.detail
    else:
        errors = {}

    assert_path_not_in_errors('default', errors)

def test_default_invalid_if_not_of_single_type():
    with pytest.raises(ValidationError) as err:
        schema_validator({
            'type': STRING,
            'default': True,
        })

    assert_message_in_errors(
        MESSAGES['default']['invalid_type'],
        err.value.detail,
        'default',
    )

def test_default_invalid_if_not_of_multiple_types():
    with pytest.raises(ValidationError) as err:
        schema_validator({
            'type': [STRING, INTEGER],
            'default': True,
        })

    assert_message_in_errors(
        MESSAGES['default']['invalid_type'],
        err.value.detail,
        'default',
    )

def test_default_valid_for_single_type():
    try:
        schema_validator({
            'type': BOOLEAN,
            'default': True,
        })
    except ValidationError as err:
        errors = err.detail
    else:
        errors = {}

    assert_path_not_in_errors('default', errors)
    assert_message_not_in_errors(MESSAGES['default']['invalid_type'], errors)

def test_default_valid_for_multiple_types():
    try:
        schema_validator({
            'type': [STRING, BOOLEAN, INTEGER],
            'default': True,
        })
    except ValidationError as err:
        errors = err.detail
    else:
        errors = {}

    assert_path_not_in_errors('default', errors)
    assert_message_not_in_errors(MESSAGES['default']['invalid_type'], errors)


import logging

from keras.optimizers import SGD
from keras.regularizers import l2
from sklearn.model_selection import train_test_split

from csrank.choicefunction.choice_functions import ChoiceFunctions
from csrank.choicefunction.util import generate_complete_pairwise_dataset
from csrank.core.cmpnet_core import CmpNetCore

logger = logging.getLogger(__name__)

class CmpNetChoiceFunction(ChoiceFunctions, CmpNetCore):
    def __init__(
        self,
        n_hidden=2,
        n_units=8,
        loss_function="binary_crossentropy",
        batch_normalization=True,
        kernel_regularizer=l2,
        kernel_initializer="lecun_normal",
        activation="relu",
        optimizer=SGD,
        metrics=("binary_accuracy",),
        batch_size=256,
        random_state=None,
        **kwargs,
    ):
        """
            Create an instance of the :class:`CmpNetCore` architecture for learning a choice function.
            CmpNet breaks the preferences in form of rankings into pairwise comparisons and learns a pairwise model for
            the each pair of object in the underlying set. For prediction list of objects is converted in pair of
            objects and the pairwise predicate is evaluated using them. The outputs of the network for each pair of
            objects :math:`U(x_1,x_2), U(x_2,x_1)` are evaluated.
            :math:`U(x_1,x_2)` is a measure of how favorable it is to choose :math:`x_1` over :math:`x_2`.
            The utility score of object :math:`x_i` in query set
            :math:`Q = \\{ x_1 , \\ldots , x_n \\}` is evaluated as:

            .. math::

                U(x_i) = \\left\\{ \\frac{1}{n-1} \\sum_{j \\in [n]
                \\setminus \\{i\\}} U_1(x_i , x_j)\\right\\}

            The choice set is defined as:

            .. math::

                c(Q) = \\{ x_i \\in Q \\lvert \\, U(x_i) > t \\}

            Parameters
            ----------
            n_hidden : int
                Number of hidden layers used in the scoring network
            n_units : int
                Number of hidden units in each layer of the scoring network
            loss_function : function or string
                Loss function to be used for the binary decision task of the pairwise comparisons
            batch_normalization : bool
                Whether to use batch normalization in each hidden layer
            kernel_regularizer : uninitialized keras regularizer
                Regularizer function applied to all the hidden weight matrices.
            activation : function or string
                Type of activation function to use in each hidden layer
            optimizer: Class
                Uninitialized optimizer class following the keras optimizer interface.
            optimizer__{kwarg}
                Arguments to be passed to the optimizer on initialization, such as optimizer__lr.
            metrics : list
                List of metrics to evaluate during training (can be non-differentiable)
            batch_size : int
                Batch size to use during training
            random_state : int, RandomState instance or None
                Seed of the pseudorandom generator or a RandomState instance
            hidden_dense_layer__{kwarg}
                Arguments to be passed to the Dense layers (or NormalizedDense
                if batch_normalization is enabled). See the keras documentation
                for those classes for available options.

            References
            ----------
                [1] Leonardo Rigutini, Tiziano Papini, Marco Maggini, and Franco Scarselli. 2011. SortNet: Learning to Rank by a Neural Preference Function. IEEE Trans. Neural Networks 22, 9 (2011), 1368–1380. https://doi.org/10.1109/TNN.2011.2160875

        """
        self._store_kwargs(
            kwargs, {"optimizer__", "kernel_regularizer__", "hidden_dense_layer__"}
        )
        super().__init__(
            n_hidden=n_hidden,
            n_units=n_units,
            loss_function=loss_function,
            batch_normalization=batch_normalization,
            kernel_regularizer=kernel_regularizer,
            kernel_initializer=kernel_initializer,
            activation=activation,
            optimizer=optimizer,
            metrics=metrics,
            batch_size=batch_size,
            random_state=random_state,
        )

    def _convert_instances_(self, X, Y):
        logger.debug("Creating the Dataset")
        x1, x2, garbage, y_double, garbage = generate_complete_pairwise_dataset(X, Y)
        del garbage
        logger.debug("Finished the Dataset instances {}".format(x1.shape[0]))
        return x1, x2, y_double

    def fit(
        self,
        X,
        Y,
        epochs=10,
        callbacks=None,
        validation_split=0.1,
        tune_size=0.1,
        thin_thresholds=1,
        verbose=0,
        **kwd,
    ):
        """
            Fit a CmptNet model for learning a choice fucntion on the provided set of queries X and preferences Y of
            those objects. The provided queries and corresponding preferences are of a fixed size (numpy arrays). For
            learning this network the binary cross entropy loss function for a pair of objects :math:`x_i, x_j \\in Q`
            is defined as:

            .. math::

                C_{ij} =  -\\tilde{P_{ij}}(0)\\cdot \\log(U(x_i,x_j)) - \\tilde{P_{ij}}(1) \\cdot \\log(U(x_j,x_i)) \\ ,

            where :math:`\\tilde{P_{ij}}` is ground truth probability of the preference of :math:`x_i` over :math:`x_j`.
            :math:`\\tilde{P_{ij}} = (1,0)` if :math:`x_i \\succ x_j` else :math:`\\tilde{P_{ij}} = (0,1)`.

            Parameters
            ----------
            X : numpy array
                (n_instances, n_objects, n_features)
                Feature vectors of the objects
            Y : numpy array
                (n_instances, n_objects)
                Preferences in form of Orderings or Choices for given n_objects
            epochs : int
                Number of epochs to run if training for a fixed query size
            callbacks : list
                List of callbacks to be called during optimization
            validation_split : float (range : [0,1])
                Percentage of instances to split off to validate on
            tune_size: float (range : [0,1])
                Percentage of instances to split off to tune the threshold for the choice function
            thin_thresholds: int
                The number of instances of scores to skip while tuning the threshold
            verbose : bool
                Print verbose information
            **kwd :
                Keyword arguments for the fit function
        """
        self._pre_fit()
        if tune_size > 0:
            X_train, X_val, Y_train, Y_val = train_test_split(
                X, Y, test_size=tune_size, random_state=self.random_state
            )
            try:
                super().fit(
                    X_train,
                    Y_train,
                    epochs,
                    callbacks,
                    validation_split,
                    verbose,
                    **kwd,
                )
            finally:
                logger.info(
                    "Fitting utility function finished. Start tuning threshold."
                )
                self.threshold_ = self._tune_threshold(
                    X_val, Y_val, thin_thresholds=thin_thresholds, verbose=verbose
                )
        else:
            super().fit(X, Y, epochs, callbacks, validation_split, verbose, **kwd)
            self.threshold_ = 0.5


#Bailey Bonaci
#Code started from CLOUD APPLICATION DEVELOPMENT (CS_493_400_S2023) materials

from flask import Blueprint, request
from google.cloud import datastore
import json
import constants

http = "http://127.0.0.1:8080"

client = datastore.Client()

bp = Blueprint('boat', __name__, url_prefix='/boats')

@bp.route('', methods=['POST','GET'])
def boats_get_post():
    if request.method == 'POST':
        content = request.get_json()
        if len(content) < 3:
            return (json.dumps({"Error" : "The request object is missing at least one of the required attributes"}),400)

        new_boat = datastore.entity.Entity(key=client.key(constants.boats))
        #define new boat based on content passed in
        new_boat.update({"name": content["name"], "type": content["type"], "length": content["length"],
                        "loads": [], "self": http + "/boats/"})
        #put new boat entity into databse
        client.put(new_boat)

        #update entry with self - need ID for self so this has to be done 2 times
        new_boat.update({"id" : new_boat.key.id, "name": content["name"], "type": content["type"], "length": content["length"],
                    "loads": [], "self": http + "/boats/" + str(new_boat.key.id)})
        client.put(new_boat)
        #make a dictionary to return the results in the body neatly
        boat_dict = {"id" : new_boat.key.id, "name" : new_boat["name"], "type" : new_boat["type"],
                     "length" : new_boat["length"], "loads" : new_boat["loads"],
                     "self": new_boat["self"]}
        return (json.dumps(boat_dict), 201)
    elif request.method == 'GET':
        query = client.query(kind=constants.boats)
        q_limit = int(request.args.get('limit', '3'))
        q_offset = int(request.args.get('offset', '0'))
        l_iterator = query.fetch(limit= q_limit, offset=q_offset)
        pages = l_iterator.pages
        results = list(next(pages))
        if l_iterator.next_page_token:
            next_offset = q_offset + q_limit
            next_url = request.base_url + "?limit=" + str(q_limit) + "&offset=" + str(next_offset)
        else:
            next_url = None
        for e in results:
            e["id"] = e.key.id
        output = {"boats": results}
        if next_url:
            output["next"] = next_url
        return json.dumps(output)
    else:
        return 'Method not recogonized'

@bp.route('/<id>', methods=['PUT','DELETE', 'GET'])
def boats_put_delete(id):
    if request.method == 'PUT':
        content = request.get_json()
        # boat_key = client.key(constants.boats, int(id))
        # boat = client.get(key=boat_key)
        # boat.update({"name": content["name"], "description": content["description"],
        #   "price": content["price"]})
        # client.put(boat)
        # return ('',200)
    elif request.method == 'DELETE':
        boat_key = client.key(constants.boats, int(id))
        if client.get(key=boat_key) == None:
            return (json.dumps({"Error" : "No boat with this boat_id exists"}), 404)
        #THIS NEEDS WORK - THIS IS WHERE YOU LEFT OFF

        boat = client.get(key=boat_key)
        #and unload any loads assiciated
        for item in boat['loads']:
            item_id = item["id"]
            load_key = client.key(constants.loads, int(item_id))
            load = client.get(key=load_key)
            load['carrier'] = None
            client.put(load)

        # delete boat
        client.delete(boat_key)
        return ('',204)
    elif request.method == 'GET':
        boat_key = client.key(constants.boats, int(id))
        #is the ID real?
        if client.get(key=boat_key) == None:
            return (json.dumps({"Error" : "No boat with this boat_id exists"}), 404)
        boat = client.get(key=boat_key)
        boat_dict = {"id" : boat.key.id, "name" : boat["name"], "type" : boat["type"],
                     "length" : boat["length"], "loads" : boat["loads"],
                     "self": boat["self"]}
        print("this is the boat", boat, "this is the boat dict", boat_dict)
        return (json.dumps(boat_dict),200)
    else:
        return 'Method not recogonized'

@bp.route('/<bid>/loads/<lid>', methods=['PUT','DELETE'])
def add_delete_reservation(bid,lid):
    if request.method == 'PUT':
        boat_key = client.key(constants.boats, int(bid))
        load_key = client.key(constants.loads, int(lid))

        #check if boat and load exist
        if client.get(key=boat_key) == None or client.get(key=load_key) == None:
            return (json.dumps({"Error" : "The specified boat and/or load does not exist"}), 404)

        load = client.get(key=load_key)
        #check if load is already else where
        if load['carrier'] != None:
            return (json.dumps({"Error" : "The load is already loaded on another boat"}), 403)

        #load not on other boat
        #get the boat
        boat = client.get(key=boat_key)

        boat['loads'].append({"id":load.id, "self": http + "/loads/" + str(load.id)})

        client.put(boat)

        #update the load with the carrier
        load['carrier'] = ({ "id" : str(boat.id), "name": boat["name"], "self": http + "/boats/" + str(boat.id)})
        client.put(load)

        return('',204)
    if request.method == 'DELETE':
        #get Keys
        boat_key = client.key(constants.boats, int(bid))
        load_key = client.key(constants.loads, int(lid))

        #check if boat and load exist
        if client.get(key=boat_key) == None or client.get(key=load_key) == None:
            return (json.dumps({"Error" : "No boat with this boat_id is loaded with the load with this load_id"}), 404)

        load = client.get(key=load_key)
        #check if load is already removed
        if load['carrier'] == None:
            return (json.dumps({"Error" : "No boat with this boat_id is loaded with the load with this load_id"}), 404)

        #find the carrier boat
        boat = client.get(key=boat_key)

        print("boat loads before removal", boat['loads'])
        boat['loads'].remove({"id":load.id, "self": http + "/loads/" + str(load.id)})
        print("boat loads after removal", boat['loads'])

        client.put(boat)

        #update the load to have no carrier
        load['carrier'] = None
        client.put(load)
        return('',204)

@bp.route('/<id>/loads', methods=['GET'])
def get_reservations(id):
    boat_key = client.key(constants.boats, int(id))
    if client.get(key=boat_key) == None:
            return (json.dumps({"Error" : "No boat with this boat_id exists"}), 404)
    boat = client.get(key=boat_key)
    load_ids  = []
    for load in boat['loads']:
        #get all the ids of the loads
        load_ids.append(load["id"])
    list_loads = []
    for load_id in load_ids:
        print("Round: ", load_id)
        load_key = client.key(constants.loads, int(load_id))
        load = client.get(key=load_key)
        list_loads.append({"id": load["id"], "volume": load["volume"],  "item": load["item"],
                        "creation_date": load["creation_date"], "self": load["self"]})
    results_dict = {}
    results_dict["loads"] = list_loads
    #then with all the ids display the contents of the loads
    return results_dict