import historical_data
import indicators_sma_rsi
import support_resistance
import sys
sys.path.append("init")
from init import *

@dataclass
class Values:
    ticker_csv: str
    selected_timeframe: str

    def __post_init__(self):
        self.ticker_csv = self.ticker_csv.upper()
        self.selected_timeframe = self.selected_timeframe.lower()

class Supres(Values):
    @staticmethod
    def main(ticker_csv, selected_timeframe, candle_count=254):
        df = pd.read_csv(
            ticker_csv,
            delimiter=",",
            encoding="utf-8-sig",
            index_col=False,
            nrows=candle_count,
            keep_default_na=False,
        )
        df = df.iloc[::-1]
        df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d")
        df = pd.concat([df, df.tail(1)], axis=0, ignore_index=True)
        df.dropna(inplace=True)
        historical_hightimeframe = (
            historical_data.Client.KLINE_INTERVAL_1DAY,
            historical_data.Client.KLINE_INTERVAL_3DAY,
            historical_data.Client.KLINE_INTERVAL_1WEEK,
        )
        historical_lowtimeframe = (
            historical_data.Client.KLINE_INTERVAL_1MINUTE,
            historical_data.Client.KLINE_INTERVAL_3MINUTE,
            historical_data.Client.KLINE_INTERVAL_5MINUTE,
            historical_data.Client.KLINE_INTERVAL_15MINUTE,
            historical_data.Client.KLINE_INTERVAL_30MINUTE,
            historical_data.Client.KLINE_INTERVAL_1HOUR,
            historical_data.Client.KLINE_INTERVAL_2HOUR,
            historical_data.Client.KLINE_INTERVAL_4HOUR,
            historical_data.Client.KLINE_INTERVAL_6HOUR,
            historical_data.Client.KLINE_INTERVAL_8HOUR,
            historical_data.Client.KLINE_INTERVAL_12HOUR,
        )
        sma_values = 20, 50, 100
        sma1, sma2, sma3, rsi = indicators_sma_rsi.indicators(df[:-1], *sma_values)
        (
            support_list,
            resistance_list,
            fibonacci_uptrend,
            fibonacci_downtrend,
            pattern_list,
            support_above,
            support_below,
            resistance_below,
            resistance_above,
        ) = (
            [],
            [],
            [],
            [],
            [],
            [],
            [],
            [],
            [],
        )

        fibonacci_multipliers = 0.236, 0.382, 0.500, 0.618, 0.705, 0.786, 0.886
        # Chart settings
        (
            legend_color,
            chart_color,
            background_color,
            support_line_color,
            resistance_line_color,
        ) = ("#D8D8D8", "#E7E7E7", "#E7E7E7", "LightSeaGreen", "MediumPurple")
        fig = make_subplots(
            rows=3,
            cols=1,
            shared_xaxes=True,
            vertical_spacing=0,
            row_width=[0.1, 0.1, 0.8],
        )

        def fibonacci_pricelevels(high_price, low_price):
            """
            Uptrend Fibonacci Retracement Formula =>
            Fibonacci Price Level = High Price - (High Price - Low Price)*Fibonacci Level
            :param high_price: High price for the period
            :param low_price: Low price for the period
            """
            for multiplier in fibonacci_multipliers:
                retracement_levels_uptrend = (
                    low_price + (high_price - low_price) * multiplier
                )
                fibonacci_uptrend.append(retracement_levels_uptrend)
                retracement_levels_downtrend = (
                    high_price - (high_price - low_price) * multiplier
                )
                fibonacci_downtrend.append(retracement_levels_downtrend)
            return fibonacci_uptrend, fibonacci_downtrend

        def sensitivity(sens=2):
            """
            Find the support and resistance levels for a given asset.
            sens:1 is recommended for daily charts or high frequency trade scalping.
            :param sens: sensitivity parameter default:2, level of
            detail 1-2-3 can be given to function
            """
            for sens_row in range(3, len(df) - 1):
                if support_resistance.support(df, sens_row, 3, sens):
                    support_list.append((sens_row, df.low[sens_row]))
                if support_resistance.resistance(df, sens_row, 3, sens):
                    resistance_list.append((sens_row, df.high[sens_row]))
            return support_list, resistance_list

        def chart_lines():
            """
            Check if the support and resistance lines are above or below the latest close price.
            """
            all_support_list = tuple(map(lambda sup1: sup1[1], support_list))
            all_resistance_list = tuple(map(lambda res1: res1[1], resistance_list))
            latest_close = df["close"].iloc[-1]
            for support_line in all_support_list:  # Find closes
                if support_line < latest_close:
                    support_below.append(support_line)
                else:
                    resistance_below.append(support_line)
            if len(support_below) == 0:
                support_below.append(min(df.low))
            for resistance_line in all_resistance_list:
                if resistance_line > latest_close:
                    resistance_above.append(resistance_line)
                else:
                    support_above.append(resistance_line)
            if len(resistance_above) == 0:
                resistance_above.append(max(df.high))
            return fibonacci_pricelevels(max(resistance_above), min(support_below))

        def candlestick_patterns() -> list:
            """
            Takes in a dataframe and returns a list of candlestick patterns
            found in the dataframe then returns pattern list.
            """
            from candlestick import candlestick as cd

            nonlocal df
            all_patterns = [
                cd.inverted_hammer,
                cd.hammer,
                cd.doji,
                cd.bearish_harami,
                cd.bearish_engulfing,
                cd.bullish_harami,
                cd.bullish_engulfing,
                cd.dark_cloud_cover,
                cd.dragonfly_doji,
                cd.hanging_man,
                cd.gravestone_doji,
                cd.morning_star,
                cd.morning_star_doji,
                cd.piercing_pattern,
                cd.star,
                cd.shooting_star,
            ]
            # Loop through the candlestick pattern functions
            for pattern in all_patterns:
                # Apply the candlestick pattern function to the data frame
                df = pattern(df)
            # Replace True values with 'pattern_found'
            df.replace({True: "pattern_found"}, inplace=True)

            def pattern_find_func(pattern_row) -> list:
                """
                Find Candlestick patterns in the dataframe.
                """
                t = 0
                pattern_find = list(df.columns)
                for pattern_f in pattern_row:
                    if pattern_f == "pattern_found":
                        pattern_list.append(
                            (pattern_find[t], pattern_row["date"].strftime("%b-%d-%y"))
                        )  # pattern, date
                    t += 1
                return pattern_list

            return df.iloc[-3:-30:-1].apply(pattern_find_func, axis=1)

        def legend_candle_patterns():
            fig.add_trace(
                go.Scatter(
                    y=[support_list[0]],
                    name="----------------------------------------",
                    mode="markers",
                    marker=dict(color=legend_color, size=14),
                )
            )
            fig.add_trace(
                go.Scatter(
                    y=[support_list[0]],
                    name="Latest Candlestick Patterns",
                    mode="markers",
                    marker=dict(color=legend_color, size=14),
                )
            )
            for pat1, count in enumerate(pattern_list):  # Candlestick patterns
                fig.add_trace(
                    go.Scatter(
                        y=[support_list[0]],
                        name=f"{pattern_list[pat1][1]} : {str(pattern_list[pat1][0]).capitalize()}",
                        mode="lines",
                        marker=dict(color=legend_color, size=10),
                    )
                )

        def create_candlestick_plot():
            """
            Creates a candlestick plot using the dataframe df, and adds it to the figure.
            """
            fig.add_trace(
                go.Candlestick(
                    x=df["date"][:-1].dt.strftime(x_date),
                    name="Candlestick",
                    text=df["date"].dt.strftime(x_date),
                    open=df["open"],
                    high=df["high"],
                    low=df["low"],
                    close=df["close"],
                ),
                row=1,
                col=1,
            )

        def add_volume_subplot() -> None:
            """
            Adds a volume subplot to the figure.
            """
            fig.add_trace(
                go.Bar(
                    x=df["date"][:-1].dt.strftime(x_date),
                    y=df["Volume USDT"],
                    name="Volume USDT",
                    showlegend=False,
                ),
                row=2,
                col=1,
            )

        def add_rsi_subplot():
            fig.add_trace(
                go.Scatter(
                    x=df["date"][:-1].dt.strftime(x_date),
                    y=rsi,
                    name="RSI",
                    showlegend=False,
                ),
                row=3,
                col=1,
            )
            fig.add_hline(
                y=30,
                name="RSI lower band",
                line=dict(color="red", width=1),
                line_dash="dash",
                row=3,
                col=1,
            )
            fig.add_hline(
                y=70,
                name="RSI higher band",
                line=dict(color="red", width=1),
                line_dash="dash",
                row=3,
                col=1,
            )
            fig.add_hrect(
                y0=30, y1=70, line_width=0, fillcolor="gray", opacity=0.2, row=3, col=1
            )

        def draw_support():
            """
            Draws the support lines and adds annotations to the chart.
            """
            for s in range(len(support_list)):
                # Support lines
                fig.add_shape(
                    type="line",
                    x0=support_list[s][0] - 1,
                    y0=support_list[s][1],
                    x1=len(df) + 25,
                    y1=support_list[s][1],
                    line=dict(color=support_line_color, width=2),
                )
                # Support annotations
                fig.add_annotation(
                    x=len(df) + 7,
                    y=support_list[s][1],
                    text=str(support_list[s][1]),
                    font=dict(size=15, color=support_line_color),
                )

        def draw_resistance():
            """
            Draws the resistance lines and adds annotations to the chart.
            """
            for r in range(len(resistance_list)):
                # Resistance lines
                fig.add_shape(
                    type="line",
                    x0=resistance_list[r][0] - 1,
                    y0=resistance_list[r][1],
                    x1=len(df) + 25,
                    y1=resistance_list[r][1],
                    line=dict(color=resistance_line_color, width=1),
                )
                # Resistance annotations
                fig.add_annotation(
                    x=len(df) + 20,
                    y=resistance_list[r][1],
                    text=str(resistance_list[r][1]),
                    font=dict(size=15, color=resistance_line_color),
                )

        def legend_texts():
            fig.add_trace(
                go.Scatter(
                    y=[support_list[0]],
                    name="Resistances    ||   Supports",
                    mode="markers+lines",
                    marker=dict(color=resistance_line_color, size=10),
                )
            )
            sample_price = df["close"][0]
            str_price_len = len(str(sample_price)) if sample_price < 1 else 3

            def legend_support_resistance_values():
                """
                Takes the support and resistance values and adds them to the legend.
                """
                temp = 0
                blank = " " * (len(str(sample_price)) + 1)
                differ = abs(len(f_res_above) - len(f_sup_below))
                try:
                    if len(f_res_above) < len(f_sup_below):
                        f_res_above.extend([0] * differ)
                    else:
                        f_sup_below.extend([0] * differ)
                    for _ in range(min(max(len(f_res_above), len(f_sup_below)),12)):
                        if f_res_above[temp] == 0:  # This is for legend alignment
                            legend_supres = (
                                f"{float(f_res_above[temp]):.{str_price_len - 1}f}{blank}     "
                                f"||   {float(f_sup_below[temp]):.{str_price_len - 1}f}"
                            )
                        else:
                            legend_supres = (
                                f"{float(f_res_above[temp]):.{str_price_len - 1}f}       "
                                f"||   {float(f_sup_below[temp]):.{str_price_len - 1}f}"
                            )
                        fig.add_trace(
                            go.Scatter(
                                y=[support_list[0]],
                                name=legend_supres,
                                mode="lines",
                                marker=dict(color=legend_color, size=10),
                            )
                        )
                        temp += 1 if temp < 12 else 0
                except IndexError:
                    pass

            def text_and_indicators():
                fig.add_trace(
                    go.Scatter(
                        y=[support_list[0]],
                        name="Equity Maestro",
                        mode="markers",
                        marker=dict(color=legend_color, size=0),
                    )
                )
                fig.add_trace(
                    go.Scatter(
                        y=[support_list[0]],
                        name=f"RSI          : {int(rsi[-1])}",
                        mode="lines",
                        marker=dict(color=legend_color, size=10),
                    )
                )
                # Add SMA1, SMA2, and SMA3 to the chart and legend
                fig.add_trace(
                    go.Scatter(
                        x=df["date"].dt.strftime(x_date),
                        y=sma1,
                        name=f"SMA{sma_values[0]}     : {float(sma1[-1]):.{str_price_len}f}",
                        line=dict(color="#5c6cff", width=3),
                    )
                )
                fig.add_trace(
                    go.Scatter(
                        x=df["date"].dt.strftime(x_date),
                        y=sma2,
                        name=f"SMA{sma_values[1]}     : {float(sma2[-1]):.{str_price_len}f}",
                        line=dict(color="#950fba", width=3),
                    )
                )
                fig.add_trace(
                    go.Scatter(
                        x=df["date"].dt.strftime(x_date),
                        y=sma3,
                        name=f"SMA{sma_values[2]}   : {float(sma3[-1]):.{str_price_len}f}",
                        line=dict(color="#a69b05", width=3),
                    )
                )
                fig.add_trace(
                    go.Scatter(
                        y=[support_list[0]],
                        name="       Fibonacci Uptrend | Downtrend ",
                        mode="markers",
                        marker=dict(color=legend_color, size=0),
                    )
                )

            def legend_fibonacci():
                """
                Adds to the legend for each Fibonacci level text.
                """
                mtp = len(fibonacci_multipliers) - 1
                for _ in fibonacci_uptrend:
                    fig.add_trace(
                        go.Scatter(
                            y=[support_list[0]],
                            name=f"Fib {fibonacci_multipliers[mtp]:.3f} "
                            f": {float(fibonacci_uptrend[mtp]):.{str_price_len}f} "
                            f"| {float(fibonacci_downtrend[mtp]):.{str_price_len}f} ",
                            mode="lines",
                            marker=dict(color=legend_color, size=10),
                        )
                    )
                    mtp -= 1

            legend_support_resistance_values()
            text_and_indicators()
            legend_fibonacci()
            # Candle patterns for HTF
            if selected_timeframe in historical_hightimeframe:
                legend_candle_patterns()

        def chart_updates():
            """
            Updates the chart's layout, background color, chart color, legend color, and margin.
            """
            fig.update_layout(
                title=str(
                    f"{historical_data.ticker} {selected_timeframe.upper()} Chart"
                ),
                hovermode="x",
                dragmode="zoom",
                paper_bgcolor=background_color,
                plot_bgcolor=chart_color,
                xaxis_rangeslider_visible=False,
                legend=dict(bgcolor=legend_color, font=dict(size=11)),
                margin=dict(t=30, l=0, b=0, r=0),
            )
            fig.update_xaxes(showspikes=True, spikecolor="green", spikethickness=2)
            fig.update_yaxes(showspikes=True, spikecolor="green", spikethickness=2)

        sensitivity()
        chart_lines()
        # Checking if the selected timeframe is in the historical_hightimeframe list.
        if selected_timeframe in historical_hightimeframe:
            candlestick_patterns()
            x_date = "%b-%d-%y"
        elif selected_timeframe in historical_lowtimeframe:
            x_date = "%H:%M %d-%b"
        create_candlestick_plot()
        f_res_above = list(map(float, sorted(resistance_above + resistance_below)))
        f_sup_below = list(
            map(float, sorted(support_below + support_above, reverse=True))
        )
        draw_support()
        draw_resistance()
        add_volume_subplot()
        add_rsi_subplot()
        legend_texts()
        chart_updates()

        return fig.show(id="the_graph", config={"displaylogo": False})

