# 2021.01.05 made by Candykick(우수몽)

from flask import request, current_app, jsonify, make_response
from flask_restx import Resource, Api, Namespace, fields
from flask_jwt_extended import *
from sqlalchemy import text
import datetime, json

MyPage = Namespace(
    name='Mypage',
    description="마이페이지 정보를 관리하는 API 모음."
)

@MyPage.route('/user_info')
class User_Info(Resource):
    @jwt_required
    def post(self):
        """현재 유저의 정보를 가져옴."""
        # 0. 토큰값을 가져옴.
        current_user = get_jwt_identity()
        # 1. 필요한 값들을 parameter로 받아옴.
        user_id = request.form['id']

        # 2. 토큰값이 잘못된 경우
        if current_user is None:
            # 3. 401(Unauthorized)를 리턴한다.
            return make_response(jsonify({
                "result": "fail",
                "contents": "잘못된 토큰값입니다."
            }), 401)
        # 2. 토큰값이 정상인 경우
        else:
            # 3. 유저 정보를 가져옴.
            user = current_app.database.execute(text("""
                                                            SELECT * FROM User WHERE id= :user_id ;
                                                        """), {
                'user_id': user_id
            }).fetchone()

            # 4. 유저 정보가 존재하지 않는다면 -> 오류 리턴
            if user is None:
                return [], 204
            # 4. 유저 정보가 존재한다면
            else:
                # 5. is_author 값을 가지고 작가 여부를 체크함.
                d_user = dict(user)

                if d_user['is_author'] == 0:
                    d_user['is_author'] = False
                else:
                    d_user['is_author'] = True

                return jsonify({
                    'id': d_user['id'],
                    'name': d_user['name'],
                    'profile': d_user['profile'],
                    'is_author': d_user['is_author'],
                    'email': d_user['email'],
                    'introduction': d_user['introduction'],
                    'coin': d_user['coin']
                })

@MyPage.route('/my_writing')
class Write_By_Me(Resource):
    @jwt_required
    def post(self):
        """내가 쓴 글 정보를 불러옴. 정렬 순서는 0(최신순), 1(인기순)"""
        # 0. 토큰값을 가져옴.
        current_user = get_jwt_identity()
        # 1. 필요한 값들을 parameter로 받아옴.
        user_id = request.form['id']

        # 2. 토큰값이 잘못된 경우
        if current_user is None:
            # 3. 401(Unauthorized)를 리턴한다.
            return make_response(jsonify({
                "result": "fail",
                "contents": "잘못된 토큰값입니다."
            }), 401)
        # 2. 토큰값이 정상인 경우
        else:
            # 3. 최신순 정렬인 경우 -> id 내림차순 순서대로 쿼리.
            writing_list = current_app.database.execute(text("""
                                                        SELECT * FROM Writing Where author_id = :author_id ORDER BY id DESC;
                                                    """), {
                'author_id': user_id
            }).fetchall()
            # if sort == 0:
            # writing_list = current_app.database.execute(text(
            #                                         SELECT * FROM Writing Where author_id = :author_id ORDER BY id DESC;
            #                                     ), {
            #      'author_id': user_id
            #   }).fetchall()
            # 3. 인기순 정렬인 경우 -> 인기도 내림차순 순서대로 쿼리.
            # (인기도를 구현하지 않은 관계로 일단 최신순 정렬과 동일하게 작동함.)
            # else:
            # writing_list = current_app.database.execute(text(

            # 4. 등록된 글이 있다면 모든 글의 정보를 리턴.
            if len(writing_list) > 0:
                d_writing_list = []
                for writing in writing_list:
                    d_writing = dict(writing)
                    # permission 값을 가지고 업로드 여부를 체크함.
                    # if d_writing['permission']==0:
                    #    d_writing['is_uploaded'] = False
                    # else:
                    #    d_writing['is_uploaded'] = True

                    # if d_writing['is_end']==0:
                    #    d_writing['is_end'] = False
                    # else:
                    #    d_writing['is_end'] = True

                    # series_id -> series_name
                    series_info = current_app.database.execute(text("""
                                                                            SELECT title, is_end FROM Series WHERE id = :series_id;
                                                                        """), {
                        'series_id': d_writing['series_id']
                    }).fetchone()
                    d_writing['series_name'] = series_info['title']
                    if series_info['is_end'] == 1:
                        d_writing['is_end'] = True
                    else:
                        d_writing['is_end'] = False

                    d_writing_list.append(d_writing)

                return jsonify(
                    [{
                        'id': writing['id'],
                        'series_id': writing['series_id'],
                        'series_name': writing['series_name'],
                        'episode_num': writing['episode_num'],
                        'title': writing['title'],
                        'image': writing['image'],
                        'date': writing['date'],
                        'hits': writing['hits'],
                        'comment_num': writing['comment_num'],
                        'permission': writing['permission'],
                        'is_end': writing['is_end'],
                        'author_id': writing['author_id'],
                        'contents': writing['contents']
                    } for writing in d_writing_list]
                )
            # 4. 등록된 작품이 없다면 204(No Content) 리턴.
            else:
                return jsonify([])

@MyPage.route('/subscribe_author')
class Subscribe_List_Author(Resource):
    @jwt_required
    def post(self):
        """현재 유저가 구독하고 있는 모든 작가의 정보를 불러옴."""
        # 0. 토큰값을 가져옴.
        current_user = get_jwt_identity()
        # 1. Subscribe_Author 테이블에서 현재 유저가 구독하고 있는 모든 작가의 id 정보를 가져옴.
        user_id = request.form['id']

        # 2. 토큰값이 잘못된 경우
        if current_user is None:
            # 3. 401(Unauthorized)를 리턴한다.
            return make_response(jsonify({
                "result": "fail",
                "contents": "잘못된 토큰값입니다."
            }), 401)
        # 2. 토큰값이 정상인 경우
        else:
            subscribe_list = current_app.database.execute(text("""
                                    SELECT * FROM Subscribe_Author
                                    WHERE user_id = :user_id ;
                                """), {
                'user_id': user_id
            }).fetchall()

            # 3. 구독한 작가 정보가 있다면 다음으로 진행.
            if len(subscribe_list) > 0:
                # 4. 모든 작가의 id 정보를 OR로 연결한 문자열을 만듬. (3번 작업을 위함)
                author_query_string = ''
                for subscribe in subscribe_list:
                    author_query_string += 'id = '
                    author_query_string += str(subscribe['author_id'])
                    author_query_string += ' OR '

                author_query_string = author_query_string[0:len(author_query_string) - 4]
                author_query_string = 'SELECT * FROM User WHERE ' + author_query_string + ";"

                # 5. Author 테이블에서 모든 작가의 id 정보에 해당하는 작가 정보를 가져옴.
                author_list = current_app.database.execute(text(author_query_string)).fetchall()

                # 6. 구독한 작가 정보를 json array로 리턴함.
                return jsonify(
                    [{
                        'id': author['id'],
                        'name': author['name'],
                        'profile': author['profile'],
                        'series_num': author['series_num'],
                        'recent_update': author['recent_update'],
                        'zzimkkong': author['zzimkkong']
                    } for author in author_list]
                )
            # 구독한 작가 정보가 없다면 204(No Content) 리턴함.
            else:
                return jsonify([])

