import cv2
import numpy as np

coordinates = []
ix = -1
iy = -1
drawing = False

def set_polygon(image, lines=True):
    print("Left click to select point, click 'r' to restart selection, 's' to save and exit.")
    print("left_bottom, left_top, right_top, right_bottom")
    if lines:
        click_event = click_event_lines
    else:
        click_event = click_event_rectangle
    coordinates.clear()
    roi_image = image.copy()
    # setting mouse handler for the image
    # and calling the click_event() function
    window_name = 'setPolygon r:reset s:save'
    cv2.namedWindow(window_name)
    cv2.setMouseCallback(window_name, click_event, roi_image)
    #cv2.imshow(window_name,image)
    while(True):
        cv2.imshow(window_name, roi_image)
        key = cv2.waitKey(1) & 0xFF
        if key == ord("r"):
            roi_image = image.copy()
            cv2.setMouseCallback(window_name, click_event, roi_image)
            coordinates.clear()
            print("Clear selection.")
            print("left_bottom, left_top, right_top, right_bottom")
        elif key == ord("s"):
            if len(coordinates) >= 4:
                break
            else:
                print('First select four points.')
        # Debug
        elif key == ord("q"):
            coordinates.clear()
            coordinates.extend([(360, 630), (568, 499), (847, 494), (1017, 623)])
            break
        if lines:
        # Draw ROI
            num_points = len(coordinates)
            if num_points == 2:
                cv2.circle(roi_image, coordinates[1], radius=1, color=(255, 0, 0), thickness=3)
                cv2.line(roi_image, coordinates[0], coordinates[1], color=(255, 0, 0), thickness=2)
            elif num_points == 3:
                cv2.circle(roi_image, coordinates[2], radius=1, color=(255, 0, 0), thickness=3)
                cv2.line(roi_image, coordinates[1], coordinates[2], color=(255, 0, 0), thickness=2)
            elif num_points == 4:
                cv2.circle(roi_image, coordinates[3], radius=1, color=(255, 0, 0), thickness=3)
                cv2.line(roi_image, coordinates[2], coordinates[3], color=(255, 0, 0), thickness=2)
                cv2.line(roi_image, coordinates[3], coordinates[0], color=(255, 0, 0), thickness=2)
            elif num_points == 1:
                cv2.circle(roi_image, coordinates[0], radius=1, color=(255, 0, 0), thickness=3)

    cv2.destroyAllWindows()
    return [coordinates[0], coordinates[1], coordinates[2], coordinates[3]]

# function to display the coordinates of
# of the points clicked on the image
def click_event_rectangle(event, x, y, flags, image):
    global ix, iy, drawing
    # left_bottom, left_top, right_top, right_bottom
    # checking for left mouse clicks
    if event == cv2.EVENT_LBUTTONDOWN:
        drawing = True
        ix = x
        iy = y
        # Bottom left
        coordinates.append((ix, iy))

    elif event == cv2.EVENT_MOUSEMOVE:
        if drawing == True:
            cv2.rectangle(image, pt1=(ix,iy), pt2=(x, y),color=(255, 0, 0),thickness=-1)

    elif event == cv2.EVENT_LBUTTONUP:
        drawing = False
        cv2.rectangle(image, pt1=(ix,iy), pt2=(x, y),color=(255, 0, 0),thickness=-1)
        # Top left
        coordinates.append((x, iy))
        # Top right
        coordinates.append((x, y))
        # Bottom right
        coordinates.append((ix, y))

# function to display the coordinates of
# of the points clicked on the image
def click_event_lines(event, x, y, flags, image):
    # left_bottom, left_top, right_top, right_bottom
    # checking for left mouse clicks
    if event == cv2.EVENT_LBUTTONDOWN:
        print('col: {}, row: {}'.format(x, y))
        # Save points
        coordinates.append((x, y))

# Run as script
if __name__=="__main__":
    import utils
    image_folder_name = 'input_image'
    image_paths = utils.get_list_of_input_files(image_folder_name, "jpg")
    # reading the image
    image = cv2.imread(image_paths[0])

    cords = set_polygon(image)
    print(cords)


from django.shortcuts import render, redirect, get_object_or_404
from .models import Article, Comment
from .forms import ArticleForm, CommentForm
from django.contrib.auth.decorators import login_required
from django.contrib.auth import get_user_model