if __name__ == "__main__":
    file_name = historical_data.user_ticker.file_name
    try:
        perf = time.perf_counter()
        historical_data.user_ticker.historical_data_write()
        if os.path.isfile(file_name):  # Check .csv file exists
            Supres.main(file_name, historical_data.time_frame)

            os.remove(file_name)  # remove the .csv file
        else:
            raise print(
                "One or more issues caused the download to fail. "
                "Make sure you typed the filename correctly."
            )
    except KeyError:
        os.remove(file_name)
        raise KeyError("Key error, algorithm issue")


from django.test import TestCase
from rest_framework.test import APIClient
from rest_framework import status
from .models import Product, ProductQuantity

class ProductAPITestCase(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.product_data = {
            'name': 'Test Product',
            'quantity': 10,
            'price': 9.99
        }
        self.product = Product.objects.create(**self.product_data)

    def test_create_product(self):
        response = self.client.post('/products/', self.product_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(Product.objects.count(), 2)  # One product already exists in setUp

    def test_get_product_list(self):
        response = self.client.get('/products/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)  # Only one product in setUp

    def test_get_product_detail(self):
        response = self.client.get(f'/products/{self.product.id}/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['name'], self.product_data['name'])

    def test_update_product(self):
        new_quantity = 5
        response = self.client.patch(f'/products/{self.product.id}/', {'quantity': new_quantity}, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.product.refresh_from_db()
        self.assertEqual(self.product.quantity, new_quantity)

    def test_update_product_creates_quantity_instance(self):
        new_quantity = 7
        response = self.client.patch(f'/products/{self.product.id}/', {'quantity': new_quantity}, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertTrue(ProductQuantity.objects.filter(product=self.product).exists())

    def test_update_product_updates_quantity_instance(self):
        existing_quantity = 15
        ProductQuantity.objects.create(product=self.product, quantity=existing_quantity)

        new_quantity = 8
        response = self.client.patch(f'/products/{self.product.id}/', {'quantity': new_quantity}, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        product_quantity = ProductQuantity.objects.get(product=self.product)
        self.assertEqual(product_quantity.quantity, existing_quantity + new_quantity)


import datetime
import werkzeug
from celery.result import AsyncResult
from flask import request, current_app
from flask_restful import reqparse, Resource
from typing import List

from MTMS import db_session, cache
from MTMS.Utils.utils import register_api_blueprints, get_user_by_id, get_average_gpa, get_course_by_id, \
    create_email_sending_status
from ..Course.services import get_term_by_id
from ..Models.setting import Email_Delivery_Status
from ..Utils.enums import ApplicationStatus, ApplicationType, EmailCategory, EmailStatus
from MTMS.Utils import validator
from MTMS.Models.users import Users
from MTMS.Models.applications import Application, SavedProfile, CourseApplication
from MTMS.Models.courses import CourseUser, RoleInCourse
from MTMS.Auth.services import auth, get_permission_group, check_user_permission
from .services import get_student_application_list_by_id, get_application_by_id, \
    saved_student_profile, get_saved_student_profile, save_course_application, get_course_application, upload_file, \
    check_application_data, exist_termID, get_all_application_by_term, get_status_application_by_term, \
    get_application_by_course_id, get_saved_student_profile_Files, send_application_result_email, \
    ApplicationLimitationChecker

class NewApplication(Resource):
    @auth.login_required(role=get_permission_group("NewApplication"))
    def get(self, termID, app_type):
        """
        create a new application for current user
        ---
        tags:
          - Application
        parameters:
            - in: path
              name: termID
              type: string
              required: true
            - in: path
              name: type
              type: string
              required: true
        responses:
            200:
              schema:
                properties:
                  application_id:
                    type: integer
        security:
          - APIKeyHeader: ['Authorization']
        """
        term = get_term_by_id(termID)
        if term is None:
            return {"message": "Term is not exist"}, 404
        current_user = auth.current_user()
        result = ApplicationLimitationChecker(term, app_type, current_user.id)
        if not result:
            return {"message": f"You can only apply for {app_type} position once a term"}, 400
        application = Application(createdDateTime=datetime.datetime.now(tz=datetime.timezone.utc),
                                  studentID=current_user.id, status="Unsubmit",
                                  term=termID, type=app_type)
        db_session.add(application)
        db_session.commit()
        db_session.refresh(application)
        return {"application_id": application.ApplicationID}, 200

class saveApplication_Files(Resource):
    @auth.login_required
    def get(self, application_id):
        application = get_application_by_id(application_id)
        files = get_saved_student_profile_Files(application)
        return {'files': files}, 200

class saveApplication(Resource):
    @auth.login_required
    def post(self, application_id):
        """
        save the application profile
        ---
        tags:
          - Application
        parameters:
          - name: application_id
            in: path
            required: true
            schema:
              type: string
          - in: body
            name: body
            required: true
            schema:
              properties:
                applicationPersonalDetail:
                  type: array
                course:
                  type: array
                  items:
                    properties:
                      courseID:
                        type: integer
                      hasLearned:
                        type: boolean
                      grade:
                        type: string
                      preExperience:
                        type: string
                      preference:
                        type: integer
        responses:
          200:
            schema:
              properties:
                message:
                  type: string
        security:
          - APIKeyHeader: ['Authorization']
        """
        parser = reqparse.RequestParser()
        args = parser.add_argument('applicationPersonalDetail', type=dict, location='json', required=False) \
            .add_argument('course', type=list, location='json', required=False) \
            .add_argument('fileURLCV', type=str, location='json', required=False) \
            .add_argument('fileURLAD', type=str, location='json', required=False) \
            .parse_args()

        application = get_application_by_id(application_id)
        if application is None:
            return {"message": "This application could not be found."}, 404
        current_user = auth.current_user()
        ApplicationApprovalPermission = check_user_permission(current_user, "ApplicationApproval")
        if not ApplicationApprovalPermission and application.status != ApplicationStatus.Unsubmit:
            return {"message": "This application has been completed."}, 400

        if current_user.id == application.studentID or ApplicationApprovalPermission:
            processed = 0
            if args['applicationPersonalDetail'] is not None:
                if len(args['applicationPersonalDetail']) == 0:
                    return {
                               "message": "Given 'applicationPersonalDetail' field, but did not give any student personal detail"}, 400
                saved_student_profile_res = saved_student_profile(application, args['applicationPersonalDetail'])
                if saved_student_profile_res[0]:
                    processed += 1
                else:
                    return {"message": saved_student_profile_res[1]}, saved_student_profile_res[2]
            if args['course'] is not None:
                response = save_course_application(application, args["course"])
                if response[0]:
                    processed += 1
                else:
                    return {"message": response[1]}, response[2]
            if isinstance(args['fileURLCV'], str) and args['fileURLCV']:
                fileURLCV = eval(args['fileURLCV'])
                if fileURLCV['_value'] != '':
                    response = upload_file(application, 'cv', fileURLCV['_value'])
                    if response[0]:
                        processed += 1
                    else:
                        return {"message": response[1]}, response[2]

            if isinstance(args['fileURLAD'], str) and args['fileURLAD']:
                fileURLAD = eval(args['fileURLAD'])
                if fileURLAD['_value'] != '':
                    response = upload_file(application, 'academicRecord', fileURLAD['_value'])
                    if response[0]:
                        processed += 1
                    else:
                        return {"message": response[1]}, response[2]
            if processed >= 1:
                return {"message": "Successful"}, 200
            else:
                return {"message": "Did not give any valid data"}, 400
        else:
            return {"message": "Unauthorized Access"}, 403

    @auth.login_required()
    def get(self, application_id):
        """
        get the saved application profile
        ---
        tags:
          - Application
        parameters:
          - name: application_id
            in: path
            required: true
            schema:
              type: string
        responses:
          200:
            schema:
              properties:
                application:
                  type: object
                  properties:
        security:
            - APIKeyHeader: ['Authorization']
        """
        application = get_application_by_id(application_id)
        if application is None:
            return {"message": "This application could not be found."}, 404
        current_user = auth.current_user()
        if current_user.id == application.studentID:
            saved_PersonalDetail = get_saved_student_profile(application)
            saved_course = get_course_application(application)
            return {"applicationPersonalDetail": saved_PersonalDetail, "course": saved_course}, 200
        else:
            return {"message": "Unauthorized Access"}, 403

class submitApplication(Resource):
    @auth.login_required()
    def get(self, application_id):
        application = get_application_by_id(application_id)
        if application.studentID != auth.current_user().id:
            return {"message": "Unauthorized Access"}, 403
        if application is None:
            return {"message": "This application could not be found."}, 404
        check = check_application_data(application)
        if not check[0]:
            return {"message": check[1]}, 400

        user: Users = application.Users
        profile: SavedProfile = application.SavedProfile
        if user.email is None:
            user.email = profile.email
        if user.name is None:
            user.name = profile.name
        if user.auid is None:
            user.auid = profile.auid
        if user.upi is None:
            user.upi = profile.upi

        user.currentlyOverseas = profile.currentlyOverseas
        user.willBackToNZ = profile.willBackToNZ
        user.isCitizenOrPR = profile.isCitizenOrPR
        user.haveValidVisa = profile.haveValidVisa
        user.enrolDetails = profile.enrolDetails
        user.studentDegree = profile.studentDegree
        user.haveOtherContracts = profile.haveOtherContracts
        user.otherContracts = profile.otherContracts
        user.maximumWorkingHours = profile.maximumWorkingHours
        user.cv = profile.cv
        user.academicRecord = profile.academicRecord

        application.status = ApplicationStatus.Pending.name
        application.submittedDateTime = datetime.datetime.now(tz=datetime.timezone.utc)
        db_session.commit()
        return {"message": "Success"}, 200

class Application_api(Resource):
    @auth.login_required()
    def delete(self, application_id):
        """
        delete the student application
        ---
        tags:
          - Application
        parameters:
          - name: application_id
            in: path
            required: true
            schema:
              type: integer
        responses:
          200:
            schema:
              properties:
                message:
                  type: string
        security:
          - APIKeyHeader: ['Authorization']
        """
        current_user: Users = auth.current_user()
        application = get_application_by_id(application_id)
        if application is None:
            return {"message": "This application could not be found."}, 404
        if current_user.id == application.studentID or check_user_permission(current_user, "ApplicationApproval"):
            if application.status != ApplicationStatus.Unsubmit:
                return {"message": "Submitted application cannot be deleted."}, 400
            db_session.delete(application)
            db_session.commit()
            return {"message": "Successful"}, 200
        else:
            return {"message": "Unauthorized Access"}, 403

    @auth.login_required()
    def get(self, application_id):
        """
        get the student application meta information by id
        ---
        tags:
          - Application
        parameters:
          - name: application_id
            in: path
            required: true
            schema:
              type: integer
        responses:
          200:
            schema:
              properties:
                application:
                  type: object
                  properties:
        security:
          - APIKeyHeader: ['Authorization']
        """
        current_user: Users = auth.current_user()
        application = get_application_by_id(application_id)
        if application is None:
            return {"message": "This application could not be found."}, 404
        if current_user.id == application.studentID or check_user_permission(current_user, "ApplicationApproval"):
            return application.serialize(), 200
        else:
            return {"message": "Unauthorized Access"}, 403

class ApplicationListByTerm(Resource):
    @auth.login_required(role=get_permission_group("ApplicationApproval"))
    def get(self, term_id, status, app_type):
        """
        get all applications by term (Approval)
        ---
        tags:
          - Application
        parameters:
          - name: term_id
            in: path
            required: true
            schema:
              type: integer
          - name: status
            in: path
            required: true
            schema:
              type: string
          - name: app_type
            in: path
            required: true
            schema:
              type: string
        responses:
          200:
            schema:
              properties:
                application:
                  type: array
                  items:
                    type: object
        security:
          - APIKeyHeader: ['Authorization']
        """
        status = status[0].upper() + status[1:].lower()
        app_type = app_type.lower()
        if app_type not in [a.value for a in ApplicationType]:
            return {"message": "Invalid type. It should be tutor or marker"}, 400
        if not exist_termID(term_id):
            return {"message": "Term does not exist."}, 404
        if status == "All":
            res = get_all_application_by_term(term_id, app_type)
            if res[0]:
                applications = res[1]
        elif status == "Published":
            applications = get_status_application_by_term(term_id, "", True, app_type)
        elif status in [a.name for a in ApplicationStatus]:
            applications: list[Application] = get_status_application_by_term(term_id, status, False, app_type)
        else:
            return {"message": "Invalid status"}, 400
        response = []
        for a in applications:
            application_dict = a.serialize()
            if a.SavedProfile is not None:
                application_dict.update(a.SavedProfile.serialize())
            if a.Courses:
                preferCourseList = [c.serialize() for c in a.Courses]
                application_dict.update({"PreferCourse": preferCourseList})
                application_dict.update({"haveCourseCoordinatorEndorsed": False})
                for c in preferCourseList:
                    if c["courseCoordinatorEndorsed"]:
                        application_dict.update({"haveCourseCoordinatorEndorsed": True})
                        break
                preferCourseGPA = get_average_gpa([c.grade for c in a.Courses])
                application_dict.update({"PreferCourseGPA": preferCourseGPA})
            if a.course_users:
                application_dict.update(
                    {"EnrolledCourse": [cu.serialize_with_course_information() for cu in a.course_users]})
            response.append(application_dict)
        return response, 200

class CurrentStudentApplicationList(Resource):
    @auth.login_required()
    def get(self):
        """
        get current student application list
        ---
        tags:
          - Application
        responses:
          200:
            schema:
              type: array
              items:
                type: object
                properties:
                  applicationID:
                    type: integer
                  createdDateTime:
                    type: string
                    format: date-time
                  submittedDateTime:
                    type: string
                    format: date-time
                  isCompleted:
                    type: boolean
        security:
          - APIKeyHeader: ['Authorization']
        """
        current_user = auth.current_user()
        if current_user is None:
            return {"message": "This student could not be found."}, 404

        return get_student_application_list_by_id(current_user.id), 200

class StudentApplicationList(Resource):
    @auth.login_required()
    def get(self, student_id):
        """
        get the student application list
        ---
        tags:
          - Application
        parameters:
          - name: student_id
            in: path
            required: true
            schema:
              type: string
        responses:
          200:
            schema:
              type: array
              items:
                type: object
                properties:
                  applicationID:
                    type: integer
                  createdDateTime:
                    type: string
                    format: date-time
                  submittedDateTime:
                    type: string
                    format: date-time
                  isCompleted:
                    type: boolean
        security:
          - APIKeyHeader: ['Authorization']
        """
        if get_user_by_id(student_id) is None:
            return {"message": "This student could not be found."}, 404

        current_user: Users = auth.current_user()
        if current_user.id == student_id or check_user_permission(current_user, "GetEveryStudentProfile"):
            return get_student_application_list_by_id(student_id), 200
        else:
            return {"message": "Unauthorized Access"}, 403

class ApplicationApproval(Resource):
    @auth.login_required(role=get_permission_group("ApplicationApproval"))
    def put(self, application_id, status):
        """
        update the application status
        ---
        tags:
          - Application
        parameters:
          - name: application_id
            in: path
            required: true
            schema:
              type: integer
          - name: status
            in: path
            required: true
            schema:
              type: string
          - in: body
            name: body
            required: true
        responses:
          200:
            schema:
              properties:
                message:
                  type: string
        security:
          - APIKeyHeader: ['Authorization']
        """
        try:
            application: Application = get_application_by_id(application_id)
            if application is None:
                return {"message": "This application could not be found."}, 404
            if application.type is None:
                return {
                           "message": "Application Type Error! It may be due to an abnormal way of submitting the application."}, 400
            status = status[0].upper() + status[1:].lower()
            if status == "Accepted":
                args = request.json
                if not isinstance(args, list):
                    return {"message": "Invalid input"}, 400
                if args is None:
                    return {"message": "Missing required fields."}, 400
                if len(args) == 0:
                    return {"message": "You must enroll for at least one course."}, 400
                if len(set([a['courseID'] for a in args])) != len(args):
                    return {"message": "You can not enroll for the same course twice or more."}, 400
                for a in args:
                    if 'courseID' not in a:
                        db_session.rollback()
                        return {"message": "You must select courses in all rows."}, 400
                    course = get_course_by_id(a["courseID"])
                    if course is None:
                        db_session.rollback()
                        return {"message": "Course does not exist."}, 404
                    role: RoleInCourse = db_session.query(RoleInCourse).filter(
                        RoleInCourse.Name == application.type.value).one_or_none()
                    if role is None:
                        db_session.rollback()
                        return {"message": "Role does not exist."}, 404
                    if role.Name == "courseCoordinator":
                        db_session.rollback()
                        return {"message": "The application type is courseCoordinator."}, 400
                    courseUserChecker: CourseUser = db_session.query(CourseUser).filter(
                        CourseUser.courseID == a['courseID'],
                        CourseUser.userID == application.studentID,
                        CourseUser.roleID == role.roleID).one_or_none()
                    if courseUserChecker is not None:
                        db_session.rollback()
                        if courseUserChecker.isPublished:
                            return {
                                       "message": f"This student has already been enrolled to {course.courseNum}. Already published."}, 400
                        else:
                            return {
                                       "message": f"This student has already been enrolled to {course.courseNum}. Not published."}, 400
                    courseUser = CourseUser(
                        courseID=a["courseID"],
                        userID=application.studentID,
                        roleID=role.roleID,
                        estimatedHours=a["estimatedHours"] if "estimatedHours" in a else None,
                        ApplicationID=application_id
                    )
                    db_session.add(courseUser)
                application.status = ApplicationStatus.Accepted
                db_session.commit()
                return {"message": "Application Accepted(No Published)"}, 200
            elif status == "Rejected":
                for cu in application.course_users:
                    db_session.delete(cu)
                application.status = ApplicationStatus.Rejected
                db_session.commit()
                return {"message": "Application Rejected"}, 200
            if status not in [a.name for a in ApplicationStatus]:
                return {"message": "Invalid status"}, 400
            application.status = status
            db_session.commit()
            return {"message": "Successful"}, 200
        except Exception as e:
            db_session.rollback()
            return {"message": "Unexpected Error"}, 400

class MultiApplicationStatus_api(Resource):
    @auth.login_required(role=get_permission_group("ApplicationApproval"))
    def put(self):
        """
        update multi application status
        ---
        tags:
          - Application
        parameters:
          - in: body
            name: body
            required: true
            type: array
        responses:
          200:
            schema:
              properties:
                message:
                  type: string
        security:
          - APIKeyHeader: ['Authorization']
        """
        response = request.json
        for r in response:
            application = get_application_by_id(r["applicationID"])
            if application is None:
                db_session.rollback()
                return {"message": "This application could not be found."}, 404
            status = r["status"][0].upper() + r["status"][1:].lower()
            if status not in [a.name for a in ApplicationStatus]:
                db_session.rollback()
                return {"message": "Invalid status"}, 400
            application.status = status
        db_session.commit()
        return {"message": "Successful"}, 200

class GetNumOfApplicationStatus(Resource):
    @auth.login_required(role=get_permission_group("ApplicationApproval"))
    def get(self, term_id, app_type):
        """
        get the number of each application status
        ---
        tags:
          - Application
        parameters:
            - name: term_id
              in: path
              required: true
              schema:
                type: integer
        responses:
          200:
            schema:
              properties:
                numberOfApplication:
                  type: integer
        security:
          - APIKeyHeader: ['Authorization']
        """
        if not exist_termID(term_id):
            return f"termID:{term_id} does not exist", 404
        result = {"accepted": 0, "rejected": 0, "pending": 0, "published": 0}
        for k in result:
            if k == "published":
                res = get_status_application_by_term(term_id, k, True, app_type)
            else:
                res = get_status_application_by_term(term_id, k, False, app_type)
            result[k] = len(res)
        result.update({"unpublished": result["accepted"] + result["rejected"]})
        return result, 200

class GetApplicationByCourseID(Resource):
    @auth.login_required(role=get_permission_group("ApplicationWithCC"))
    def get(self, course_id):
        """
        get the application list by course id (Exclude the application that has been published)
        ---
        tags:
          - Application
        parameters:
            - name: course_id
              in: path
              required: true
              schema:
                type: integer
        responses:
          200:
            schema:
              properties:
                applicationList:
                  type: array
                  items:
                    type: object
                    properties:
                      applicationID:
                        type: integer
                      studentID:
                        type: integer
                      studentName:
                        type: string
                      status:
                        type: string
        security:
          - APIKeyHeader: ['Authorization']
        """
        if get_course_by_id(course_id) is None:
            return f"courseID:{course_id} does not exist", 404
        applications: list[Application] = get_application_by_course_id(course_id)
        return [a.serialize_application_detail() for a in applications], 200

class EndorsedApplicationByCC(Resource):
    @auth.login_required(role=get_permission_group("ApplicationWithCC"))
    def get(self, applicationID, courseID):
        """
        endorsed or cancel endorsed application by course coordinator
        ---
        tags:
          - Application
        parameters:
            - name: applicationID
              in: path
              required: true
              schema:
                type: integer
            - name: courseID
              in: path
              required: true
              schema:
                type: integer
        responses:
          200:
            schema:
              properties:
                message:
                  type: string
        security:
          - APIKeyHeader: ['Authorization']
        """
        application: Application = get_application_by_id(applicationID)
        if application is None:
            return {"message": "This application could not be found."}, 404
        if application.type is None:
            return {
                       "message": "Application Type Error!  It may be due to an abnormal way of submitting the application."}, 400
        if application.status == ApplicationStatus.Unsubmit:
            return {"message": "The application has not been submitted yet."}, 400
        course = get_course_by_id(courseID)
        if course is None:
            return {"message": "This course could not be found."}, 404
        courseUserChecker: CourseUser = db_session.query(CourseUser).join(RoleInCourse).filter(
            CourseUser.courseID == courseID,
            CourseUser.userID == application.studentID,
            RoleInCourse.Name == application.type.name).one_or_none()
        if courseUserChecker is not None:
            if courseUserChecker.isPublished:
                return {
                           "message": f"This student has already been enrolled to {course.courseNum}. Already published."}, 400
        courseApplication: CourseApplication = db_session.query(CourseApplication).filter(
            CourseApplication.ApplicationID == applicationID,
            CourseApplication.courseID == courseID).one_or_none()
        if courseApplication is None:
            return {"message": "The student did not apply for this course"}, 404
        if courseApplication.courseCoordinatorEndorsed:
            courseApplication.courseCoordinatorEndorsed = False
            db_session.commit()
            return {"message": "Cancel Endorsed"}, 200
        courseApplication.courseCoordinatorEndorsed = True
        db_session.commit()
        return {"message": "Endorse Successfully!"}, 200

class PublishApplication(Resource):
    @auth.login_required(role=get_permission_group("ApplicationApproval"))
    def post(self):
        """
         Publish Applications
         ---
         tags:
           - Application
         security:
            - APIKeyHeader: ['Authorization']
        """
        args = request.json
        currentUser: Users = auth.current_user()
        if not isinstance(args, list):
            return {"message": "JSON format error"}, 400
        if len(args) == 0:
            return {"message": "No application selected"}, 400
        for a in args:
            application = get_application_by_id(a)
            if application is None:
                db_session.rollback()
                return {"message": f"ApplicationID:{a} could not be found."}, 404
            if application.isResultPublished:
                db_session.rollback()
                return {"message": f"ApplicationID:{a} has already been published."}, 400
            if application.status not in [ApplicationStatus.Accepted, ApplicationStatus.Rejected]:
                db_session.rollback()
                return {"message": f"The status of ApplicationID:{a} is neither accepted nor rejected"}, 400

        if not current_app.config['CELERY_BROKER_URL'] or not current_app.config['CELERY_BROKER_URL'].strip():
            application_email_status = []
            for a in args:
                application = get_application_by_id(a)
                email_status: Email_Delivery_Status = create_email_sending_status(application.Users.id,
                                                                                EmailCategory.application_result,
                                                                                application.Users.email, currentUser.id,
                                                                                None)
                application_email_status.append((application, email_status))
                if email_status is None or not email_status[0]:
                    return {"message": "Create email sending status error"}, 500

            for a in application_email_status:
                try:
                    courseUsers: List[CourseUser] = a[0].course_users
                    for cu in courseUsers:
                        cu.isPublished = True
                    a[0].isResultPublished = True
                    send_application_result_email(a[0].Users.email, a[0].Users.id, a[0].Users.name,
                                                  a[0].Term.termName, a[0].type, a[0].status)
                    a[1][1].status = EmailStatus.sent
                    db_session.commit()
                except Exception as e:
                    print(e)
                    db_session.rollback()
                    try:
                        a[1][1].status = EmailStatus.failed
                        a[1][1].error_message = str(e)
                        db_session.commit()
                    except Exception as e:
                        db_session.rollback()
                    continue
            return {"message": "Successful"}, 200
        else:
            from MTMS.tasks import send_application_result_email_celery
            task: AsyncResult = send_application_result_email_celery.delay(args, currentUser.id)
            result = task.wait()
            return result[0], result[1]

class ApplicationCV(Resource):
    @auth.login_required
    def get(self, application_id):
        """
        get the application CV
        ---
        tags:
          - Application
        parameters:
            - name: application_id
              in: path
              required: true
              schema:
                type: integer
        responses:
          200:
            schema:
              properties:
                applicationCV:
                  type: string
        security:
          - APIKeyHeader: ['Authorization']
        """
        application: Application = get_application_by_id(application_id)
        if application is None:
            return {"message": "This application could not be found."}, 404
        if application.studentID != auth.current_user().id and not check_user_permission(auth.current_user(), "ApplicationApproval"):
            return {"message": "You are not allowed to access this CV"}, 403

        return {"applicationCV": application.SavedProfile.cv}, 200

class ApplicationTranscript(Resource):
    @auth.login_required
    def get(self, application_id):
        """
        get the application Transcript
        ---
        tags:
          - Application
        parameters:
            - name: application_id
              in: path
              required: true
              schema:
                type: integer
        responses:
          200:
            schema:
              properties:
                applicationTranscript:
                  type: string
        security:
          - APIKeyHeader: ['Authorization']
        """
        application: Application = get_application_by_id(application_id)
        if application is None:
            return {"message": "This application could not be found."}, 404
        if application.studentID != auth.current_user().id and not check_user_permission(auth.current_user(), "ApplicationApproval"):
            return {"message": "You are not allowed to access this CV"}, 403

        return {"applicationTranscript": application.SavedProfile.academicRecord}, 200

def register(app):
    '''
        restful router.
        eg /api/auth/users
    '''
    register_api_blueprints(app, "Application", __name__,
                            [
                                (NewApplication, "/api/newApplication/<int:termID>/<string:app_type>"),
                                (StudentApplicationList, "/api/studentApplicationList/<string:student_id>"),
                                (CurrentStudentApplicationList, "/api/currentStudentApplicationList"),
                                (Application_api, "/api/application/<int:application_id>"),
                                (saveApplication, "/api/saveApplication/<int:application_id>"),
                                (submitApplication, "/api/submitApplication/<int:application_id>"),
                                (ApplicationListByTerm,
                                 "/api/applicationListByTerm/<int:term_id>/<string:status>/<string:app_type>"),
                                (ApplicationApproval, "/api/applicationApproval/<int:application_id>/<string:status>"),
                                (MultiApplicationStatus_api, "/api/multiApplicationStatus"),
                                (GetNumOfApplicationStatus,
                                 "/api/getNumOfApplicationStatus/<int:term_id>/<string:app_type>"),
                                (GetApplicationByCourseID, "/api/getApplicationByCourseID/<int:course_id>"),
                                (EndorsedApplicationByCC,
                                 "/api/endorsedApplicationByCC/<int:applicationID>/<int:courseID>"),
                                (PublishApplication, "/api/publishApplication"),
                                (saveApplication_Files, "/api/saveApplication_Files/<int:application_id>"),
                                (ApplicationCV, "/api/ApplicationCV/<int:application_id>"),
                                (ApplicationTranscript, "/api/ApplicationTranscript/<int:application_id>"),
                            ])


import setuptools

with open("README.txt", "r") as fh:
    long_description = fh.read()

setuptools.setup(
    name="similarweb_scraper",
    version="0.0.3",
    author="Roy Lam",
    author_email="13032765d@connect.polyu.hk",
    description="Using proxycrawl api to scrape similarweb data",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url='https://github.com/wchan757/similarweb_scraper',
    packages=setuptools.find_packages(),
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    python_requires='>=3.6'
    )


"""
Problem Statement : How to replace all missing values with 0 in a numpy array?
"""

# Import numpy as 'np'
import numpy as np

url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
iris_2d = np.genfromtxt(url, delimiter = ',', dtype = 'float', usecols = [0, 1, 2, 3])

# Replace all occurrences of nan with 0 in numpy array
iris_2d[np.isnan(iris_2d)] = 0
print("Replace all occurrences of nan with 0 in array iris_2d:")
print(iris_2d[:4])


from django.shortcuts import render

from django.http import request
from django.http import response
from django.http import HttpResponse
# Create your views here.

from .forms import DaylightHours #NEW
# from .models import User  #NEW

from datetime import datetime
from math import sin , asin , cos , acos ,tan , atan , pi

def day_lenght(L:float ,fun_date=1 , fun_month=1 , fun_year=2023 ):
    """
    L:- lattitude in degrees

    """

    date1 = datetime(day=fun_date , month=fun_month,  year=fun_year)
    date2 = datetime(day= 1 ,month=1 ,year= fun_year)
    day_count = date1 - date2

    P = asin(0.39795*cos(0.2163108 + 2*atan(0.9671396*tan(0.00860*((day_count.days)-186)))))

    num = sin(0.8333*pi/180) + sin(L*pi/180)*sin(P)
    deno = cos(L*pi/180)*cos(P)

    D = 24 - (24/pi)*acos(num/deno)

    return D

def home_view(request):
    return HttpResponse('''This is Home Page .
                            Welcome !!
                            Hemlo World !!!
                        ''')

def home_view_temp(request):
    return render(request,"home.html")

def about_view_temp(request):
    return render(request ,"about.html")

def coffee_view_temp(request):
    return render(request , "coffee.html")

def p2_view_temp(request):
    return render(request , "p2.html")

def sr_view_temp(request):

    if request == "POST":
        fm = DaylightHours(request.POST)
        if fm.is_valid():
            dt = fm.data["date"]
            ltn = fm["latitude_south"]
            print(dt)
            print(ltn)
    else:
        fm = DaylightHours()

    return render(request , "srookie.html" , {'form':fm})

def se_view_temp(request):
    if request == "POST":
        fm = DaylightHours(request.POST)
        if fm.is_valid():
            dt = fm.data["date"]
            ltn = fm["latitude_south"]
            print(dt)
            print(ltn)
    else:
        fm = DaylightHours()

    return render(request , "senthu.html" , {'form':fm})

def result_view_temp(request):
    dt = int(request.POST["date"])
    mt = int(request.POST["month"])
    yt = int(request.POST["year"])
    lt  = float(request.POST["latitude"])

    context = {}
    context["date"] = (dt)
    context["month"] = mt
    context["year"] = yt
    context["latitude"] = float(lt)

    context["result"] = day_lenght(lt , dt ,mt , yt)

    print(context)

    # return HttpResponse(f"The date is {dt}")
    return render(request , "result.html" , context)

from datetime import datetime
from math import sin , asin , cos , acos ,tan , atan , pi

def day_lenght(L:float ,fun_date=1 , fun_month=1 , fun_year=2023 ):
    """
    L:- lattitude in degrees

    """

    date1 = datetime(day=fun_date , month=fun_month,  year=fun_year)
    date2 = datetime(day= 1 ,month=1 ,year= fun_year)
    day_count = date1 - date2

    P = asin(0.39795*cos(0.2163108 + 2*atan(0.9671396*tan(0.00860*((day_count.days)-186)))))

    num = sin(0.8333*pi/180) + sin(L*pi/180)*sin(P)
    deno = cos(L*pi/180)*cos(P)

    D = 24 - (24/pi)*acos(num/deno)

    return D


def chk(x):
    i=2
    f=True
    while i<(x/2):
        if x%i==0:
            f=False
            break
        i+=1
    return f
i=2
while i<100:
    if chk(i):print(i,end=" ")
    i+=1


import time

def main():
    print("\n\nVamos lhe apresentar a tabuada do número desejado!\n\n")

    numero = input("\nDigite o número: ")
    print("\nCalculando...\n")
    time.sleep(1.5)

    for i in range(1, 11):
        print("{} x {} = {}".format(numero, i, int(numero)*i))

def loop():
    print("\n\nVocê deseja consultar novamente?"
          "\n\n1 - SIM"
          "\n2 - NÃO\n")
    choice = int(input(""))
    if choice == 1:
        main()
        loop()
    elif choice == 2:
        print("\n\nCerto, estamos finalizando sua solicitação.\n\n")
    else:
        print("\n\nVocê inseriu uma opção inválida, tente novamente.")
        loop()

main()
loop()

Mundo 02/046 a 056 - Repeticoes(for)/049 - Tabuada 2.0.py


#Aula sobre expressões regulares
import re

patterns = ['term1','term2']
text = 'this is a string with term1. This another with term2'
print(re.search(patterns[0], text))
for pattern in patterns:
    print('searching for "%s" in "%s"' % (pattern,text))
    if re.search(pattern,text):
        print('\n')
        print('match was found')
    else:
        print('\n')
        print('no match was found')

print(match = re.search(pattern[0], text))

test_phrase = "sdsd..sssddd...sdddsddd...dsds...dsssss...sdddd"
test_patterns = ['sd*', #s seguido de zero ou mais d's
                'sd+', #s seguido de um ou mais d's
                'sd?', #s seguido de 3 d's
                'sd{3}', #s seguido de 2 a 3 d's
                'sd{2,3}',
                ]


import sys
from collections import deque
n, m = map(int,sys.stdin.readline().split())
ground = [list(map(int,sys.stdin.readline().split())) for _ in range(n)]
dx = [0,1,0,-1]
dy = [1,0,-1,0]

def melt(visit):

  for i in range(n):
    for j in range(m):
      cnt = 0
      if ground[i][j] != 0:
        visit[i][j] = 1
        for k in range(4):
          cdx = i+dx[k]
          cdy = j+dy[k]
          if 0 <= cdx <n and 0<= cdy < m and ground[cdx][cdy] ==0 and visit[cdx][cdy] == 0:
            cnt+=1
        ground[i][j] = max(0,ground[i][j]-cnt)

def bfs(x,y):
  queue = deque([(x,y)])
  visit[x][y] = 0
  while queue:
    x,y = queue.popleft()
    for i in range(4):
      cdx = x + dx[i]
      cdy = y + dy[i]
      if 0 <=cdx < n and 0<=cdy <m and visit[cdx][cdy] == 1:
        visit[cdx][cdy] = 0
        queue.append((cdx,cdy))

year = 0
while True:
  count = 0
  visit = [[0]*m for _ in range(n)]
  melt(visit)
  year+=1

  for i in range(1,n-1):
    for j in range(1,m-1):
      if visit[i][j] == 1:
        count+=1
        bfs(i,j)

  if count == 0:
    print(0)
    break

  if count >= 2:
    print(year-1)
    break


COLLECTOR = 10000

SUCCESS = 0
FAIL = COLLECTOR
MAX_ERROR_CODE = COLLECTOR +10000

UNCAUGHT_EXCEPTION_ERR = COLLECTOR + 1

AUTHEN_ERROR = COLLECTOR + 100
INVALID_TOKEN_ERR      = AUTHEN_ERROR + 1                # 无效的token
ERR_LOGIN_TIMEOUT      = AUTHEN_ERROR + 2                # 登录超时
USER_UN_LOGIN_ERR      = AUTHEN_ERROR + 3                # 用户未登录
ERR_ACCESS_IDNOTEXIST  = AUTHEN_ERROR + 4                # access_uuid不存在
ERR_NO_TOKEN_PARAM     = AUTHEN_ERROR + 5                # 缺少token
ERR_TIME_MISMATCH      = AUTHEN_ERROR + 6                # 时间偏移超过限制范围

ERR_TIMESTAMP_REPEAT   = AUTHEN_ERROR + 7                # 时间戳重复，调用太频繁或非法调用

PERMISSION_DENIED_ERR  = AUTHEN_ERROR + 8                # 用户权限不够
ERR_UNDEFINED_RING     = AUTHEN_ERROR + 9                # 未定义的ring
NO_SUCH_GROUP_ERR      = AUTHEN_ERROR + 10               # 分组不存在
NO_SUCH_USER_ERR       = AUTHEN_ERROR + 11               # 用户不存在
INVALID_PASSWORD_ERR   = AUTHEN_ERROR + 12               # 密码错误
ERR_ACCESS_AUTHENFAIL  = AUTHEN_ERROR + 13               # 密钥未通过验证

COMMON_ERR = COLLECTOR + 200
FILE_OPERATE_ERR = COMMON_ERR + 1
CALL_REMOTE_METHOD_ERR = COMMON_ERR + 2
REMOTE_SERVICE_ABNORMAL_ERR = COMMON_ERR + 3
SERVER_IS_DISCONNECT_ERR = COMMON_ERR + 4     # 服务器连接断开

ERR_SERVICE_INACTIVE = COMMON_ERR + 5
INTERNAL_OPERATE_ERR = COMMON_ERR + 6
INTERNAL_EXCEPT_ERR = COMMON_ERR + 7
PERMISSION_DENIED_ERR = COMMON_ERR + 8
ERR_METHOD_CONFLICT = COMMON_ERR + 10
RESULT_FORMAT_INVALID = COMMON_ERR + 11
INVALID_PARAM_ERR = COMMON_ERR + 12
INVALID_RESULT_DATA_ERR = COMMON_ERR + 13
INVALID_JSON_DATA_ERR = COMMON_ERR + 14

CONFIG_OUT_OF_LIMIT_ERR = COMMON_ERR + 50                # 用户输入超出配置限制
DATE_FAMAT_IS_INVALID = COMMON_ERR + 51                  # 日期格式不合法
PARAME_IS_INVALID_ERR = COMMON_ERR + 52                  # 参数不合法
CERT_NOT_EXIST_ERR = COMMON_ERR + 53                     # 参数不合法

DB_ERROR               = COLLECTOR + 500
DATABASE_EXCEPT_ERR    = DB_ERROR + 1                    # 数据库异常
NO_SUCH_RECORD_ERR     = DB_ERROR + 3                    # 记录不存在
IDENTITY_KEY_NOT_EXIST_ERR = DB_ERROR + 9                # 记录信息中没有ID
FILTER_IS_INVALID_ERR = DB_ERROR + 10                    # 过滤条件不合法

CONSOLE_ERROR = COLLECTOR + 1000

INVALID_PARAMETERS_ERR = CONSOLE_ERROR + 1               #
NO_IMPLEMENT_INTERFACE_ERR = CONSOLE_ERROR + 2           # 没有实现对应的接口

API_ERR = COLLECTOR + 2000
INVALID_USER_INFO_ERR = API_ERR + 1
USER_EXIST_ALREADY_ERR = API_ERR + 2
INVALID_GROUP_INFO_ERR = API_ERR + 3
GROUP_EXIST_ALREADY_ERR = API_ERR + 4
INVALID_NAMESPACE_INFO_ERR = API_ERR + 5
NAMESPACE_EXIST_ALREADY_ERR = API_ERR + 6
LOG_FILE_NOT_EXIST_ERR = API_ERR + 7
TAG_NOT_EXIST_ERR = API_ERR + 8
TAG_NAME_INVALID_ERR = API_ERR + 9
USER_INFO_INVALID_ERR = API_ERR + 10

INVALID_STORAGE_TYPE_ERR = API_ERR + 11
INVALID_STORAGE_PATH_ERR = API_ERR + 12
ABNORMAL_CALL_ERR = API_ERR + 13
TAG_EXIST_AlREADY_ERR = API_ERR + 14
OLD_PASSWORD_INVALID_ERR = API_ERR + 15
INVALID_CONTENT_TYPE_ERRR = API_ERR + 16
USER_NAMESPACE_NOT_EMPTY_ERR = API_ERR + 17
DELETE_IMAGE_FAIL_ERR = API_ERR + 18
BACKUP_MANIFEST_FAIL_ERR = API_ERR + 19
SAVE_MANIFEST_FILE_FAIL_ERR = API_ERR + 20
MANIFEST_IS_INVALID_ERR = API_ERR + 21
RECYCLE_BIN_NOT_EMPTY_ERR = API_ERR + 22
DOCKER_CLIENT_INIT_FAIL_ERR = API_ERR + 23
DELETE_LAYER_FAIL_ERR = API_ERR + 24
USER_NAME_INVALID_ERR = API_ERR + 25
NAMESPACE_INVALID_ERR = API_ERR + 26

LDAP_SERVER_INFO_INVALID_ERR = API_ERR + 100
TEST_LDAP_AUTH_FAIL_ERR = API_ERR + 101
LDAP_TEST_ACCOUNT_INVALID_ERR = API_ERR + 102
INVALID_AUTH_METHOD_ERR = API_ERR + 103
LDAP_USER_NOT_EXIST_ERR = API_ERR + 104
LDAP_USER_PWD_INVALID_ERR = API_ERR + 105
LDAP_USER_LOGIN_REFUSE_ERR = API_ERR + 106
CAN_NOT_ADD_LOCAL_USER_ERR = API_ERR + 107

CREATE_CRON_SCHEDULE_FAIL = API_ERR + 500
CREATE_INTERVAL_SCHEDULE_FAIL = API_ERR + 501

SEND_EMAIL_FAIL_ERR = API_ERR + 600

REGISTRY_ERR = COLLECTOR + 3000
INVALID_MANIFEST_URL_ERR = REGISTRY_ERR + 1
READ_MANIFEST_FAIL_ERR = REGISTRY_ERR + 2
INVALID_SCHEMA_VERSION_ERR = REGISTRY_ERR + 3
CALL_DOCKER_INTERFACE_FAIL_ERR = REGISTRY_ERR + 4
TAG_IMAGE_FAIL_ERR = REGISTRY_ERR + 5
LOGIN_TO_REGISTRY_FAIL_ERR = REGISTRY_ERR + 6
LAYER_DIGEST_INVALID_ERR = REGISTRY_ERR + 300

UPGRADE_ERR = COLLECTOR + 4000
UPGRADE_FAIL_ERR = UPGRADE_ERR + 1
DO_NOT_NEED_UPGRADE_ERR = UPGRADE_ERR + 2

BACKUP_DATA_FILE_INVALID_ERR = UPGRADE_ERR + 200
BACKUP_DATABASE_FAIL_ERR = UPGRADE_ERR + 201
CLEAR_DATABASE_FAIL_ERR = UPGRADE_ERR + 202
RESTORE_DATABASE_FAIL_ERR = UPGRADE_ERR + 203
UPLOAD_DATA_FILE_FAIL_ERR = UPGRADE_ERR + 204
EXTRACT_DATA_FILE_FAIL_ERR = UPGRADE_ERR + 205
DROP_DATABASE_FAIL_ERR = UPGRADE_ERR + 206
DATABASE_NOT_EXIST_ERR = UPGRADE_ERR + 207

EXTENTION_ERR = COLLECTOR + 5000
EXTENTION_NOT_EXIST_ERR = EXTENTION_ERR + 1
INIT_DOCKER_CLIENT_FAILERR = EXTENTION_ERR + 2
INVALID_EXTENSION_INFO_ERR = EXTENTION_ERR + 3
EXTENSION_ADDRESS_EXIST_ERR = EXTENTION_ERR + 4
PULL_IMAGE_FAIL_ERR = EXTENTION_ERR + 5
PUSH_IMAGE_FAIL_ERR = EXTENTION_ERR + 6
INIT_DEFAULT_EXTENTION_FAIL_ERR = EXTENTION_ERR + 7

WORK_FLOW_ERR = COLLECTOR + 6000
WORK_INFO_INVALID_ERR = WORK_FLOW_ERR + 1
TASK_TIMEOUT_ERR = WORK_FLOW_ERR + 2
TASK_ALREADY_EXIST_ERR = WORK_FLOW_ERR + 3


import matplotlib.pyplot as plt

def plot_component(results, results_train, component, ts_data, ts_idx, cs):
    for j, r in enumerate(results):
        df = r["predictions"][ts_idx]
        plt.plot(ts_data[ts_idx]["future"]["t"], df[component], color=cs[j])
        if results_train is not None:
            df_train = results_train[j]["predictions"][ts_idx]
            plt.plot(ts_data[ts_idx]["history"]["t"],
                     df_train[component].as_matrix(), color=cs[j])
    plt.legend(loc=2, prop={'size': 24})
    plt.show()

def visualize_results(results, results_train, ts_data, ts_idx=0):
    ts = ts_data[ts_idx]
    cs = ['C1', 'C3', 'C5']

    # train and test split
    plt.axvline(x=1., color='k', linestyle='--')

    # True data
    plt.plot(ts["history"]["t"], ts["history"]["y_scaled"].as_matrix(),
             lw=1, color='C2', label='y_true')
    plt.plot(ts["future"]["t"], ts["future"]["y_scaled"].as_matrix(),
             lw=1, color='C2', label='_nolegend_')

    for j, r in enumerate(results):
        df = r["predictions"][ts_idx]
        plt.plot(ts["future"]["t"], df["y"].as_matrix(),
                 label="Model %d" % j, color=cs[j])
        if results_train is not None:
            df_train = results_train[j]["predictions"][ts_idx]
            plt.plot(ts["history"]["t"], df_train["y"],
                     color=cs[j], label='_nolegend_')
    plt.legend(loc=2, prop={'size': 24})
    # plt.legend(handles=[y_true_train,y_true_test])
    plt.show()

    plt.subplot(411)
    plot_component(results, results_train, "trend", ts_data, ts_idx, cs)
    plt.subplot(412)
    plot_component(results, results_train, "yearly", ts_data, ts_idx, cs)
    plt.subplot(413)
    plot_component(results, results_train, "weekly", ts_data, ts_idx, cs)
    plt.subplot(414)

    # Residual
    for j, r in enumerate(results):
        df = r["predictions"][ts_idx]
        plt.plot(ts["future"]["t"],
                 ts["future"]["y_scaled"].as_matrix() - df["y"].as_matrix(),
                 color=cs[j], label='residual')
        if results_train is not None:
            df_train = results_train[j]["predictions"][ts_idx]
            plt.plot(ts["history"]["t"],
                     ts["history"]["y_scaled"].as_matrix() -
                     df_train["y"].as_matrix(), color=cs[j])
    plt.legend(loc=2, prop={'size': 24})
    plt.show()


import tkinter as tk
import tkinter.messagebox as msgbox

class Window(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Hello Tkinter")
        self.label_text = tk.StringVar()
        self.label_text.set("My Name Is: ")

        self.name_text = tk.StringVar()

        self.label = tk.Label(self, textvar=self.label_text)
        self.label.pack(fill=tk.BOTH, expand=1, padx=100, pady=10)

        self.name_entry = tk.Entry(self, textvar=self.name_text)
        self.name_entry.pack(fill=tk.BOTH, expand=1, padx=20, pady=20)

        hello_button = tk.Button(self, text="Say Hello", command=self.say_hello)
        hello_button.pack(side=tk.LEFT, padx=(20, 0), pady=(0, 20))

        goodbye_button = tk.Button(self, text="Say Goodbye", command=self.say_goodbye)
        goodbye_button.pack(side=tk.RIGHT, padx=(0, 20), pady=(0, 20))

    def say_hello(self):
        message = "Hello there " + self.name_entry.get()
        msgbox.showinfo("Hello", message)

    def say_goodbye(self):
        if msgbox.askyesno("Close Window?", "Would you like to close this window?"):
            message = "Window will close in 2 seconds - goodybye " + self.name_entry.get()
            self.label_text.set(message)
            self.after(2000, self.destroy)
        else:
            msgbox.showinfo("Not Closing", "Great! This window will stay open.")

if __name__ == "__main__":
    window = Window()
    window.mainloop()


#coding=utf-8
'''
Created on 2012-3-24

@author: fengclient
'''
from http_parser.pyparser import HttpParser

if __name__ == '__main__':
    rsp = open('d:\\172_response.txt').read()
    # if your are reading a text file from windows, u may need manually convert \n to \r\n
    # universal newline support: http://docs.python.org/library/functions.html#open
    rsp = rsp.replace('\n', '\r\n')
    p = HttpParser()
    p.execute(rsp, len(rsp))
    print(p.get_headers())


from datetime import datetime
from django.http import JsonResponse
from rest_framework.parsers import JSONParser
from django.views.decorators.csrf import csrf_exempt
from rest_framework.decorators import api_view

from restapi.xmodels.index_models import Snippet
from restapi.serializers.index_serializer import SnippetSerializer

from drf_yasg.utils import swagger_auto_schema

@swagger_auto_schema(
    method='get',
    query_serializer=SnippetSerializer,
    responses={200: SnippetSerializer(many=True)},
    tags=['Users'],
)
@api_view(['GET', 'POST'])
@csrf_exempt
def index(request):
    snippets = Snippet(created=datetime.now(),
                       title="title", code="new code", linenos=4,
                       language="en-us", style=False)
    if request.method == 'GET':
        serializer = SnippetSerializer(snippets)
        response = JsonResponse(serializer.data, status=200)
        return response
    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            return JsonResponse(serializer.data, status=200)
        return JsonResponse(serializer.errors, status=400)


#
from typing import List
from tester import Tester

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        length = 1
        for num in nums:
            if num > nums[length - 1]:
                nums[length] = num
                length += 1
                continue

            start = 0
            end = length
            while start < end:
                m = (start + end) // 2
                if nums[m] < num:
                    start = m + 1
                else:
                    end = m

            nums[end] = num
        return length

t = Tester(Solution())

t.test(5, [10, 9, 2, 5, 3, 11, 12, 7, 10, 11])
t.test(4, [10, 9, 2, 5, 3, 7, 101, 18])
t.test(4, [0, 1, 0, 3, 2, 3])
t.test(1, [7, 7, 7, 7, 7, 7, 7])

t.report()


"""
.. include:: ../README.md

.. include:: ../examples/regression_example.md
.. include:: ../examples/calibration_example.md
"""
REGRESSION = 'regression'
CLASSIFICATION = 'classification'

from laplace.baselaplace import BaseLaplace, ParametricLaplace, KronLaplace, DiagLaplace, LowRankLaplace
from laplace.laplace import Laplace

__all__ = ['Laplace',  # direct access to all Laplace classes via unified interface
           'BaseLaplace', 'ParametricLaplace',  # base-class and its (first-level) subclasses
           'KronLaplace', 'DiagLaplace', 'LowRankLaplace',  # all-weights
           ]  # methods


'''
    https://leetcode.com/problems/partition-equal-subset-sum/

    416. Partition Equal Subset Sum

    Given a non-empty array nums containing only positive integers,
    find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.
'''

class Solution:
    def can_partition_helper(self, nums, current_num_index, left_subset, right_subset):
        if len(nums) == (len(left_subset) + len(right_subset)):
            # we partitioned all the numbers into 2 subsets
            return sum(left_subset) == sum(right_subset)
        else:
            # we need to check in which subset to place current_num_index
            right_subset_copy = right_subset.copy()
            left_subset_copy = left_subset.copy()

            right_subset_copy.append(nums[current_num_index])
            left_subset_copy.append(nums[current_num_index])

            # we try adding the current number if the right subset and the left subset and see if it works in either one
            return self.can_partition_helper(nums, current_num_index + 1, left_subset, right_subset_copy) \
                   or self.can_partition_helper(nums, current_num_index + 1, left_subset_copy, right_subset)

    def canPartition(self, nums: [int]) -> bool:
        if len(nums) <= 1:
            # an empty array or array one 1 element can't be partitioned into 2 subsets of equal sum
            return False

        return self.can_partition_helper(nums, 0, [], [])

'''
    Same as above but instead of creating the left and right subsets, we just accumulated their sums
'''

class Solution2:
    def can_partition_helper(self, nums, current_num_index, left_subset_sum, right_subset_sum):
        if current_num_index >= len(nums):
            # our current index is no longer inside the nums array so we already placed all elements in the subsets
            return left_subset_sum == right_subset_sum
        else:
            # we need to check in which subset to place current_num_index
            current_num = nums[current_num_index]

            # we try adding the current number if the right subset and the left subset and see if it works in either one
            return self.can_partition_helper(nums, current_num_index + 1, left_subset_sum, right_subset_sum + current_num) \
                   or self.can_partition_helper(nums, current_num_index + 1, left_subset_sum + current_num, right_subset_sum)

    def canPartition(self, nums: [int]) -> bool:
        if len(nums) <= 1:
            # an empty array or array one 1 element can't be partitioned into 2 subsets of equal sum
            return False

        return self.can_partition_helper(nums, 0, 0, 0)

# class Solution3:
#     def can_partition_helper(self, nums, current_num_index, left_subset_sum, right_subset_sum, subset_target_sum):
#         if current_num_index >= len(nums):
#             # our current index is no longer inside the nums array so we already placed all elements in the subsets
#             # check to see if both our subsets reached the goal
#             return left_subset_sum == subset_target_sum and right_subset_sum == subset_target_sum
#         else:
#             # we need to check in which subset to place current_num_index
#             current_num = nums[current_num_index]
#
#             # we try adding the current number if the right subset and the left subset and see if it works in either one
#             can_add_to_left = False
#             can_add_to_right = False
#
#             # we check if we can add the current element to the left subset
#             if current_num + left_subset_sum <= subset_target_sum:
#                 can_add_to_left = self.can_partition_helper(nums, current_num_index + 1, left_subset_sum + current_num, right_subset_sum, subset_target_sum)
#
#             if current_num + right_subset_sum <= subset_target_sum:
#                 can_add_to_right = self.can_partition_helper(nums, current_num_index + 1, left_subset_sum, right_subset_sum + current_num, subset_target_sum)
#
#             return can_add_to_left or can_add_to_right
#
#     def canPartition(self, nums: [int]) -> bool:
#         if len(nums) <= 1:
#             # an empty array or array one 1 element can't be partitioned into 2 subsets of equal sum
#             return False
#
#         # the target sum of each subset is sum(nums) / 2
#         # if the sum is odd then there's no way to reproduce it with 2 subsets because then we'd have floats
#         nums_sum = sum(nums)
#
#         if nums_sum % 2 == 1:
#             # the sum is odd so there's no way to divide it into 2
#             return False
#         else:
#             subset_target_sum = int(nums_sum / 2)
#
#             return self.can_partition_helper(nums, 0, 0, 0, subset_target_sum)

'''
    Accepted but time limit exceeded
'''

class Solution3:
    def can_partition_helper(self, nums, current_num_index, target_sum):
        if target_sum < 0:
            # the current subset we're creating will never sum up to the target sum
            return False
        elif target_sum == 0:
            return True
        else:
            if current_num_index < len(nums):
                # try adding the current element to the subset
                with_current_element = self.can_partition_helper(nums, current_num_index + 1, target_sum - nums[current_num_index])
                without_current_element = self.can_partition_helper(nums, current_num_index + 1, target_sum)

                return with_current_element or without_current_element
            else:
                return False

    def canPartition(self, nums: [int]) -> bool:
        # in this approach we'll utilize the logic in the commented code regarding the target sum
        if len(nums) <= 1:
            # an empty array or array one 1 element can't be partitioned into 2 subsets of equal sum
            return False

        # the target sum of each subset is sum(nums) / 2
        # if the sum is odd then there's no way to reproduce it with 2 subsets because then we'd have floats
        nums_sum = sum(nums)

        if nums_sum % 2 == 1:
            # the sum is odd so there's no way to divide it into 2
            return False
        else:
            subset_target_sum = int(nums_sum / 2)

            # unlike the other approaches, we won't attempt to create both subsets. All we need is one subset
            # to be equal to half of the total sum; automatically, the remaining elements (belonging to the second subset)
            # will sum up to the other half of the total
            return self.can_partition_helper(nums, 0, subset_target_sum)

'''
    DP with memoization (top-down)
'''

class Solution4:
    def can_partition_helper(self, nums, current_num_index, target_sum, memo):
        if target_sum < 0:
            # the current subset we're creating will never sum up to the target sum
            return False

        if target_sum == 0:
            return True

        if target_sum in memo:
            return memo[target_sum]

        if current_num_index < len(nums):
            # try adding the current element to the subset
            with_current_element = self.can_partition_helper(nums, current_num_index + 1, target_sum - nums[current_num_index], memo)
            without_current_element = self.can_partition_helper(nums, current_num_index + 1, target_sum, memo)

            # can we create a subset with current target sum? Yes or No
            memo[target_sum] = with_current_element or without_current_element

            return with_current_element or without_current_element
        else:
            return False

    def canPartition(self, nums: [int]) -> bool:
        # in this approach we'll utilize the logic in the commented code regarding the target sum
        if len(nums) <= 1:
            # an empty array or array one 1 element can't be partitioned into 2 subsets of equal sum
            return False

        # the target sum of each subset is sum(nums) / 2
        # if the sum is odd then there's no way to reproduce it with 2 subsets because then we'd have floats
        nums_sum = sum(nums)

        if nums_sum % 2 == 1:
            # the sum is odd so there's no way to divide it into 2
            return False
        else:
            subset_target_sum = int(nums_sum / 2)

            memo = {}

            # unlike the other approaches, we won't attempt to create both subsets. All we need is one subset
            # to be equal to half of the total sum; automatically, the remaining elements (belonging to the second subset)
            # will sum up to the other half of the total

            # this function call will help us answer: Can we create a subset with target sum subset_target_sum?
            return self.can_partition_helper(nums, 0, subset_target_sum, memo)

class Solution5:
    # in the DP with memoization solution, we have 2 indices changing with every recursive call: current num index and the target sum
    # we'll use these 2 indices to save computed results in our 2-D DP array
    def canPartition(self, nums: [int]) -> bool:
        # we have the same base cases as Solution 4
        # if len(nums) <= 1:
        #     return False

        nums_sum = sum(nums)

        if nums_sum % 2 == 1:
            # the sum is odd so there's no way to divide it into 2
            return False
        else:
            # the first dimension of dp will be index of current number in nums and the second dimension will be the target sum
            # also, just like solutions 4 and 3, we'll focus on building only one of the subsets

            dp = [[]] * len(nums)

            subset_target_sum = int(nums_sum / 2)

            # since it's bottom AND since in the recursive call of Solution4 we're calling target - num, then we need to solve the problems
            # with less target sum until we reach our ultimate target sum
            # ** according to the solution, we need to start the possible target sums with 0 not 1 **
            possible_target_sums = [i for i in range(0, subset_target_sum + 1)]

            # we initiate dp with every possible index in nums and every possible target sum
            for i in range(0, len(nums)):
                dp[i] = [False] * len(possible_target_sums)
                dp[i][0] = True  # we can always produce 2 sets (empty sets) with sum 0 => BASE CASE

            # in each iteration we need to ask the question: should we consider the current element in our subset or no?
            # to answer the question of whether to consider nums[i], we need to see if we have a solution up to i and that adding i will contribute
            # to the solution

            # it's important that the outer loop loops over the elements while the inner loop loops over the target sums
            # in each loop we're basically checking if the current element contributes to the solution that leads to a subset of current target_length
            for i in range(0, len(nums)):
                for target_sum in possible_target_sums:
                    # to guarantee that target_sum - nums[i] doesn't index something negative, we make sure that nums[i] is less than target sum
                    if nums[i] <= target_sum:
                        # with_current_element = self.can_partition_helper(nums, current_num_index + 1, target_sum - nums[current_num_index], memo)
                        # without_current_element = self.can_partition_helper(nums, current_num_index + 1, target_sum, memo)
                        dp[i][target_sum] = dp[i - 1][target_sum] or dp[i - 1][target_sum - nums[i]]  # first clause is WITHOUT nums[i] and second is WITH nums[i]
                    else:
                        # we can't possibly add this element so the solution to dp[i] is the same as dp[i-1]
                        dp[i][target_sum] = dp[i - 1][target_sum]

            return dp[len(nums) - 1][subset_target_sum]

# nums = [1, 5, 11, 5]
# nums = [1, 2, 3, 5]
nums = [14, 9, 8, 4, 3, 2]

# nums = [1, 2, 5]

print(Solution5().canPartition(nums))