@MyPage.route('/add_subscribe_author')
class Add_Subscribe_Author(Resource):
    @jwt_required
    def post(self):
        """현재 유저의 작가 구독 목록에 새로운 작가를 추가함."""
        # 토큰값을 가져옴.
        current_user = get_jwt_identity()
        # user_id, author_id를 가져옴.
        user_id = request.form['user_id']
        author_id = request.form['author_id']
        is_subscribe = request.form['is_subscribe']

        # 토큰값이 잘못된 경우
        if current_user is None:
            # 401(Unauthorized)를 리턴한다.
            return make_response(jsonify({
                "result": "fail",
                "contents": "잘못된 토큰값입니다."
            }), 401)
        # 토큰값이 정상인 경우
        else:
            # 구독하는 경우 처리
            if is_subscribe == 'T':
                # 1. 중복된 값을 찾아본다.
                duplication_value = current_app.database.execute(text("""
                        SELECT * FROM Subscribe_Author WHERE user_id=:user_id AND author_id=:author_id;
                    """), {
                    'user_id': user_id,
                    'author_id': author_id
                }).fetchall()

                # 2. 중복된 값이 없다면 등록을 진행하고 success를 리턴한다.
                if len(duplication_value) == 0:
                    current_app.database.execute(text("""
                            INSERT INTO Subscribe_Author (user_id, author_id) VALUES (:user_id, :author_id);
                        """), {
                        'user_id': user_id,
                        'author_id': author_id
                    })

                    # 추가 : 작가 DB의 zzimkkong 값을 하나 증가시킨다.
                    current_app.database.execute(text("""
                                                UPDATE User SET zzimkkong=zzimkkong+1 WHERE id = :author_id ;
                                            """), {
                        'author_id': author_id
                    })

                    return jsonify(
                        {
                            'result': 'success',
                            'contents': 'success'
                        }
                    )
                # 2. 중복된 값이 있다면 오류를 리턴한다.
                else:
                    return make_response(jsonify(
                        {
                            "result": "fail",
                            "contents": "중복된 값이 있습니다."
                        }
                    ), 202)

            # 구독을 취소하는 경우 처리
            else:
                # 1. 값이 있는지 찾아본다.
                duplication_value = current_app.database.execute(text("""
                                        SELECT * FROM Subscribe_Author WHERE user_id=:user_id AND author_id=:author_id;
                                    """), {
                    'user_id': user_id,
                    'author_id': author_id
                }).fetchall()

                # 2. 값이 있다면
                if len(duplication_value) > 0:
                    # 2. 구독 취소 처리를 하고 success 출력.
                    current_app.database.execute(text("""
                                                            DELETE FROM Subscribe_Author WHERE user_id = :user_id AND author_id = :author_id;
                                                        """), {
                        'user_id': user_id,
                        'author_id': author_id
                    })

                    # 추가 : 작가 DB의 zzimkkong 값을 하나 감소시킨다.
                    current_app.database.execute(text("""
                                                                    UPDATE User SET zzimkkong=zzimkkong-1 WHERE id = :author_id ;
                                                                """), {
                        'author_id': author_id
                    })

                    return jsonify(
                        {
                            'result': 'success',
                            'contents': 'success'
                        }
                    )

                # 해당 값이 없다면
                else:
                    return make_response(jsonify(
                        {
                            "result": "fail",
                            "contents": "해당하는 값이 없습니다."
                        }
                    ), 202)

@MyPage.route('/subscribe_series')
class Subscribe_List_Series(Resource):
    @jwt_required
    def post(self):
        """현재 유저가 구독하고 있는 모든 시리즈의 정보를 불러옴."""
        # 0. 토큰값을 가져옴.
        current_user = get_jwt_identity()
        # 1. Subscribe_Series 테이블에서 현재 유저가 구독하고 있는 모든 시리즈의 id 정보를 가져옴.
        user_id = request.form['id']

        # 토큰값이 잘못된 경우
        if current_user is None:
            # 401(Unauthorized)를 리턴한다.
            return make_response(jsonify({
                "result": "fail",
                "contents": "잘못된 토큰값입니다."
            }), 401)
        # 토큰값이 정상인 경우
        else:
            subscribe_list = current_app.database.execute(text("""
                                            SELECT * FROM Subscribe_Series
                                            WHERE user_id = :user_id ;
                                        """), {
                'user_id': user_id
            }).fetchall()

            # 구독한 시리즈 정보가 있다면 다음으로 진행.
            if len(subscribe_list) > 0:
                # 2. 모든 시리즈의 id 정보를 OR로 연결한 문자열을 만듬. (3번 작업을 위함)
                series_query_string = ''
                for subscribe in subscribe_list:
                    series_query_string += 'id = '
                    series_query_string += str(subscribe['series_id'])
                    series_query_string += ' OR '

                series_query_string = series_query_string[0:len(series_query_string) - 4]
                series_query_string = 'SELECT * FROM Series WHERE ' + series_query_string + ";"

                # 3. Series 테이블에서 모든 시리즈의 id 정보에 해당하는 시리즈 정보를 가져옴.
                series_list = current_app.database.execute(text(series_query_string)).fetchall()

                d_series_list = []
                for series in series_list:
                    d_series = dict(series)
                    if d_series['is_end'] == 1:
                        d_series['is_end'] = True
                    else:
                        d_series['is_end'] = False

                    d_series_list.append(d_series)

                # 4. 구독한 시리즈 정보를 json array로 리턴함.
                return jsonify(
                    [{
                        'id': series['id'],
                        'author_id': series['author_id'],
                        'author_name': series['author_name'],
                        'title': series['title'],
                        'image': series['image'],
                        'date': series['date'],
                        'introduction': series['introduction'],
                        'hash_tag': series['hash_tag'],
                        'hits': series['hits'],
                        'comment_num': series['comment_num'],
                        'recent_update': series['recent_update'],
                        'zzimkkong': series['zzimkkong'],
                        'is_end': series['is_end'],
                        'writing_num': series['writing_num']
                    } for series in d_series_list]
                )
            # 구독한 시리즈 정보가 없다면 204(No Content) 리턴함.
            else:
                return jsonify([])

@MyPage.route('/add_subscribe_series')
class Add_Subscribe_Series(Resource):
    @jwt_required
    def post(self):
        """현재 유저의 시리즈 구독 목록에 새로운 시리즈를 추가함."""
        # 토큰값을 가져옴.
        current_user = get_jwt_identity()
        # user_id, series_id를 가져옴.
        user_id = request.form['user_id']
        series_id = request.form['series_id']
        is_subscribe = request.form['is_subscribe']

        # 토큰값이 잘못된 경우
        if current_user is None:
            # 401(Unauthorized)를 리턴한다.
            return make_response(jsonify({
                "result": "fail",
                "contents": "잘못된 토큰값입니다."
            }), 401)
        # 토큰값이 정상인 경우
        else:
            # 구독 처리를 하는 경우
            if is_subscribe == 'T':
                # 1. 중복된 값을 찾아본다.
                duplication_value = current_app.database.execute(text("""
                        SELECT * FROM Subscribe_Series WHERE user_id=:user_id AND series_id=:series_id;
                    """), {
                    'user_id': user_id,
                    'series_id': series_id
                }).fetchall()

                # 2. 중복된 값이 없다면 등록을 진행하고 success를 리턴한다.
                if len(duplication_value) == 0:
                    current_app.database.execute(text("""
                            INSERT INTO Subscribe_Series (user_id, series_id) VALUES (:user_id, :series_id);
                        """), {
                        'user_id': user_id,
                        'series_id': series_id
                    })

                    # 추가 : 시리즈 DB의 zzimkkong 값을 하나 증가시킨다.
                    current_app.database.execute(text("""
                                                    UPDATE Series SET zzimkkong=zzimkkong+1 WHERE id = :series_id ;
                                                """), {
                        'series_id': series_id
                    })

                    return jsonify(
                        {
                            'result': 'success',
                            'contents': 'success'
                        }
                    )
                # 2. 중복된 값이 있다면 오류를 리턴한다.
                else:
                    return make_response(jsonify(
                        {
                            "result": "fail",
                            "contents": "중복된 값이 있습니다."
                        }
                    ), 202)

            # 구독 취소를 하는 경우
            else:
                # 1. 해당 값이 있는지 찾아본다.
                duplication_value = current_app.database.execute(text("""
                                        SELECT * FROM Subscribe_Series WHERE user_id=:user_id AND series_id=:series_id;
                                    """), {
                    'user_id': user_id,
                    'series_id': series_id
                }).fetchall()

                # 2. 해당 값이 있다면
                if len(duplication_value) > 0:
                    # 2. 구독 취소 처리를 하고 success 출력.
                    current_app.database.execute(text("""
                                                            DELETE FROM Subscribe_Series WHERE user_id = :user_id AND series_id = :series_id;
                                                        """), {
                        'user_id': user_id,
                        'series_id': series_id
                    })

                    # 추가 : 시리즈 DB의 zzimkkong 값을 하나 감소시킨다.
                    current_app.database.execute(text("""
                                                        UPDATE Series SET zzimkkong=zzimkkong-1 WHERE id = :series_id ;
                                                    """), {
                        'series_id': series_id
                    })

                    return jsonify(
                        {
                            'result': 'success',
                            'contents': 'success'
                        }
                    )

                # 해당 값이 없다면
                else:
                    return make_response(jsonify(
                        result="fail",
                        contents="해당하는 값이 없습니다."
                    ), 202)