bootstrap_color = {
        'red': 'danger',
        'grey': 'secondary',
        'yellow': 'warning',
        'mint': 'success',
        'skyblue': 'primary',
        'dark': 'dark'
    }
# Create your views here.
def index(request):
    colors = ['red', 'grey', 'yellow', 'mint', 'skyblue', 'dark']
    if request.user.is_authenticated:
        u_c = request.user.color
        articles = Article.objects.order_by('-pk').filter(color = u_c)
    else:
        articles = Article.objects.order_by('-pk')
        u_c = 'white'
    article_number_dict = {}
    for color in colors:
        article_number = get_user_model().objects.filter(color=color).count()
        article_number_dict[color] = article_number
    article_number_dict['best'] = sorted(article_number_dict.items(), key = (lambda x: x[1]), reverse=True)[0][0]
    article_number_dict['best_bootstrap'] = bootstrap_color[article_number_dict['best']]
    context = {
        'articles': articles,
        'article_color': u_c,
        'article_number_dict': article_number_dict,
    }
    return render(request, 'communities/index.html', context)

def article_color(request, article_color):
    colors = ['red', 'grey', 'yellow', 'mint', 'skyblue', 'dark']
    articles = Article.objects.order_by('-pk').filter(color = article_color)
    article_number_dict = {}
    for color in colors:
        article_number = get_user_model().objects.filter(color=color).count()
        article_number_dict[color] = article_number
    article_number_dict['best'] = sorted(article_number_dict.items(), key = (lambda x: x[1]), reverse=True)[0][0]
    article_number_dict['best_bootstrap'] = bootstrap_color[article_number_dict['best']]
    context = {
        'articles': articles,
        'article_color': article_color,
        'article_number_dict': article_number_dict,
    }
    return render(request, 'communities/index.html', context)

@login_required
def create(request, article_color):
    msg = False
    user = request.user
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if user.is_staff or request.user.color == article_color or article_color == 'white':
            if form.is_valid():
                article = form.save(commit=False)
                article.color = article_color
                article.user = request.user
                article.save()
                return redirect('communities:color', article_color)
        else:
            msg = '게시판 확인을 해주세요. 권한이 부족합니다.'
    else:
        form = ArticleForm()
    context = {
        'form': form,
        'article_color': article_color,
        'msg': msg,
    }

    return render(request, 'communities/form.html', context)

@login_required
def detail(request, article_color, article_pk):
    if request.method == 'POST':
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = form.save(commit=False)
            comment.article = get_object_or_404(Article, pk=article_pk)
            comment.user = request.user
            comment.save()
        return redirect('communities:detail', article_color, article_pk)

    else:
        article = get_object_or_404(Article, pk = article_pk)
        form = CommentForm()
        comments = Comment.objects.order_by('-pk').filter(article = article)
        context = {
            'article': article,
            'form': form,
            'comments': comments
        }
        return render(request, 'communities/detail.html', context)

@login_required
def delete(request, article_pk):
    article = get_object_or_404(Article, pk = article_pk)
    if article.user == request.user:
        article.delete()
    return redirect('communities:index')

@login_required
def comment_delete(request, article_pk, comment_pk):
    comment = get_object_or_404(Comment, pk = comment_pk)
    if comment.user == request.user:
        comment.delete()
    return redirect('communities:detail', article_color, article_pk)

@login_required
def update(request, article_pk):
    article = get_object_or_404(Article, pk = article_pk)
    if article.user == request.user:
        if request.method == 'POST':
            form = ArticleForm(request.POST)
            if form.is_valid():
                article_u = form.save(commit = False)
                article_u.color = article.color
                article_u.user = request.user
                article_u.save()
                return redirect('communities:detail', article.color, article_u.pk)
        else:
            form = ArticleForm(instance = article)
        context = {
            'form': form,
        }
        return render(request, 'communities/form.html', context)
    return redirect('communities:index')


""" GainTable iterators for iterating through a GainTable

"""

__all__ = ["gaintable_timeslice_iter", "gaintable_null_iter"]

import logging

import numpy

from rascil.data_models.memory_data_models import GainTable
from rascil.data_models.parameters import get_parameter

log = logging.getLogger("rascil-logger")

def gaintable_null_iter(gt: GainTable, **kwargs) -> numpy.ndarray:
    """One time iterator returning true for all rows

    :param gt:
    :param kwargs:
    :return:
    """
    yield numpy.ones_like(gt.time, dtype=bool)

def gaintable_timeslice_iter(gt: GainTable, **kwargs) -> numpy.ndarray:
    """GainTable iterator

    :param gt: GainTable
    :param timeslice: 'auto' or time in seconds
    :param gaintable_slices: Number of slices (second in precedence to timeslice)
    :return: Boolean array with selected rows=True
    """
    ##assert isinstance(gt, GainTable)
    timemin = numpy.min(gt.time)
    timemax = numpy.max(gt.time)

    timeslice = get_parameter(kwargs, "timeslice", "auto")
    if timeslice == "auto":
        boxes = numpy.unique(gt.time)
        timeslice = 0.1
    elif timeslice is None:
        timeslice = timemax - timemin
        boxes = [0.5 * (timemax + timemin)]
    elif isinstance(timeslice, float) or isinstance(timeslice, int):
        boxes = numpy.arange(timemin, timemax, timeslice)
    else:
        gt_slices = get_parameter(kwargs, "gaintable_slices", None)
        assert (
            gt_slices is not None
        ), "Time slicing not specified: set either timeslice or gaintable_slices"
        boxes = numpy.linspace(timemin, timemax, gt_slices)
        if gt_slices > 1:
            timeslice = boxes[1] - boxes[0]
        else:
            timeslice = timemax - timemin

    for box in boxes:
        rows = numpy.abs(gt.time - box) <= 0.5 * timeslice
        yield rows


from randomNumbers import LinearCongruential
import matplotlib.pyplot as plt

ran1 = LinearCongruential()
ran2 = LinearCongruential(1.0,6.0)

print(ran1.random())
print(ran1.random(10))
print(ran2.random())
print(ran2.random(10))

x = ran1.random(1000)

plt.plot(x, ".")
plt.show()


import pya
import numpy as np

def ecurvesym(rmin=10, theta=np.pi/2, n=50):
   #Reference https://en.wikipedia.org/wiki/Euler_spiral
    #normalized euler curve: l=1/r, theta=l/2/r;
    theta2=theta/2
    ds=np.sqrt(theta2)/n #ds step for normalized euler curve, ~0.01 to give smooth curve
    #n=int(np.sqrt(theta2)/ds) #number of points determined by thetamax and ds
    x=np.zeros(n)
    y=np.zeros(n)
    for i in range(1,n):
      s=i*ds
      x[i]=x[i-1]+np.cos(s*s)*ds
      y[i]=y[i-1]+np.sin(s*s)*ds
   #scale to the defined rmin@theta2, l=1/r/a**2, theta=l/2/r=1/r^2/a^2/4=a^2*l^2
   #==> 1/rmin/rmin/a^2/4=thetamax ==>sfactor=a=sqrt(1/thetamax)/2/rmin
    sfactor=np.sqrt(1/theta2)/2/rmin
    x=x/sfactor
    y=y/sfactor

   #add the symmetrical second part
    for i in range(1,n):
      xmid=(np.tan(np.pi/2-theta2)*x[n-1]+y[n-1]+np.tan(theta2)*x[n-i-1]-y[n-i-1])/(np.tan(theta2)+np.tan(np.pi/2-theta2))
      ymid=-np.tan(np.pi/2-theta2)*(xmid-x[n-1])+y[n-1]
      x=np.append(x,2*xmid-x[n-i-1])
      y=np.append(y,2*ymid-y[n-i-1])
      #x=np.append(x,y[n-1]+x[n-1]-y[n-i-1]) #for 90 degree bend
      #y=np.append(y,y[n-1]+x[n-1]-x[n-i-1]) #for 90 degree bend
    return(x,y)