@MyPage.route('/update_introduction')
class User_Intro(Resource):
    @jwt_required
    def post(self):
        """현재 유저의 정보를 가져옴."""
        # 0. 토큰값을 가져옴.
        current_user = get_jwt_identity()
        # 1. 필요한 값들을 parameter로 받아옴.
        user_id = request.form['id']
        intro = request.form['introduction']
        user_name = request.form['name']

        # 2. 토큰값이 잘못된 경우
        if current_user is None:
            # 3. 401(Unauthorized)를 리턴한다.
            return make_response(jsonify({
                "result": "fail",
                "contents": "잘못된 토큰값입니다."
            }), 401)
        # 2. 토큰값이 정상인 경우
        else:
            # 3. 유저 정보를 가져옴.
            current_app.database.execute(text("""
                                                            UPDATE User SET introduction = :intro, name = :name WHERE id = :user_id ;
                                                        """), {
                'user_id': user_id,
                'intro': intro,
                'name': user_name
            })

            return jsonify(
                {
                    'result': 'success',
                    'contents': 'success'
                }
            )

@MyPage.route('/my_series')
class Series_By_Me(Resource):
    @jwt_required
    def post(self):
        """내가 쓴 시리즈 정보를 불러옴."""
        # 0. 토큰값을 가져옴.
        current_user = get_jwt_identity()
        # 1. 필요한 값들을 parameter로 받아옴.
        user_id = request.form['id']

        # 2. 토큰값이 잘못된 경우
        if current_user is None:
            # 3. 401(Unauthorized)를 리턴한다.
            return jsonify({
                "result": "fail",
                "contents": "잘못된 토큰값입니다."
            }), 401
        # 2. 토큰값이 정상인 경우
        else:
            # 3. 최신순 정렬인 경우 -> id 내림차순 순서대로 쿼리.
            writing_list = current_app.database.execute(text("""
                                                        SELECT * FROM Series Where author_id = :author_id ORDER BY id DESC;
                                                    """), {
                'author_id': user_id
            }).fetchall()

            d_series_list = []
            for series in writing_list:
                d_series = dict(series)
                if d_series['is_end'] == 1:
                    d_series['is_end'] = True
                else:
                    d_series['is_end'] = False

                d_series_list.append(d_series)

            if len(writing_list) > 0:
                return jsonify(
                    [{
                        'id': series['id'],
                        'author_id': series['author_id'],
                        'author_name': series['author_name'],
                        'title': series['title'],
                        'image': series['image'],
                        'date': series['date'],
                        'introduction': series['introduction'],
                        'hash_tag': series['hash_tag'],
                        'recent_update': series['recent_update'],
                        'comment_num': series['comment_num'],
                        'hits': series['hits'],
                        'zzimkkong': series['zzimkkong'],
                        'is_end': series['is_end'],
                        'writing_num': series['writing_num']
                    } for series in writing_list]
                )
            else:
                return jsonify([])

@MyPage.route('/finish_series')
class Finish_Series(Resource):
    @jwt_required
    def post(self):
        """시리즈를 완결시킴."""
        # 토큰값을 가져옴.
        current_user = get_jwt_identity()
        # user_id, series_id를 가져옴.
        user_id = request.form['id']
        series_id = request.form['series_id']
        is_ended = request.form['is_end']

        # 토큰값이 잘못된 경우
        if current_user is None:
            # 401(Unauthorized)를 리턴한다.
            return jsonify({
                "result": "fail",
                "contents": "잘못된 토큰값입니다."
            }), 401
        # 토큰값이 정상인 경우
        else:
            # 완결인 경우
            if is_ended == 'T':
                current_app.database.execute(text("""
                            UPDATE Series SET is_end = 1 WHERE id = :series_id ;
                        """), {
                    'series_id': series_id
                })
            elif is_ended == 'F':
                current_app.database.execute(text("""
                            UPDATE Series SET is_end = 0 WHERE id = :series_id ;
                        """), {
                    'series_id': series_id
                })

            return jsonify(
                {
                    'result': 'success',
                    'contents': 'success'
                }
            )

def json_default(value):
    if isinstance(value, datetime.date):
        return value.strftime('%y.%m.%d');
    raise TypeError('not JSON serializable')
data = {'date': datetime.date.today()}
json_data = json.dumps(data, default=json_default)


# --------------------------------------------------------------------------
# Name:        Unique Paths III
# Author(s):   Phu Tran
# --------------------------------------------------------------------------
"""
    On a 2-dimensional grid, there are 4 types of squares:
      1. 1 represents the starting square.  There is exactly one starting square.
      2. 2 represents the ending square.  There is exactly one ending square.
      3. 0 represents empty squares we can walk over.
      4. -1 represents obstacles that we cannot walk over.
      5. Return the number of 4-directional walks from the starting square to the ending square, that walk over every non-obstacle square exactly once.

    Example 1:
      Input: [[1,0,0,0],[0,0,0,0],[0,0,2,-1]]
      Output: 2
      Explanation: We have the following two paths:
        1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2)
        2. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2)

    Example 2:
      Input: [[1,0,0,0],[0,0,0,0],[0,0,0,2]]
      Output: 4
      Explanation: We have the following four paths:
        1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2),(2,3)
        2. (0,0),(0,1),(1,1),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,3),(1,3),(2,3)
        3. (0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(1,1),(0,1),(0,2),(0,3),(1,3),(2,3)
        4. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2),(2,3)

    Example 3:
      Input: [[0,1],[2,0]]
      Output: 0
      Explanation:
        There is no path that walks over every empty square exactly once.
        Note that the starting and ending square can be anywhere in the grid.

    Note:
      1. 1 <= grid.length * grid[0].length <= 20
"""