def ebendw(rmin=10, theta=np.pi/2, w=1, n=50):
    #get the center curve
    xyc=ecurvesym(rmin, theta, n)
    xc=xyc[0]
    yc=xyc[1]
    ds=np.sqrt(theta/2)/n
    n2=len(xc)
   #determine the inner and outer outlines
    xouter=np.zeros(n2)
    youter=np.zeros(n2)
    xinner=np.zeros(n2)
    yinner=np.zeros(n2)
    for i in range(0,n):
      thetai=(i*ds)**2
      xouter[i]=xc[i]+w/2*np.sin(thetai)
      youter[i]=yc[i]-w/2*np.cos(thetai)
      xinner[i]=xc[i]-w/2*np.sin(thetai)
      yinner[i]=yc[i]+w/2*np.cos(thetai)
    for i in range(n,n2):
      thetai=theta-((n2-i)*ds)**2
      xouter[i]=xc[i]+w/2*np.sin(thetai)
      youter[i]=yc[i]-w/2*np.cos(thetai)
      xinner[i]=xc[i]-w/2*np.sin(thetai)
      yinner[i]=yc[i]+w/2*np.cos(thetai)
    x=np.append(xinner,xouter[::-1])
    x=np.append(x,xinner[0])
    y=np.append(yinner,youter[::-1])
    y=np.append(y,yinner[0])
    return(x,y)

class Ebend(pya.PCellDeclarationHelper):
  """
  The PCell declaration for the Euler Bend
  """

  def __init__(self):

    # Important: initialize the super class
    super(Ebend, self).__init__()

    # declare the parameters
    self.param("l", self.TypeLayer, "Layer", default = pya.LayerInfo(1, 0))
    self.param("s", self.TypeShape, "", default = pya.DPoint(0, 0))
    self.param("rmin", self.TypeDouble, "Min Radius", default = 1)
    self.param("w", self.TypeDouble, "Width", default =1)
    self.param("theta", self.TypeDouble, "Total bending angle", default = 3.14)
    self.param("n", self.TypeInt, "Number of points", default = 50)

  def display_text_impl(self):
    # Provide a descriptive text for the cell
    return "Euler Bend(W=" + str(self.w) + ",Rmin=" + ('%.3f' % self.rmin) + ")"

  def produce_impl(self):
    # This is the main part of the implementation: create the layout
    # fetch the parameters convert database unit
    rmin_dbu = self.rmin / self.layout.dbu
    w_dbu=self.w/self.layout.dbu

   #get the outline point coordinates
    pts = []
    xy=ebendw(rmin_dbu, self.theta, w_dbu, int(self.n/2))
    x=xy[0]
    y=xy[1]
    n=len(x)

    for i in range(0,n):
      pts.append(pya.Point.from_dpoint(pya.DPoint(x[i],y[i])))

    # create the shape
    self.cell.shapes(self.l_layer).insert(pya.Polygon(pts))

class MyLib(pya.Library):
  """
  The library where we will put the PCell into
  """

  def __init__(self):

    # Set the description
    self.description = "My First Library"

    # Create the PCell declarations
    self.layout().register_pcell("Euler Bend", Ebend())
    # That would be the place to put in more PCells ...

    # Register us with the name "MyLib".
    # If a library with that name already existed, it will be replaced then.
    self.register("MyLib")

# Instantiate and register the library
MyLib()


# SPDX-License-Identifier: MIT
# See LICENSE.md and CONTRIBUTORS.md at https://github.com/SSAGESLabs/PySAGES

from abc import ABCMeta, abstractmethod
from functools import reduce
from inspect import getfullargspec
from operator import or_
from sys import modules as sys_modules

from jax import jit
from plum import parametric

from pysages.backends import SamplingContext
from pysages.colvars.core import build
from pysages.grids import Grid, build_grid, get_info
from pysages.methods.restraints import canonicalize
from pysages.methods.utils import ReplicasConfiguration
from pysages.typing import Callable, Optional, Union
from pysages.utils import ToCPU, copy, dispatch, dispatch_table, has_method, identity

#  Base Classes
#  ============

@parametric
class Result:
    """
    Bundles the essential information needed to estimate the free energy of a
    system, as well as the necessary data for restarting the simulation.

    Parameters
    ----------
    method: ``SamplingMethod``
        The sampling method used.
    states: ``List[SamplingMethodState]``
        The last state of the sampling method for each system replica run.
    callbacks: ``Optional[List[Callback]]``
        Optional set of callbacks for all simulation replicas.
    snapshots: ``List[Snapshot]``
        Last snapshot of each replica of the simulation.
    """

    @classmethod
    def __infer_type_parameter__(cls, method, *_):
        return type(method)

    def __init__(self, method, states, callbacks, snapshots):
        self.method = method
        self.states = states
        self.callbacks = callbacks
        self.snapshots = snapshots

class ReplicaResult(Result):
    pass

class SamplingMethodMeta(ABCMeta):
    """
    Metaclass for enhanced sampling methods.

    It helps making parametric Result types serializable.
    """

    def __new__(cls, name, bases, namespace):
        S = super().__new__(cls, name, bases, namespace)
        T = Result[S]
        T.__qualname__ = T.__name__ = f"Result[{S.__name__}]"
        setattr(sys_modules[T.__module__], T.__name__, T)
        return S

class SamplingMethod(metaclass=SamplingMethodMeta):
    """
    Abstract base class for all sampling methods.

    This class defines a constructor that expects the list of collective
    variables and provides the necessary methods for initializing and executing
    the biasing during a simulation. Inheriting classes are expected to enhance
    or overwrite its methods as needed.
    """

    __special_args__ = set()
    snapshot_flags = set()

    def __init__(self, cvs, **kwargs):
        self.cvs = cvs
        self.cv = build(*cvs, differentiate=kwargs.get("cv_grad", True))
        self.requires_box_unwrapping = reduce(
            or_, (cv.requires_box_unwrapping for cv in cvs), False
        )
        self.kwargs = kwargs

    def __getstate__(self):
        return default_getstate(self)

    def __setstate__(self, state):
        default_setstate(self, state)

    @abstractmethod
    def build(self, snapshot, helpers, *args, **kwargs):
        """
        Returns the snapshot, and two functions, ``initialize`` and ``update``.

        The ``initialize`` function is intended to allocate any run-time
        information required by the ``update`` function. The ``update``
        function is called after each integration step, that is, after each
        call to the wrapped context's ``run`` method.
        """

class GriddedSamplingMethod(SamplingMethod):
    """Base class for sampling methods that use grids."""

    __special_args__ = {"grid"}

    def __init__(self, cvs, grid, **kwargs):
        check_dims(cvs, grid)
        super().__init__(cvs, **kwargs)
        self.grid = grid
        self.restraints = canonicalize(kwargs.get("restraints", None), cvs)

    def __getstate__(self):
        return (get_info(self.grid), *default_getstate(self))

    def __setstate__(self, state):
        grid_args, args, kwargs = state
        args["grid"] = build_grid(*grid_args)
        default_setstate(self, (args, kwargs))

    @abstractmethod
    def build(self, snapshot, helpers, *args, **kwargs):
        pass

class NNSamplingMethod(GriddedSamplingMethod):
    """Base class for sampling methods that use neural networks."""

    def __init__(self, cvs, grid, topology, **kwargs):
        super().__init__(cvs, grid, **kwargs)
        self.topology = topology

    @abstractmethod
    def build(self, snapshot, helpers, *args, **kwargs):
        pass

#  Main functions
#  ==============

@dispatch.abstract
def run(method_or_result, context_generator, timesteps, **kwargs):
    """
    Runs a single or multiple replicas of a simulation with the specified sampling method.

    **Note**: Many specializations for this method are provided.
    """

@dispatch
def run(  # noqa: F811 # pylint: disable=C0116,E0102
    method: SamplingMethod,
    context_generator: Callable,
    timesteps: Union[int, float],
    callback: Optional[Callable] = None,
    context_args: dict = {},
    post_run_action: Optional[Callable] = None,
    config: ReplicasConfiguration = ReplicasConfiguration(),
    **kwargs,
):
    # """
    # Parameters
    # ----------

    # method_or_result: Union[SamplingMethod, Result]

    # context_generator: Callable
    #     User defined function that sets up a simulation context with the backend.
    #     Must return an instance of hoomd.context.SimulationContext for HOOMD-blue
    #     and openmm.Simulation for OpenMM. The function gets context_args
    #     unpacked for additional user arguments.

    # timesteps: int
    #     Number of time steps the simulation is running.

    # callback: Optional[Callable] = None
    #     Allows for user defined actions into the simulation workflow of the method.
    #     kwargs gets passed to the backend run function.

    # context_args: dict = {}
    #     Arguments to pass down to context_generator to setup the simulation context.

    # post_run_action: Optional[Callable] = None
    #     Callable function that enables actions after the run execution of PySAGES.
    #     Actions are executed inside the generated context. Example uses for this
    #     include writing a final configuration file. This function gets context_args
    #     unpacked just like context_generator.

    # config: ReplicasConfiguration = ReplicasConfiguration()
    #     Specifies the number of replicas of the simulation to generate.
    #     It also contains an executor which will manage different process
    #     or threads in case the multiple simulation are to be run in parallel.
    #     Defaults to ReplicasConfiguration(1, SerialExecutor()),
    #     which means only one simulation is run.
    # """
    timesteps = int(timesteps)

    def submit_work(executor, method, callback):
        return executor.submit(
            _run_replica,
            method,
            context_generator,
            timesteps,
            context_args,
            callback,
            post_run_action,
            **kwargs,
        )

    with config.executor as ex:
        futures = [submit_work(ex, method, callback) for _ in range(config.copies)]
    results = [future.result() for future in futures]
    states = [r.states for r in results]
    snapshots = [r.snapshots for r in results]
    callbacks = None if callback is None else [r.callbacks for r in results]

    return Result(method, states, callbacks, snapshots)