class Solution(object):
    def uniquePathsIII(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        self.ans, empty = 0, 0
        m, n = len(grid[0]), len(grid)
        def dfs(x, y, rest):
            if x < 0 or x >= n or y < 0 or y >= m or  grid[x][y] < 0: return
            if grid[x][y] == 2 and rest == 0:
                self.ans += 1

            neibs = [[0,1],[0,-1],[1,0],[-1,0]]
            for dx, dy in neibs:
                save = grid[x][y]
                grid[x][y] = -2
                dfs(x+dx, y+dy, rest - 1)
                grid[x][y] = save

        for i,j in product(range(n), range(m)):
            if grid[i][j] == 1: (sx, sy) = (i,j)
            empty += (grid[i][j] != -1)

        dfs(sx, sy, empty-1)
        return self.ans

'''other faster methods (from other submissions)
##################################################
def uniquePathsIII(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        rows, cols = len(grid), len(grid[0])
        directions = [(1, 0), (0, 1), (-1, 0), (0, -1)]
        start_cell, end_cell = None, None
        empty_cells = {} # the set of neighbors' positions for each cell

        # collect empty cells
        for i in range(rows):
            for j in range(cols):
                if grid[i][j] in [0, 1]:
                    empty_cells[(i, j)] = set()
                    if grid[i][j] == 1: start_cell = (i, j)
                elif grid[i][j] == 2: end_cell = (i, j)

        # collect their neighbors
        for x, y in empty_cells:
            for xch, ych in directions:
                neighbor = (x + xch, y + ych)
                if neighbor in empty_cells: empty_cells[(x, y)].add(neighbor)

        next_to_finish = set((end_cell[0] + xch, end_cell[1] + ych) for xch, ych in directions)
        visited=set([start_cell])
        def count_routes(cell=start_cell):
            left_to_visit = len(empty_cells) - len(visited)
            if not left_to_visit: return int(cell in next_to_finish)
            routes_to_end = 0
            for neighbor in (empty_cells[cell] - visited):
                visited.add(neighbor)
                routes_to_end += count_routes(neighbor)
                visited.remove(neighbor)
            return routes_to_end

        return count_routes()
##################################################
def uniquePathsIII(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        A = grid
        self.res = 0
        m, n, empty = len(A), len(A[0]), 1
        for i in range(m):
            for j in range(n):
                if A[i][j] == 1:
                    x, y = (i, j)
                elif A[i][j] == 0:
                    empty += 1

        def dfs(x, y, empty):
            if not (0 <= x < m and 0 <= y < n and A[x][y] >= 0): return
            if A[x][y] == 2:
                self.res += empty == 0
                return
            A[x][y] = -2
            dfs(x + 1, y, empty - 1)
            dfs(x - 1, y, empty - 1)
            dfs(x, y + 1, empty - 1)
            dfs(x, y - 1, empty - 1)
            A[x][y] = 0
        dfs(x, y, empty)
        return self.res
##################################################
def uniquePathsIII(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        self.cnt = 0
        r, c, left = len(grid), len(grid[0]), 1
        for i in range(r):
            for j in range(c):
                if grid[i][j] == 1:
                    x, y = i, j
                elif grid[i][j] == 0:
                    left += 1
        def dfs(i, j, left):
            if not (0 <= i < r): return
            if not (0 <= j < c): return
            if grid[i][j] < 0: return
            if grid[i][j] == 2:
                if left == 0:
                    self.cnt += 1
                return
            grid[i][j] = -2
            dfs(i-1, j, left-1)
            dfs(i+1, j, left-1)
            dfs(i, j-1, left-1)
            dfs(i, j+1, left-1)
            grid[i][j] = 0
            return
        dfs(x, y, left)
        return self.cnt
##################################################
'''


import sys
from io import StringIO
from typing import TextIO, Optional

from awesome_lottery.lottery import Lottery, Winner

class ResultsWriter:
    lottery: Lottery
    output: TextIO

    def __init__(self, lottery: Lottery, output: Optional[StringIO] = None):
        self.lottery = lottery
        if output:
            self.output = output
        else:
            self.output = sys.stdout

    def display_results_in_console(self):
        results = 'Winners of lottery:\n'
        for winner in self.lottery.winners:
            results += str(winner) + '\n'
        results += 'Congratulations for winners!'
        print(results, file=self.output)

    def save_output_to_file(self, output: str):
        with open(output, 'w') as f:
            f.write(Winner.schema().dumps(self.lottery.winners, many=True))

    def dump(self, output: str):
        if output:
            self.save_output_to_file(output)
        else:
            self.display_results_in_console()


from copy import deepcopy
from typing import Dict, List, Optional, Union

import pbcoin.config as conf
import pbcoin.core as core
from pbcoin.block import Block
from pbcoin.blockchain import BlockChain
from pbcoin.logger import getLogger
from pbcoin.mempool import Mempool
from pbcoin.network import Node
from pbcoin.trx import Coin, Trx
from pbcoin.wallet import Wallet

logging = getLogger(__name__)

class Mine:
    """ The class to mine blocks

    Attributes
    ----------
        start_over: bool = False
            Determines mining stops or start over again from the new last block.
        mined_new: bool = False
            Determines a new block was mined and it should be added to blockchain and
            declare to other nodes.
        stop_mining: bool = False
            Determines mining stops until it becomes False.
        reset_nonce: bool = False
            It changes the nonce value to 0 and then starts to continue mining.
        blockchain: BlockChain | List[Block]
            A Blockchain object or a List of blocks. Recommend Blockchain object because
            List of Blocks just uses in unittest.
        wallet: Wallet
            A Wallet object for save your balance and stuff.
        mempool: Mempool
            A Mempool object that is a list of transactions should has been mined
        node: Optional[Node]
            A Node object for declare other nodes
    """
    def __init__(
        self,
        blockchain: Union[BlockChain, List[Block]],
        wallet: Optional[Wallet],
        mempool: Mempool,
        node: Optional[Node] = None,
    ):
        """Initializes object attribute"""
        self.blockchain = blockchain
        self.node = node
        self.mempool = mempool
        self.wallet = wallet
        self.reset()

    def reset(self):
        """Reset mine attributes for start again mining for next block"""
        self.start_over = False
        self.mined_new = False
        self.stop_mining = False
        self.reset_nonce = False

    async def mine(
        self,
        setup_block: Optional[Block] = None,
        unspent_coins: Optional[Dict[str, List[Coin]]] = None,  # almost just for unittest
        add_block = True,
        difficulty: Optional[int] = None,  # almost just for unittest
        send_network: Optional[bool] = None  # almost just for unittest
    ) -> None:
        """(async) Start mining for a new block

        This method first setup a new block if has not been provided for it
        then start mining until finds a nonce that the block hash is less than difficulty.
        Even when the blockchain update gets out of the loop.

        After a block has been mined then added to the blockchain if
        the parameter add_block is True and then send to other nodes from
        the network if send_network is True.

        Args
        ----
        setup_block: Optional[Block] = None
            The specific block to find its nonce less than difficulty.
            If it's passed None, then get from `blockchain.setup_new_block()`
        unspent_coins: Optional[Dict[str, List[Coin]]] = None
            The coins that have not been spent yet. It's used to check
            the validation block (transactions) and update that when
            the block is added to the blockchain..
            If it's passed None, it gets that from `core.py` file.
        add_block: bool = True
            If True then the mined block will be added to the blockchain.
        difficulty: Optional[int] = None
            The difficulty in which the block hash should be less (equal) than.
            The difficulty should be an unsigned int and less than 2^256.
            If it's passed None, it gets that from configs.
        send_network: Optional[bool] = None
            If it is True, then after mining, The mined block will be sent
            to other nodes from the networks.
            If it's passed None, it gets that from configs.

        Return
        ------
        nothing
        """
        if difficulty is None:
            difficulty = conf.settings.glob.difficulty
        if send_network is None:
            send_network = conf.settings.glob.network
        if unspent_coins is None:
            unspent_coins = core.ALL_OUTPUTS
        # setup block
        if setup_block is not None:
            self.setup_block = setup_block
        elif type(self.blockchain) is BlockChain:
            # TODO: get wallet object not using direct from core.py
            subsidy = Trx(self.blockchain.height, core.WALLET.public_key)
            self.setup_block = self.blockchain.setup_new_block(
                mempool=self.mempool, subsidy=subsidy)
        else:
            raise Exception("Mine needs to setup block")
        # reset mine parameters
        self.reset()
        # get mining transaction to check later for added new transaction
        transactions_mining = deepcopy(self.setup_block.transactions)
        if self.setup_block.has_subsidy:
            transactions_mining.pop(0)  # pop subsidy
        if isinstance(self.blockchain, List):
            last_n = len(self.blockchain)
        else:
            last_n = self.blockchain.height
        logging.debug("start again mine")
        while not self.check_add_block(last_n):
            if self.start_over:
                break
            if self.reset_nonce:
                self.setup_block.set_nonce(0)
            if self.stop_mining:
                continue
            # check for new transaction has been added
            if transactions_mining != self.mempool == 0:
                for trx in self.mempool:
                    if trx not in transactions_mining:
                        self.setup_block.add_trx(trx)
                transactions_mining = self.setup_block.transactions
                transactions_mining.pop(0)  # pop subsidy
            self.setup_block.set_mined()
            # calculate hash and check difficulty
            if int(self.setup_block.calculate_hash(), 16) <= difficulty:
                if self.start_over:
                    break
                self.mined_new = True
                break
            # set new nonce
            self.setup_block.set_nonce(self.setup_block.nonce+1)
        if self.mined_new:
            logging.info("A Block was mined")
            logging.debug(f"minded block info: {self.setup_block.get_data(True, False)}")
            if add_block and type(self.blockchain) is BlockChain:
                # add new blocks to other
                self.blockchain.add_new_block(self.setup_block,
                                              ignore_validation=True,
                                              unspent_coins=unspent_coins,
                                              difficulty=difficulty)
            # if blockchian in just a List type
            elif add_block:
                self.blockchain.append(self.setup_block)
                logging.debug("New blockchain: ",
                              [block.__hash__ for block in self.blockchain])
            if send_network and self.node is not None:
                # send mine block to other node
                # TODO: Check result
                await self.node.send_mined_block(self.setup_block)
                logging.debug(f"New blockchain: {self.blockchain.get_hashes()}")
            # remove mined transactions
            self.mempool.remove_transactions(self.setup_block.hash_list_trx)
            # TODO: returns the result of mining

    def check_add_block(self, last_n: int):
        """check the blockchain height with last_n
        to figure out blockchain is update or not
        """
        if isinstance(self.blockchain, List):
            height = len(self.blockchain)
        else:
            height = self.blockchain.height
        return height > last_n


# coding: utf-8
""" Tasks do celery para a aplicação `journalmanager`.
"""
import os
import io
import datetime
import operator
from copy import deepcopy

from lxml import isoschematron, etree
from django.db.models import Q
from django.db import IntegrityError, transaction, DatabaseError
from django.core.exceptions import ObjectDoesNotExist
from django.core.files.base import ContentFile
from celery.utils.log import get_task_logger
from django.templatetags.static import static
import packtools
from PIL import Image

from scielomanager.celery import app
from scielomanager import connectors
from . import models

logger = get_task_logger(__name__)

BASIC_ARTICLE_META_PATH = os.path.join(
        os.path.dirname(os.path.abspath(__file__)), 'basic_article_meta.sch')

# instâncias de isoschematron.Schematron não são thread-safe
ARTICLE_META_SCHEMATRON = isoschematron.Schematron(file=BASIC_ARTICLE_META_PATH)

elasticsearch_client = connectors.ArticleElasticsearch()

def _gen_es_struct_from_article(article):
    """Retorna `article` em estrutura de dados esperada pelo Elasticsearch.
    """
    paths = article.XPaths

    values_to_struct_mapping = [
        ['abbrev_journal_title', paths.ABBREV_JOURNAL_TITLE],
        ['epub', paths.ISSN_EPUB], ['ppub', paths.ISSN_PPUB],
        ['volume', paths.VOLUME], ['issue', paths.ISSUE],
        ['year', paths.YEAR], ['doi', paths.DOI],
        ['pid', paths.PID], ['head_subject', paths.HEAD_SUBJECT],
        ['article_type', paths.ARTICLE_TYPE],
    ]

    es_struct = {attr: article.get_value(expr)
                 for attr, expr in values_to_struct_mapping}

    article_as_octets = str(article.xml)

    try:
        links_to = [{'aid': rel.link_to.aid, 'type': rel.link_type}
                    for rel in article.links_to.all()]
    except ObjectDoesNotExist:
        links_to = []

    try:
        referrers = [{'aid': rel.referrer.aid, 'type': rel.link_type}
                     for rel in article.referrers.all()]
    except ObjectDoesNotExist:
        referrers = []

    partial_struct = {
        'version': article.xml_version,
        'is_aop': article.is_aop,
        'source': article_as_octets,
        'aid': article.aid,
        'timestamp': datetime.datetime.now(),
        'links_to': links_to,
        'referrers': referrers,
    }

    es_struct.update(partial_struct)

    return es_struct

@app.task(ignore_result=True)
def submit_to_elasticsearch(article_pk):
    """ Submete a instância de `journalmanager.models.Article` para indexação
    do Elasticsearch.
    """
    try:
        article = models.Article.objects.get(pk=article_pk,
                control_attributes__es_is_dirty=True)
    except models.Article.DoesNotExist:
        logger.error('Cannot find or Article is not ready to be submitted to '
                     'elasticsearch. Pk: %s. Skipping.', article_pk)
        return None

    struct = _gen_es_struct_from_article(article)
    elasticsearch_client.add(article.aid, struct)

    logger.info('Article "%s" was indexed successfully.', article.domain_key)

    article_ctrl_attrs = article.control_attributes
    article_ctrl_attrs.es_updated_at = datetime.datetime.now()
    article_ctrl_attrs.es_is_dirty = False
    article_ctrl_attrs.save()

@app.task(ignore_result=True)
def link_article_to_journal(article_pk):
    """ Tenta associar o artigo ao seu periódico.
    """
    try:
        article = models.Article.objects.get(pk=article_pk, journal=None)
    except models.Article.DoesNotExist:
        logger.info('Cannot find unlinked Article with pk "%s". '
                    'Skipping the linking task.', article_pk)
        return None

    try:
        query_params = []
        if article.issn_ppub:
            query_params.append(Q(print_issn=article.issn_ppub))

        if article.issn_epub:
            query_params.append(Q(eletronic_issn=article.issn_epub))

        if not query_params:
            logger.error('Missing attributes issn_ppub and issn_epub in '
                         'Article wit pk "%s".', article_pk)
            return None

        query_expr = reduce(operator.or_, query_params)
        journal = models.Journal.objects.get(query_expr)

    except models.Journal.DoesNotExist:
        # Pode ser que os ISSNs do XML estejam invertidos...
        try:
            query_params = []
            if article.issn_ppub:
                query_params.append(Q(eletronic_issn=article.issn_ppub))

            if article.issn_epub:
                query_params.append(Q(print_issn=article.issn_epub))

            if not query_params:
                logger.error('Missing attributes issn_ppub and issn_epub in '
                             'Article with pk "%s".', article_pk)
                return None

            query_expr = reduce(operator.or_, query_params)
            journal = models.Journal.objects.get(query_expr)

        except models.Journal.DoesNotExist:
            logger.info('Cannot find parent Journal for Article with pk "%s". '
                        'Skipping the linking task.', article_pk)
            return None

    article.journal = journal
    article.save()

    logger.info('Article "%s" is now linked to journal "%s".',
                article.domain_key, journal.title)

    link_article_to_issue.delay(article_pk)

@app.task(ignore_result=True)
def link_article_to_issue(article_pk):
    """ Tenta associar o artigo ao seu número.

    Por hora não são suportados artigos de suplementos e fascículos especiais.
    https://github.com/scieloorg/scielo-manager/issues/1248
    """
    try:
        article = models.Article.objects.get(pk=article_pk, issue=None, is_aop=False)
    except models.Article.DoesNotExist:
        logger.info('Cannot find unlinked Article with pk "%s". '
                    'Skipping the linking task.', article_pk)
        return None

    if article.journal is None:
        logger.info('Cannot link Article to issue without having a journal. '
                    'Article pk "%s". Skipping the linking task.', article_pk)
    else:
        volume = article.get_value(article.XPaths.VOLUME)
        issue = article.get_value(article.XPaths.ISSUE)
        year = article.get_value(article.XPaths.YEAR)

        try:
            issue = article.journal.issue_set.get(
                    volume=volume, number=issue, publication_year=year)
        except (models.Issue.DoesNotExist, models.Issue.MultipleObjectsReturned):
            logger.info('Cannot find Issue for Article with pk "%s". '
                        'Skipping the linking task.', article_pk)
        else:
            article.issue = issue
            article.save()

            logger.info('Article "%s" is now linked to issue "%s".',
                    article.domain_key, issue.label)

    return None

@app.task(ignore_result=True)
def process_orphan_articles():
    """ Tenta associar os artigos órfãos com periódicos e fascículos.
    """
    orphans = models.Article.objects.only('pk', 'journal').filter(issue=None)

    for orphan in orphans:
        if orphan.journal is None:
            link_article_to_journal.delay(orphan.pk)
        else:
            link_article_to_issue.delay(orphan.pk)

@app.task(ignore_result=True)
def process_dirty_articles():
    """ Task (periódica) que garanta a indexação dos artigos sujos.
    """
    dirties = models.Article.objects.only('pk').filter(
            control_attributes__es_is_dirty=True)

    for dirty in dirties:
        submit_to_elasticsearch.delay(dirty.pk)

@app.task(throws=(IntegrityError, ValueError))
def create_article_from_string(xml_string, overwrite_if_exists=False):
    """ Cria uma instância de `journalmanager.models.Article`.

    Pode levantar `django.db.IntegrityError` no caso de artigos duplicados,
    TypeError no caso de argumento com tipo diferente de unicode ou
    ValueError no caso de artigos cujos elementos identificadores não estão
    presentes.

    :param xml_string: String de texto unicode.
    :param overwrite_if_exists: (opcional) valor booleano indicando se o artigo
                                deve ser substituído caso já exista.
    :return: aid (article-id) formado por uma string de 32 bytes.
    """
    if not isinstance(xml_string, unicode):
        raise TypeError('Only unicode strings are accepted')

    xml_bstring = xml_string.encode('utf-8')

    try:
        parsed_xml = etree.parse(io.BytesIO(xml_bstring))

    except etree.XMLSyntaxError as exc:
        raise ValueError(u"Syntax error: %s.", exc.message)

    metadata_sch = deepcopy(ARTICLE_META_SCHEMATRON)
    if not metadata_sch.validate(parsed_xml):
        logger.debug('Schematron validation error log: %s.', metadata_sch.error_log)
        raise ValueError('Missing identification elements')

    new_article = models.Article.parse(xml_bstring)

    with transaction.commit_manually():
        sid = transaction.savepoint()
        try:
            new_article.save()

        except IntegrityError:
            transaction.savepoint_rollback(sid)

            if overwrite_if_exists:
                try:
                    old_article = models.Article.objects.only('pk', 'aid').get(
                            domain_key=new_article.domain_key)

                    old_article.control_attributes.delete()
                    old_article.related_articles.clear()

                    new_article.pk = old_article.pk
                    new_article.aid = old_article.aid
                    new_article.save()

                except DatabaseError:
                    transaction.savepoint_rollback(sid)

                else:
                    transaction.savepoint_commit(sid)

            else:
                raise

        else:
            transaction.savepoint_commit(sid)

        transaction.commit()

    logger.info('New Article added with aid: %s.', new_article.aid)

    return new_article.aid

@app.task(ignore_result=True)
def mark_articles_as_dirty():
    """ Marca todos os artigos para serem reindexados.
    """
    with transaction.commit_on_success():
        total_rows = models.ArticleControlAttributes.objects.all().update(es_is_dirty=True)

    logger.info('%s Articles were set as dirty to Elasticsearch.', total_rows)

@app.task(ignore_result=True)
def link_article_with_their_related(article_pk):
    """ Tenta associar artigos relacionados.

    Essa função é idempotente, e pode ser executada inúmeras vezes até
    que todas as referências de um artigo sejam estabelecidas.

    Caso alguma exceção seja levantada, nenhuma mudança será persistida na
    base de dados.
    """
    try:
        referrer = models.Article.objects.get(pk=article_pk,
                control_attributes__articles_linkage_is_pending=True)
    except models.Article.DoesNotExist:
        logger.info('Cannot find Article with with pk: %s. Skipping the task.',
                article_pk)
        return None

    related_article_elements = referrer.xml.xpath(
            models.Article.XPaths.RELATED_CORRECTED_ARTICLES)

    doi_type_pairs = ([elem.attrib['{http://www.w3.org/1999/xlink}href'],
                      elem.attrib['related-article-type']]
                      for elem in related_article_elements)

    def _ensure_articles_are_linked(doi, rel_type):
        try:
            target = models.Article.objects.get(doi=doi)
        except (models.Article.DoesNotExist, models.Article.MultipleObjectsReturned) as exc:
            logger.error('Cannot get article with DOI "%s". The error message is: "%s"',
                    doi, exc.message)
            return False

        _, created = models.ArticlesLinkage.objects.get_or_create(
                referrer=referrer, link_to=target, link_type=rel_type)

        logger.info('Article with pk %s is %s linked to %s',
                'now' if created else 'already', target.pk, referrer.pk)

        return True

    with transaction.commit_on_success():
        link_statuses = (_ensure_articles_are_linked(doi, rel_type)
                         for doi, rel_type in doi_type_pairs)

        if all(link_statuses):
            referrer.control_attributes.articles_linkage_is_pending = False
            referrer.control_attributes.save()

@app.task(ignore_result=True)
def process_related_articles():
    """ Tenta associar artigos relacionados.
    """
    articles = models.Article.objects.only('pk').filter(
            control_attributes__articles_linkage_is_pending=True)

    for article in articles:
        link_article_with_their_related.delay(article.pk)

@app.task(throws=(ValueError,))
def create_articleasset_from_bytes(aid, filename, content, owner=None,
        use_license=None):
    """Cria uma instância de `journalmanager.models.ArticleAsset`.

    :param aid: ``article-id`` formado por uma string de 32 bytes.
    :param filename: string de texto contendo o nome do arquivo do ativo digital.
    :param content: string de bytes com o conteúdo do ativo digital.
    :param owner: (opcional) string de texto informando o detentor do copyright.
    :param use_license: (opcional): string de texto informando a licença de uso.
    """
    try:
        article = models.Article.objects.get(aid=aid)

    except models.Article.DoesNotExist:
        raise ValueError('Cannot find Article with aid: %s' % aid)

    _owner = owner or u''
    _use_license = use_license or u''

    # create and save the asset
    asset = models.ArticleAsset(article=article, owner=_owner,
            use_license=_use_license)
    asset.file.save(filename, ContentFile(content))

    # create a preferred alternative for the asset
    create_preferred_image_file.delay(asset.pk)

    logger.info('New ArticleAsset %s added to Article with aid: %s.',
            repr(asset), aid)

    return asset.file.url

@app.task(throws=(ValueError,))
def create_article_html_renditions(article_pk, css_url=None, valid_only=False):
    """Cria os documentos HTML para cada idioma do artigo.

    :param css_url: (opcional) URL da CSS a ser relacionada no documento HTML.
    :param valid_only: (opcional) produz o HTML apenas para XMLs válidos.
    """
    try:
        article = models.Article.objects.get(pk=article_pk)

    except models.Article.DoesNotExist:
        raise ValueError('Cannot find Article with pk: %s' % article_pk)

    css_url = css_url or static('css/htmlgenerator/styles.css')

    files_urls = []
    for lang, html in packtools.HTMLGenerator.parse(article.xml.root_etree,
            valid_only=valid_only, css=css_url):
        rendition, _ = models.ArticleHTMLRendition.objects.get_or_create(
                article=article, lang=lang)
        rendition.build_version = packtools.__version__

        content = etree.tostring(html, encoding='utf-8', method='html',
                doctype=u'<!DOCTYPE html>')

        filename = ''.join([article.aid, u'-', lang, u'.html'])
        rendition.file.save(filename, ContentFile(content))

        files_urls.append(rendition.file.url)

        logger.info('ArticleHTMLRendition in lang "%s" added to Article with '
                    'aid: %s.', lang, article.aid)

    return files_urls

def convert_image_to_jpeg(filepath, mode=None, **kwargs):
    """Converte a imagem no caminho `filepath` para o formato JPEG.

    Retorna um buffer com o conteúdo convertido da imagem. Pode levantar
    `ValueError` caso `filepath` não seja reconhecido como uma imagem.

    :param **kwargs: (opcional) argumentos nomeados serão repassados para a
    função `Image.save`, com exceção de `format` que foi pré-definido.
    """
    output_buffer = io.BytesIO()
    _ = kwargs.pop('format', None)

    original = Image.open(filepath)
    if mode:
        _image = original.convert(mode=mode)
    else:
        _image = original

    _image.save(output_buffer, format='jpeg', **kwargs)

    return output_buffer

@app.task(throws=(ValueError, TypeError,))
def create_preferred_image_file(asset_pk):
    """Cria uma versão alternativa de `ArticleAsset.file`, preferida para o
    manuseio.

    Por hora a versão preferida é o JPEG ao invés de TIFF. Para os demais
    formatos não serão geradas outras versões.

    :param asset_pk: chave primária da instância de `ArticleAsset`.
    """
    try:
        asset = models.ArticleAsset.objects.get(pk=asset_pk)

    except models.ArticleAsset.DoesNotExist:
        raise ValueError('Cannot find ArticleAsset with pk: %s' % asset_pk)

    try:
        filepath = asset.file.path
    except ValueError as exc:
        logger.exception(exc)
        logger.error('Cannot create a preferred alt file for %s. Skipping.',
                     filepath)
        raise

    if not asset.is_image():
        logger.error('Cannot create a preferred alt file for %s. Skipping.',
                     filepath)
        raise ValueError('Cannot create preferred alternatives for files '
                        'other than images.')

    _file = Image.open(asset.file.path)
    if _file.format.lower() != 'tiff':
        raise ValueError('Image is already in a preferred format.')

    try:
        # Levanta IOError caso `filepath` não seja um arquivo de imagem.
        jpeg_buffer = convert_image_to_jpeg(filepath)
    except IOError as exc:
        logger.exception(exc)
        logger.error('Cannot create a preferred alt file for %s. Skipping.',
                     filepath)
        raise

    _, filename = os.path.split(filepath)
    filename_head, _ = os.path.splitext(filename)

    jpeg_filename = filename_head + '.jpeg'

    asset.preferred_alt_file.save(jpeg_filename,
                                  ContentFile(jpeg_buffer.getvalue()))

    logger.info('Finished creating an alternative file for %s.',
            repr(asset))

    return asset.preferred_alt_file.url


T = int(input())
for tc in range(1, T+1):
    str_list = input()
    word = ""
    for i in str_list:
        word += i
        length = len(word)
        if word == str_list[length:length+length]:
            break

    print(f"#{tc} {len(word)}")

문제 모음/추천 문제/[SWEA] 패턴 마디의 길이.py


import re

def getProductName(src_string):
    # Regex pattern to extract the portion between common patterns
    pattern = r'instock_m_q\d+_(.*?)_\d+_530x.progressive.jpg'

    # Use regex to extract the desired portion
    match = re.search(pattern, src_string)

    if match:
        extracted_text = match.group(1)
        return extracted_text
    else:
        return src_string


import numpy as np
import cv2
import os
from matplotlib import pyplot as plt
from imutils import resize
from filelist import FileList  # import the filelist class
import logging
import argparse

# # for getting focal length from the photos database
from issimage import ISSIMAGE

# filename = r'C:\Users\lrmayer\Documents\Mayer\MyOpenCV_Code\Smallcoins.jpg'
# filename = r'F:\imagews\training\Earthlimb_ISS005-E-8536.jpg'  # perfect for learning
#filename = r'F:\imagews\training\Earthlimb_ISS009-E-23413.jpg'
#filename = r'F:\imagews\training\Earthlimb_ISS009-E-28843.jpg'
# filename = r'F:\imagews\training\Land_ISS009-E-22319.jpg'  # dark part is window, not limb
# filename = r'F:\imagews\training\Night_ISS028-E-26603.jpg'

# python find_limb_part2.py E:\BetterLightning --log=debug --list="E:\BetterLightning\lightning.txt"
# python find_limb_part2.py F:\imagews\training --log=debug --list="F:\imagews\training\earthlimb.txt"
# python find_limb_part2.py F:\imagews\training --log=debug --list="F:\imagews\training\various.txt"
# python find_limb_part2.py F:\imagews\training --log=debug --list="F:\imagews\training\aurora.txt"
# python find_limb_part2.py F:\imagews\training --log=debug --list="F:\imagews\training\list.txt"

# -------------------------------------------
# Thresholding for finding where the limb is
# -------------------------------------------
doThreshold = False
threshold = 50

# -------------------------------------------------
# Get rid of small bright features (stars,  noise)
# -------------------------------------------------
doOpening = True

# -------------------------------------------------
# Get rid of small bright features (stars,  noise)
# -------------------------------------------------
doClosing = True

# -----------------------------------------------------
# Construct the argument parse and parse the arguments
# -----------------------------------------------------
ap = argparse.ArgumentParser()
ap = argparse.ArgumentParser(description="Find earth limb in images")
ap.add_argument("dataset", help="Path to images")  # this is required

# Add logging file argument
ap.add_argument("-l", "--log", help="Logging Value", default="info")

# Add list file argument
ap.add_argument("-li", "--list", help="List of images to read")

# parse the arguments
args = ap.parse_args()

# -------------------------------------------------------------
# Set up logging file
# -------------------------------------------------------------
# loglevel is bound to the string value obtained from the
# command line argument. Convert to upper case to allow the user to
# specify --log=DEBUG or --log=debug
# -------------------------------------------------------------
numeric_level = getattr(logging, args.log.upper(), None)
if not isinstance(numeric_level, int):
    raise ValueError('Invalid log level: %s' % loglevel)
logging.basicConfig(level=numeric_level, filename='find_limb_part2.log', filemode='w')

# -------------------------------------
# Get the list of image names to read
# -------------------------------------
theList = FileList(args.dataset,  args.list)
imagePaths = theList.getPathFilenames()

logging.info("imagePath is %s", imagePaths)

# --------------------------------------
# Loop over the input dataset of images
# --------------------------------------
for filename in imagePaths:

    if args.list :
        if not os.path.isfile(filename) :
               sys.exit("filename " + filename + "  does not exist")

        # Read image
        img = cv2.imread(filename)

        # resize the image
        # def resize(image, width = None, height = None, inter = cv2.INTER_AREA)
        resized = resize(img, width=500)

        # initialize ISSIMAGE class for setting up the image
        xx = ISSIMAGE(filename)

        xx.resize()

        xx.show()

        # ----------------------
        # Get the sun elevation
        # ----------------------
        sunElev = xx.get_sun_elev()
        logging.debug("Sun elevation = %s", sunElev)

        # ----------------------
        # Get the focal length
        # ----------------------
        focalLength = xx.get_focal_length()
        logging.debug("Focal length = %s", focalLength)

        # -----------------
        # convert to gray
        # -----------------
        gray = cv2.cvtColor(resized,cv2.COLOR_BGR2GRAY)

        # Do histogram equalization ??? (only needed for night time limb)
        # equalize the histogram of the image
        eq = cv2.equalizeHist(gray)
        cv2.imshow("histogram of open/close", eq)
        #closed = eq.copy()

        # ------------------------------------------------------
        # Smooth the image
        # nn = the neighborhood size
        # For finding the limb, want the image to be very smooth
        # -------------------------------------------------------
        nn = 11  # Get less edges w/ higher n
        #nn = 5
        #gray = cv2.GaussianBlur(gray, (nn, nn), 0)

        # -----------------------------------------------
        # Bilateral filtering preserves the edges
        # 2nd parameter is the window size for smoothing
        # -----------------------------------------------
        #filtered = cv2.bilateralFilter(gray,9,75,75)
        filtered = cv2.bilateralFilter(gray,19,75,75)

        cv2.imshow("bilateral filter", filtered)

        # ==================================================================
        # Do an opening to get rid of more noise (the small bright areas)
        # ==================================================================
        if (doOpening):
            #kernel = np.ones((3, 3), np.uint8)
            kernel = np.ones((9, 9), np.uint8)
            #kernel = np.ones((19, 19), np.uint8)  # to big for night images
            opened = cv2.morphologyEx(filtered, cv2.MORPH_OPEN, kernel)
        else:
            opened = filtered.copy()

        # ---------------------------------------
        # Do closing to get of small dark spots
        # ---------------------------------------
        if (doClosing):
            #kernel = np.ones((3, 3), np.uint8)
            #kernel = np.ones((9, 9), np.uint8)
            #kernel = np.ones((19, 19), np.uint8)
            closed = cv2.morphologyEx(opened, cv2.MORPH_CLOSE, kernel)
        else:
            closed = opened.copy()

        cv2.imshow("opened/closed", closed)

        # ---------------------------------------------------------------------------------------------
        # Show the gradient image (for testing whether this is a viable method for getting night limb)
        # ---------------------------------------------------------------------------------------------
        kernel = np.ones((3, 3), np.uint8)
        gradient = cv2.morphologyEx(closed, cv2.MORPH_GRADIENT, kernel)
        cv2.imshow("gradient of opened/closed", gradient)

        # ---------------------
        # Threshold the image
        # ---------------------
        if (doThreshold) :

            if (sunElev > 10) :
                # image needs to be 1D for threshold function
                # Simple, binary thresholding
                #ret, thresh = cv2.threshold(opened, 100, 255, cv2.THRESH_BINARY)  # not bad, but may be too high
                # pretty good, but not good w/ very bright limb images : Earthlimb_ISS009-E-16194.jpg - Earthlimb_ISS009-E-16199.jpg
                # May need to figure out peaks of bimodal color histogram on the gray image or something like that

                logging.debug("threshold = %s", threshold)
                ret, thresh = cv2.threshold(closed, threshold, 255, cv2.THRESH_BINARY)

            elif (sunElev < -30. and sunElev > -70.) :

                #threshold = 75  # to get the bright limb  # too low
                #threshold = 100  # to get the bright limb # too high
                #threshold = 90  # too high
                #threshold = 85 # too high
                threshold = 80
                #logging.debug("threshold = %s", threshold)
                # this global thresholding not really working b/c of variable lighting
                #ret, thresh = cv2.threshold(closed, threshold, 255, cv2.THRESH_BINARY)

                # adaptive gaussian thresholding
                logging.debug("Adaptive Gaussian threshold")
                nn = 11  # not good - too many, lots of them dots
                nn = 5  # better, but only gets the really big contrasts
                nn = 3  # same as above
                #nn = 9  # not sure why there is a bunch of dot contours above limb
                #thresh = cv2.adaptiveThreshold(closed, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, nn, 2)
                thresh = cv2.adaptiveThreshold(closed, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, nn, 10)
                #  This only gets the egdes that are very big in contrast (very dark next to very light)
                #thresh = cv2.adaptiveThreshold(closed, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, nn, -10)
                #ret, thresh = cv2.threshold(closed, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)\

            else:

                logging.debug("binary + otsu threshold")
                ret, thresh = cv2.threshold(closed,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

        else:
            thresh = opened.copy()   # gives too many contours

        #cv2.imshow("threshold", thresh)

        # ---------------------------------------------------------------
        # compute edges on the smoothed image
        # any gradient values below 30 are considered
        # non-edges whereas any values above 150 are considered
        # sure edges.
        # ----------------------------------------------------------------
        edgeThresh1 = 30
        edgeThresh2 = 50  # use for thresholded day limb

        # too many contours when sun elev is high (by night standards)
        # no contours in very low sun elev : -76
        edgeThresh1 = 50
        edgeThresh2 = 100  # alot of contours, but maybe its ok, as long as it gets the limb

        #edgeThresh2 = 200  # not enough
        #edgeThresh2 = 150  # not enough
        #edgeThresh2 = 150  # good for city lights

        edged = cv2.Canny(thresh, edgeThresh1, edgeThresh2)  # use for day limb

        # run the canny edge detection on smoothed image
        #                image, threshold1, threshold2
        # Any gradient value larger than threshold2 is considered
        # to be an edge. Any value below threshold1 is considered
        # not to be an edge. Values in between threshold1
        # and threshold2 are either classified as edges or non-edges
        # based on how their intensities are 'connected'
        # threshold1 = 30
        # threshold2 = 150
        # canny = cv2.Canny(image, threshold1, threshold2)
        # cv2.imshow("Canny 1", canny)
        # cv2.waitKey(0)

        cv2.imshow("Edges", edged)

        # find the contours of the edges. cnts is the returned contours
        # This method returns
        # a 3-tuple of: (1) our image after applying contour detection
        # (which is modified and essentially destroyed), (2) the
        # contours themselves, cnts, and (3) the hierarchy of the contours
        # (OpenCV 3.0 is different from OpenCV 2.4, which returns a tuple of 2)
        # This function is destructive
        # to the image you pass in. If you intend using that image
        # later on in your code, it's best to make a copy of it, using
        # the NumPy copy method.
        # The second argument is the type of contours we want.
        # We use cv2.RETR_EXTERNAL to retrieve only the outermost
        # contours (i.e., the contours that follow the outline of the
        # coin). We can also pass in cv2.RETR_LIST to grab all contours.
        # Other methods include hierarchical contours using
        # cv2.RETR_COMP and cv2.RETR_TREE, but hierarchical contours
        # are outside the scope of this book.
        # of resources.
        # Our contours cnts is simply a Python list. We can use
        # the len function on it to count the number of contours that
        # were returned.
        (_, cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
            cv2.CHAIN_APPROX_SIMPLE)

        # draw the contours in green on the coins image
        # contour index of -1 means draw all contours.  If just
        # wanted to draw ith contour, just enter i
        # The last argument is the thickness of the line we
        # are drawing. We'll draw the contour with a thickness of
        # two pixels.
        # cv2.drawContours(resized, cnts, -1, (0, 255, 0), 2)
        # cv2.imshow("contours", resized)

        # See :
        # http://docs.opencv.org/3.1.0/dd/d49/tutorial_py_contour_features.html
        # for information about contour characterics (used below, like getting bounding rectangle)

        green = (0, 255, 0)
        red = (0, 0, 255)
        yellow = (0, 255, 255)

        # write number of contours on the image
        numContours = len(cnts)

        # loop over contours
        for (i, c) in enumerate(cnts):

            #logging.info("\nContour #%s",format(c))
            logging.info("\nContour #%s", c)

            # -------------------------------------------------------------------------------------------
            # Straight Bounding Rectangle :
            # A straight rectangle, it
            # doesn't consider the rotation of the object. So area of the bounding rectangle won't be
            # minimum. It is found by the function sget the bounding rectangle
            # -------------------------------------------------------------------------------------------
            (x, y, w, h) = cv2.boundingRect(c)
            #print("x,y,w,h : {} {} {} {} ".format(x,y,w,h))

            # get the aspect ratio
            aspect_ratio = float(w) / float(h)
            #print("aspect_ratio : {}".format(aspect_ratio))
            logging.info("aspect ratio : %12.5f",aspect_ratio)

            # get extent : Extent is the ratio of contour area to bounding rectangle area.
            # extent = object area/bounding rectangle area
            area = cv2.contourArea(c)  # get the contour area
            logging.info("contour area : %12.5f", area)
            rect_area = w * h
            extent = float(area) / rect_area
            logging.info("extent : %12.5f", extent)

            # ------------------------------------------------------------------------------------
            # Get the minimum closing circle that completely covers the object with minimum area.
            # ------------------------------------------------------------------------------------
            (x, y), radius = cv2.minEnclosingCircle(cnts[i])
            center = (int(x), int(y))
            radius = int(radius)
            #cv2.circle(resized, center, radius, (0, 255, 255), 2)
            logging.info("x,y, radius of min circle : %s %s %12.3f", x, y, radius)

            # Rotated Rectangle :
            # The bounding rectangle is drawn
            # with minimum area, so it considers the rotation also.  The function used is cv2.minAreaRect().
            # It returns a Box2D structure which contains following detals - ( center (x, y), (width, height),
            # angle of rotation ).But to draw this rectangle, we need 4 corners of the rectangle.
            # It is obtained by the function cv2.boxPoints()

            rect = cv2.minAreaRect(cnts[i])
            box = cv2.boxPoints(rect)
            box = np.int0(box)
            cv2.drawContours(resized, [box], 0, (0, 255, 255), 2)

            if  (area < 1.) :
            #if (area == 0.):
                cv2.drawContours(resized, cnts, i, red, 2)
            else:
                cv2.drawContours(resized, cnts, i, green, 2)

            logging.info("-----------------------")

        cv2.putText(resized, str(numContours) + ' contours', (20, 60),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)

        cv2.imshow("contours", resized)
        cv2.waitKey(0)

# # noise removal
# kernel = np.ones((3,3),np.uint8)
# opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
#
# # sure background area
# sure_bg = cv2.dilate(opening,kernel,iterations=3)
#
# # Finding sure foreground area
# dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
# ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
#
# # Finding unknown region
# sure_fg = np.uint8(sure_fg)
# unknown = cv2.subtract(sure_bg,sure_fg)
#
# # Marker labelling
# ret, markers = cv2.connectedComponents(sure_fg)
#
# # Add one to all labels so that sure background is not 0, but 1
# markers = markers+1
#
# # Now, mark the region of unknown with zero
# markers[unknown==255] = 0
#
# #markers = cv2.watershed(img,markers)
# #img[markers == -1] = [255,0,0]
#
# markers = cv2.watershed(resized,markers)
# resized[markers == -1] = [255,0,0]

#cv2.imshow("watershed", resized)
#cv2.waitKey(0)


import requests
from bs4 import BeautifulSoup
import json
import sys
import refGen

def getTitlesPrices(url):
    #setting headers so we dont get refused
    userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36"
    acceptLanguage = 'en-US,en;q=0.5'
    dnt = '1'
    accept = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
    referer = refGen.getRef()#random refere

    #defining headers
    headers = {'user-agent':userAgent,'accept':accept,'referer':referer,'accept-language':acceptLanguage,'dnt':dnt}

    #initilizing requests
    r = requests.get(url, headers=headers)

    #getting the json
    soup = BeautifulSoup(r.text, 'lxml')
    s = str(soup.find('script', {'id': 'searchContent'}))

    #striping unnecesary javascript
    s = s.strip('<script id="searchContent" type="application/json"></script>')
    s = s.strip('"</script>"')

    #try loading json and catch exxeption if not loading
    try:
        j = json.loads(s)
    except:
        print (s)
        sys.exit()

    #going into json to find what we neeed
    x = j['searchContent']['preso']['items']

    #making 2 blank lists
    titles = []
    prices = []
    ids = []

    #going in an getting needed data and adding to list
    for i in x:
        try:
            price = i['primaryOffer']['offerPrice']
            title = i['title'].replace("<mark>","").replace("</mark>","")
            id = i['productId']
            titles.append(title)
            prices.append(price)
            ids.append(id)
        except:
            pass

    return titles, prices ,ids

if __name__ == "__main__":
    print(getTitlesPrices(r"https://walmart.com/search?query=Mr.%20Coffee%20Steam%20Espresso%20Cappuccino%20and%20Latte%20Maker,%20One%20Size,%20Black"))