@dispatch
def run(  # noqa: F811 # pylint: disable=C0116,E0102
    sampling_context: SamplingContext,
    timesteps: Union[int, float],
    **kwargs,
):
    # """
    # Alternative interface for running a simulation from a SamplingContext.

    # Parameters
    # ----------
    # sampling_context: SamplingContext
    #     Instance of the simulation context of one of the supported backends
    #     wrapped as a SamplingContext.
    # timesteps: int
    #     Number of time steps the simulation is running.
    # kwargs: dict
    #     These gets passed to the backend run function.

    # Notes
    # -----
    # This interface supports only single replica runs.
    # """
    method = sampling_context.method
    method_type = type(method)
    if has_custom_run(method_type):
        raise RuntimeError(
            f"Method {method_type} is not compatible with the SamplingContext interface "
            "use pysages.run(method, context_generator, timesteps) instead."
        )

    timesteps = int(timesteps)
    sampler = sampling_context.sampler
    callback = None if sampler.callback is None else [sampler.callback]

    with sampling_context:
        sampling_context.run(timesteps, **kwargs)

    return Result(method, [sampler.state], callback, [sampler.take_snapshot()])

@dispatch
def run(  # noqa: F811 # pylint: disable=C0116,E0102
    result: Result,
    context_generator: Callable,
    timesteps: Union[int, float],
    context_args: dict = {},
    post_run_action: Optional[Callable] = None,
    config: ReplicasConfiguration = ReplicasConfiguration(),
    **kwargs,
):
    # """
    # Restart a simulation from a previously stored result.
    # """
    method = result.method
    timesteps = int(timesteps)
    callbacks_ = result.callbacks
    callbacks = [None] * len(result.states) if callbacks_ is None else callbacks_

    def submit_work(executor, result):
        return executor.submit(
            _run_replica,
            result,
            context_generator,
            timesteps,
            context_args,
            post_run_action,
            **kwargs,
        )

    with config.executor as ex:
        result_args = zip(result.states, callbacks, result.snapshots)
        futures = [submit_work(ex, ReplicaResult(method, *args)) for args in result_args]

    results = [future.result() for future in futures]
    states = [r.states for r in results]
    snapshots = [r.snapshots for r in results]
    callbacks = None if callbacks_ is None else [r.callbacks for r in results]

    return Result(method, states, callbacks, snapshots)

def _run_replica(method, *args, **kwargs):
    # Trampoline method to enable multiple replicas to be run with mpi4py.
    run = dispatch_table(dispatch)["_run"]
    return run(method, *args, **kwargs)

@dispatch
def _run(  # noqa: F811 # pylint: disable=C0116,E0102
    method: SamplingMethod,
    context_generator: Callable,
    timesteps: Union[int, float],
    context_args: dict = {},
    callback: Optional[Callable] = None,
    post_run_action: Optional[Callable] = None,
    **kwargs,
):
    """
    Base implementation for running a single simulation with the specified `SamplingMethod`.

    Parameters
    ----------

    method: SamplingMethod

    context_generator: Callable
        User defined function that sets up a simulation context with the backend.
        Must return an instance of `hoomd.context.SimulationContext` for HOOMD-blue
        and `openmm.Simulation` for OpenMM. The function gets `context_args`
        unpacked for additional user arguments.

    timesteps: int
        Number of time steps the simulation is running.

    context_args: dict = {}
        Arguments to pass down to `context_generator` to setup the simulation context.

    callback: Optional[Callable] = None
        Allows for user defined actions into the simulation workflow of the method.
        `kwargs` gets passed to the backend `run` function. Default value is `None`.

    post_run_action: Optional[Callable] = None
        Callable function that enables actions after the run execution of PySAGES.
        Actions are executed inside the generated context. Example uses for this
        include writing a final configuration file. This function gets `context_args`
        unpacked just like `context_generator`.

    **Note**: All arguments must be pickable.
    """
    timesteps = int(timesteps)

    sampling_context = SamplingContext(method, context_generator, callback, context_args)
    context_args["context"] = sampling_context.context
    sampler = sampling_context.sampler

    with sampling_context:
        sampling_context.run(timesteps, **kwargs)
        if post_run_action:
            post_run_action(**context_args)

    return ReplicaResult(method, sampler.state, callback, sampler.take_snapshot())

@dispatch
def _run(  # noqa: F811 # pylint: disable=C0116,E0102
    result: ReplicaResult,
    context_generator: Callable,
    timesteps: Union[int, float],
    context_args: dict = {},
    post_run_action: Optional[Callable] = None,
    **kwargs,
):
    """
    Base implementation for running a single simulation from a previously stored `Result`.
    """
    timesteps = int(timesteps)
    method = result.method
    callback = result.callbacks

    sampling_context = SamplingContext(method, context_generator, callback, context_args)
    context_args["context"] = sampling_context.context
    sampler = sampling_context.sampler
    prev_snapshot = result.snapshots
    if sampler.state.xi.device().platform == "cpu":
        prev_snapshot = copy(prev_snapshot, ToCPU())
    sampler.restore(prev_snapshot)
    sampler.state = result.states

    with sampling_context:
        sampling_context.run(timesteps, **kwargs)
        if post_run_action:
            post_run_action(**context_args)

    return ReplicaResult(method, sampler.state, callback, sampler.take_snapshot())

@dispatch.abstract
def analyze(result: Result):
    pass

#  Utils
#  =====

def default_getstate(method: SamplingMethod):
    init_args = set(getfullargspec(method.__init__).args[1:]) - method.__special_args__
    return {key: method.__dict__[key] for key in init_args}, method.kwargs

def default_setstate(method, state):
    args, kwargs = state
    method.__init__(**args, **kwargs)

@dispatch
def check_dims(cvs, grid: Grid):
    if len(cvs) != grid.shape.size:
        raise ValueError("Grid and Collective Variable dimensions must match.")

@dispatch
def check_dims(cvs, grid: type(None)):  # noqa: F811 # pylint: disable=C0116,E0102
    pass

@dispatch
def get_method(method: SamplingMethod):
    return method

@dispatch
def get_method(result: Result):  # noqa: F811 # pylint: disable=C0116,E0102
    return result.method

def has_custom_run(method: type):
    """
    Determine if ``method`` has a specialized ``run`` implementation.
    """
    return has_method(dispatch_table(dispatch)["run"], method, 0)

def generalize(concrete_update, helpers, jit_compile=True):
    if jit_compile:
        _jit = jit
    else:
        _jit = identity

    _update = _jit(concrete_update)

    def update(snapshot, state):
        return _update(state, helpers.query(snapshot))

    return _jit(update)


"""Module to interact with Google Maps Geocode API."""

from my_secrets import ggeocode as GEOCODE_API_KEY
import json
import requests

def get_location_coordinates(location):
    """Get the lat and long coordinates for the location."""
    # Replace the "," with a "+"
    address = location.replace(",", "+")

    # Define the base url
    geo_url = f"https://maps.googleapis.com/maps/api/geocode/json?address={address}&key={GEOCODE_API_KEY}"
    response = requests.get(geo_url)
    content = response.content.decode("utf8")
    geo_js = json.loads(content)
    geo_status = geo_js["status"]

    if geo_status == "OK":
        geo_elements = geo_js["results"][0]
        geometry = geo_elements["geometry"]
        location_coordinates = geometry["location"]
        location_lat = location_coordinates["lat"]
        location_long = location_coordinates["lng"]
        print(f"Long/lat coordinates successfully extracted.")
    else:
        location_lat = "Unavailable"
        location_long = "Unavailable"
        print(f"Long/lat coordinates unavailable.")

    return location_lat, location_long


# Website Scraping using BeautifulSoup of Python
# Made by following up YouTube tutorial of freecodecamp.org
# Link : https://www.youtube.com/watch?v=XVv6mJpFOb0&ab_channel=freeCodeCamp.orgfreeCodeCamp.orgVerified
# RifatXia

from os import waitpid
from bs4 import BeautifulSoup

# Requests info from any website
import requests

import time

# Condition for filtering out the results using skill
print('Input the skill that you are unfamiliar with: ')
notSkills = input('>')
print(f'Filtering out {notSkills}')

# The default work function
def findJobs():

    # The variable to store the website URL and .text is used at the end to get the actual html in text format
    html_text = requests.get('https://www.timesjobs.com/candidate/job-search.html?searchType=personalizedSearch&from=submit&txtKeywords=python&txtLocation=').text

    # The format of the BeautifulSoup
    soup = BeautifulSoup(html_text, 'lxml')

    # Find all related job posts
    jobs = soup.find_all('li', class_ = 'clearfix job-bx wht-shd-bx')

    # Using a for loop to access all the information, index is used to maintain the position at which the data is relevant
    for index, job in enumerate(jobs):
        posted = job.find('span', class_ = 'sim-posted').span.text

        # Filtering out
        if 'few' in posted:

            # Strip method is used to remove the extra whitespaces
            companyName = job.find('h3', class_ = 'joblist-comp-name').text.strip()

            skill = job.find('span', class_ = 'srp-skills').text.strip()

            # Only the links will be printed when a['href' is used]
            more_info = job.header.h2.a['href']

            # Filtering out if not a relevant skill
            if notSkills not in skill:

                # Writing the data in separate txt files according to the different indices
                with open(f'posts/{index}.txt', 'w') as f:

                    # Printing out the relevant data
                    f.write(f"Company Name: {companyName}\n")
                    f.write(f"Required Skills: {skill}\n")
                    f.write(f"More Info: {more_info}\n")

                print(f'File saved: {index}')

# The main function which would run the program every 10 seconds and it will go on forever since an infinite while loop is used
if __name__ == '__main__':
    while True:
        findJobs()
        waitingTime = 10
        print(f'Waiting for {waitingTime} seconds')
        time.sleep(waitingTime)


#!/usr/bin/env python

'''
Advent of Code 2021 - Day 10: Syntax Scoring (Part 2)
https://adventofcode.com/2021/day/10
'''

from queue import LifoQueue
from statistics import median

OPEN_CHARS = ['(', '[', '{', '<']

def scoring(char):
    points = {
        ')': 1,
        ']': 2,
        '}': 3,
        '>': 4,
    }

    return points.get(char, 0)

def get_paired_char(char):
    pairs = {
        ')': '(',
        ']': '[',
        '}': '{',
        '>': '<',
        '(': ')',
        '[': ']',
        '{': '}',
        '<': '>'
    }

    return pairs.get(char, None)

def main():

    filename = input("What is the input file name? ")

    try:
        with open(filename, "r") as file:

            scores = []

            # Read the lines and check the syntax
            for line in file:
                line = line.strip()

                stack = LifoQueue()
                corrupted = False
                for char in line:
                    if char in OPEN_CHARS:
                        # Push to the stack
                        stack.put(char)
                        continue

                    # Pop the stack
                    stack_char = stack.get()
                    if stack_char != get_paired_char(char):
                        print(
                            f"{line:30} - Expected {get_paired_char(stack_char)}, but found {char} instead.")
                        corrupted = True
                        break

                if not corrupted:
                    # Get the symbols needed for completion
                    completion_chars = []
                    score = 0
                    while not stack.empty():
                        stack_char = stack.get()
                        completion_chars.append(get_paired_char(stack_char))
                        score *= 5
                        score += scoring(get_paired_char(stack_char))
                    scores.append(score)
                    print(
                        f"{''.join(completion_chars):>30} - {score} Total points.")

            # Sort the scores
            scores.sort()
            print(f"\nThe middle score: {median(scores)}\n")
    except FileNotFoundError:
        print(f"No such file or directory: '{filename}'")

if __name__ == "__main__":
    main()