from typing import Optional, Dict, Any, Union, List

from phidata.asset.table.sql.postgres import PostgresTable
from phidata.workflow import Workflow
from phidata.task import TaskArgs, task
from phidata.utils.log import logger

from data.products.tiingo.nasdaq_100 import nasdaq_100_tickers
from data.products.tiingo.sp_500 import sp_500_tickers
from workspace.config import dev_db, pg_db_connection_id

##############################################################################
## This file defines a workflow that downloads daily stock prices from tiingo
## The workflow is part of the tiingo data product
##############################################################################

# Step 1: Define a postgres table named `daily_prices`.
prices_table = PostgresTable(
    name="daily_prices",
    db_conn_id=pg_db_connection_id,
    db_conn_url=dev_db.get_db_connection_url_local(),
)

# Step 2: Build a workflow that loads the prices_table
# 2.1: Define typed inputs for our workflow
#   Create a class that inherits from TaskArgs
#   and contains the inputs for our task as class variables
class LoadPricesArgs(TaskArgs):
    # The table to load
    prices_table: PostgresTable = prices_table
    # Tickers to get prices for, as a string or list of strings
    tickers: Union[str, List[str]] = ["AAPL", "GOOG"]
    # start_date: Start of price download range in YYYY-MM-DD format.
    start_date: Optional[str] = None
    # end_date: End of price download range in YYYY-MM-DD format.
    end_date: Optional[str] = None
    # Rows to cache before writing to db
    cache_size: int = 5000
    frequency: str = "daily"
    # Tiingo Api key should be provided using the TIINGO_API_KEY
    # env variable. But for local testing, we can pass the api_key here if needed
    api_key: Optional[str] = None
    # If True, will drop table before loading data, thereby rewriting the table
    drop_table_before_load: bool = False

# 2.2: Write a task to drop daily data.
@task
def drop_prices(**kwargs) -> bool:
    """
    This task drops daily data before loading, so we dont have duplicates
    """
    args = LoadPricesArgs.from_kwargs(kwargs)
    run_date = args.run_date

    # drop_table_before_load
    if args.drop_table_before_load:
        logger.info(f"Dropping table: {args.prices_table.name}")
        args.prices_table.delete()
    # or drop rows for current date so we dont have duplicates
    else:
        logger.info(f"Dropping data for: {run_date}")
        args.prices_table.run_sql_query(
            f"DELETE FROM {args.prices_table.name} WHERE ds = '{run_date}'"
        )
    return True

# 2.3: Instantiate the task
drop = drop_prices()

# 2.4: Write a task to load daily prices
@task
def load_ticker_prices(**kwargs) -> bool:

    import pandas as pd
    from tiingo import TiingoClient

    # Get inputs as typed arguments by creating LoadPricesArgs from kwargs
    args: LoadPricesArgs = LoadPricesArgs.from_kwargs(kwargs)
    # logger.info(f"args: {args}")

    run_date = args.run_date
    if args.run_date is None:
        logger.error("Invalid run_date")
        return False
    logger.info(f"Loading {args.prices_table.name} for {run_date}")

    # Build tiingo_config
    tiingo_config: Dict[str, Any] = {
        "session": True,
    }
    # Set TIINGO_API_KEY if provided
    if args.api_key is not None:
        tiingo_config["api_key"] = args.api_key

    # Build TiingoClient
    tiingo_client: TiingoClient = TiingoClient(tiingo_config)

    # List of tickers to read prices for
    tickers_list: List[str] = []

    # If a ticker is provided as a str, convert to a list
    if isinstance(args.tickers, str):
        tickers_list = [args.tickers]
    elif isinstance(args.tickers, list):
        tickers_list = args.tickers

    # Get ticker prices and load prices_table
    prices_df: pd.DataFrame = pd.DataFrame()
    for ticker in tickers_list:
        logger.info(f"Getting prices for {ticker}")
        try:
            single_ticker_price = tiingo_client.get_dataframe(
                tickers=ticker,
                startDate=args.start_date,
                endDate=args.end_date,
                frequency=args.frequency,
            )
            single_ticker_price["ticker"] = ticker
        except Exception as e:
            logger.warning(
                f"Received error with {ticker}: {e}. Continuing to next ticker"
            )
            continue

        prices_df = pd.concat([prices_df, single_ticker_price])

        # write to table if rows_in_df > cache_size
        rows_in_df = prices_df.shape[0]
        if rows_in_df >= args.cache_size:
            prices_df["ds"] = run_date
            prices_df.reset_index(drop=True, inplace=True)
            prices_df.set_index(["ds", "ticker"], inplace=True)
            write_success = args.prices_table.write_pandas_df(
                prices_df, if_exists="append"
            )
            if write_success:
                # only clear existing df if write is successful
                prices_df = pd.DataFrame()

    # write final set of rows
    prices_df["ds"] = run_date
    prices_df.reset_index(drop=True, inplace=True)
    prices_df.set_index(["ds", "ticker"], inplace=True)

    # logger.info("Sample data:")
    # logger.info(prices_df[:5])
    return args.prices_table.write_pandas_df(prices_df, if_exists="append")

# 2.5: Instantiate the task to load daily nasdaq prices
# Because we are reusing the load_ticker_prices task, we need to provide
#   a unique name so airflow can create separate tasks for each
# Run this:
#   Locally: `phi wf run tiingo:prices:nasdaq`
#   In a Dev databox: `phi wf run tiingo:prices:nasdaq -e dev`
#   In a Prd databox: `phi wf run tiingo:prices:nasdaq -e prd`
load_nasdaq = load_ticker_prices(
    name="load_nasdaq",
    tickers=nasdaq_100_tickers,
)

# 2.6: Instantiate the task to load daily s&p 500 prices
# Run this:
#   Locally: `phi wf run tiingo:prices:sp500`
#   In a Dev databox: `phi wf run tiingo:prices:sp500 -e dev`
#   In a Prd databox: `phi wf run tiingo:prices:sp500 -e prd`
load_sp500 = load_ticker_prices(
    name="load_sp500",
    tickers=sp_500_tickers,
)

# 2.6: Create a Workflow object and add the tasks
prices = Workflow(
    name="prices",
    tasks=[drop, load_nasdaq, load_sp500],
    graph={
        load_nasdaq: [drop],
        load_sp500: [drop],
    },
    outputs=[prices_table],
)


# Script to compile all english URL-based articles into a pandas dataframe with all language texts included.

import requests
import json
import sys
import wikipedia
import subprocess
import json
import pandas as pd

languagelist = ['en', 'zh', 'es', 'de', 'ru', 'ja']
#From translate.py ec3bd
#given a link to a wikipedia article,
#will return the text content of that article in a dictionary keyed by its page title
def fetch_article(link):
	ind = link.index("/wiki/")
	title = link[ind+6:]
	print(title)
	wikipedia.set_lang("en")
	page = wikipedia.page(title)
	content = page.content
	#print(len(content))
	#Divide at earliest point to cut down on unnecessary characters
	try:
		content = content[:content.index("== See also ==")]
	except ValueError:
		try:
			content = content[:content.index("== References ==")]
		except ValueError:
			print("no references might be other language")

	#print("Num characters in article: "+ str(len(content)))

	retdict = {'title': title, 'en': content}
	#print(content.split()[:30])
	languagelist = ['zh', 'es', 'de', 'ru', 'ja']
	for lang_title in languagelist:
		#get languages this article is in
		param_dict = {"action":"query",
						"prop":"langlinks",
						"format":"json",
						"lllang": lang_title,
						"redirects":1,
						"titles":title}
		req = requests.get("https://en.wikipedia.org/w/api.php", params=param_dict)
		results = req.json()
		#print(results)

		for pageid, info in results['query']['pages'].items():
			if 'langlinks' in info.keys():
				for lang in info['langlinks']:
					#print(lang)
					wikipedia.set_lang(lang['lang'])
					page = wikipedia.page(lang['*'])
					content = page.content
					retdict[lang['lang']] = content
			else:
				retdict[lang_title]="NONE"
	print(retdict.keys())
	return retdict

# Combine multiple translated articles into a dictionary in the following form:
# {title: [title1, title2, title3 ... titleN], en: [en-text1, en-text2...], ... }

def compile_article_dict(article_list):
	article_dict = {"title":[], "en":[], "zh":[], "es":[], "de":[], "ru":[], "ja":[]}
	new_article_dict = {}
	for article in article_list:
		new_article_dict = fetch_article(article)
		#print(new_article_dict.keys())
		for k in new_article_dict.keys() & article_dict.keys():
			# Assuming actually changes the list in article_dict
			article_dict[k].append(new_article_dict[k])
	return article_dict

# Given dictionary with multiple languages titles/articles, creates a pandas dataframe
# with following headers: Title | en text | zh text | es text | de text | ru text | ja text
def articles_to_dataframe_append(article_dict):
	article_df = pd.DataFrame.from_dict(article_dict)
	print(article_df.head())
	# Finally, place into csv for others to use.
	with open('multi-lang_articles_pandas_1.csv', 'a') as f:
		article_df.to_csv(path_or_buf=f, index=False, header=False)
	return article_df

def articles_to_dataframe(article_dict):
	article_df = pd.DataFrame.from_dict(article_dict)
	print(article_df.head())
	# Finally, place into csv for others to use.
	with open('multi-lang_articles_pandas_1.csv', 'w') as f:
		article_df.to_csv(path_or_buf=f, index=False, header=False)
	return article_df

def main():
	link_list = []
	# raw_article_links.txt is the list of title , link\n lines.
	with open('raw_article_links.txt', 'r') as raw_text:
		# Comes in format title, link\n title, link\n
		line = raw_text.readline()
		while line:
			link_list.append(line.split(",")[1].lstrip().strip('\n'))
			line = raw_text.readline()

		#print(link_list)
	raw_text.close()

	article_dict = compile_article_dict(link_list)

	# If creating new dataframe, uncomment this one and comment following one.
	#art_df = articles_to_dataframe(article_dict)
	art_df = articles_to_dataframe_append(article_dict)
	#print(art_df.head())

if __name__ == '__main__':
	main()


from typing import Optional, List

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def sum_left_leaves(node: TreeNode, parent: TreeNode, result: List[int]):
    if node is None:
        return
    if node.left is None and node.right is None and parent is not None:
        if node == parent.left:
            result.append(node.val)
    sum_left_leaves(node.left, node, result)
    sum_left_leaves(node.right, node, result)

class Solution:
    def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
        result = []
        sum_left_leaves(root, None, result)
        return sum(result)


import sys
from collections import defaultdict
from collections import deque

input = lambda: sys.stdin.readline().rstrip()

N, M = map(int, input().split())
materials = defaultdict(int)

for _ in range(N):
    name, amount = input().split()
    # 파는 재료는 중복되지 않는다
    materials[name] = int(amount)

equations = defaultdict(list)

for _ in range(M):
    name, equation = input().split("=")
    required_materials = defaultdict(int)
    for eq in equation.split("+"):
        i = 0

        while '0' <= eq[i] <= '9':
            i += 1

        material_amount = int(eq[:i])
        material_name = eq[i:]
        required_materials[material_name] += material_amount

    equations[name].append(required_materials)

dq = deque(equations.items())
visited = set()
for i in range(50*50):
    if not dq:
        break
    name, required_materials_list = dq.popleft()
    outerCanMake = False
    for required_materials in required_materials_list:
        canMake = True
        price = 0
        for material in required_materials:
            if material not in materials:
                canMake = False
                continue
            else:
                price += required_materials[material] * materials[material]
                if price > 1000000000:
                    price = 1000000001
        if canMake:
            outerCanMake = True
            materials[name] = price if materials[name] == 0 else min(materials[name], price)

    dq.append([name, required_materials_list])

if 'LOVE' not in materials and 'LOVE' not in equations:
    print(-1)
else:
    ans = materials['LOVE']
    print(ans if ans !=0 else -1)


from collections import OrderedDict, namedtuple

from multicorn import ForeignDataWrapper
from multicorn.utils import log_to_postgres, ERROR, WARNING, INFO, DEBUG

from .bqclient import BqClient

class ConstantForeignDataWrapper(ForeignDataWrapper):

    # Default vars
    client = None  # BqClient instance
    partitionPseudoColumn = 'partition_date'  # Name of the partition pseudo column
    # Pseudo column to fetch `count(*)` when using the remote counting and grouping feature
    countPseudoColumn = '_fdw_count'
    castingRules = None  # Dict of casting rules when using the `fdw_casting` option

    def __init__(self, options, columns):
        """
            Initialize instance, set class level vars
        """

        super(ConstantForeignDataWrapper, self).__init__(options, columns)

        # Set options
        self.setOptions(options)

        # Set table columns
        self.columns = columns

        # Set data types and conversion rules mapping
        self.setDatatypes()
        self.setConversionRules()

    def setOptions(self, options):
        """
            Set table options at class level
        """

        # Set options at class scope
        try:
            self.dataset = options['fdw_dataset']
            self.table = options['fdw_table']
            self.convertToTz = options.get('fdw_convert_tz')

            # Set verbose option
            self.setOptionVerbose(options.get('fdw_verbose'))

            # Set SQL dialect
            self.setOptionSqlDialect(options.get('fdw_sql_dialect'))

            # Set grouping option
            self.setOptionGroupBy(options.get('fdw_group'))

            # Set casting rules
            self.setOptionCasting(options.get('fdw_casting'))
        except KeyError:
            log_to_postgres(
                "You must specify these options when creating the FDW: fdw_dataset, fdw_table", ERROR)

    def setDatatypes(self):
        """
            Set data types mapping
        """

        # Create a named tuple
        datatype = namedtuple('datatype', 'postgres bq_standard bq_legacy')

        self.datatypes = [
            datatype('text', 'STRING', 'STRING'),
            # datatype('bytea', 'BYTES', 'BYTES'), # Not supported, need testing for support
            datatype('bigint', 'INT64', 'INTEGER'),
            datatype('double precision', 'FLOAT64', 'FLOAT'),
            datatype('boolean', 'BOOL', 'BOOLEAN'),
            datatype('timestamp without time zone', 'TIMESTAMP', 'TIMESTAMP'),
            datatype('date', 'DATE', 'DATE'),
            datatype('time without time zone', 'TIME', 'TIME'),
            datatype('timestamp without time zone', 'DATETIME', 'DATETIME'),
        ]

    def setConversionRules(self):
        """
            Set list of allowed conversion rules
        """

        # Create a named tuple
        conversionRule = namedtuple(
            'conversionRule', 'bq_standard_from bq_standard_to')

        self.conversionRules = [
            conversionRule('INT64', ['BOOL', 'FLOAT64', 'INT64', 'STRING']),
            conversionRule('FLOAT64', ['FLOAT64', 'INT64', 'STRING']),
            conversionRule('BOOL', ['BOOL', 'INT64', 'STRING']),
            conversionRule('STRING', ['BOOL', 'BYTES', 'DATE', 'DATETIME',
                                      'FLOAT64', 'INT64', 'STRING', 'TIME', 'TIMESTAMP']),
            conversionRule('BYTES', ['BYTES', 'STRING']),
            conversionRule(
                'DATE', ['DATE', 'DATETIME', 'STRING', 'TIMESTAMP']),
            conversionRule(
                'DATETIME', ['DATE', 'DATETIME', 'STRING', 'TIME', 'TIMESTAMP']),
            conversionRule('TIME', ['STRING', 'TIME']),
            conversionRule(
                'TIMESTAMP', ['DATE', 'DATETIME', 'STRING', 'TIME', 'TIMESTAMP']),
            conversionRule('ARRAY', ['ARRAY']),
            conversionRule('STRUCT', ['STRUCT']),
        ]

    def setOptionSqlDialect(self, standard_sql=None):
        """
            Set a flag for the SQL dialect.
            It can be `standard` or `legacy`. `standard` will be the default
        """

        self.dialect = 'standard'

        if standard_sql == 'legacy':
            self.dialect = 'legacy'

        # Verbose log
        if self.verbose:
            log_to_postgres("Set SQL dialect to `" + self.dialect + "`", INFO)

    def setOptionGroupBy(self, group):
        """
            Set a flag `self.groupBy` as `True` if `group` contains the string 'true'
            Otherwise, set it as `False`
        """

        if group == 'true':
            self.groupBy = True
            return

        self.groupBy = False

    def setOptionVerbose(self, verbose):
        """
            Set a flag `self.verbose` as `True` if `verbose` contains the string 'true'
            Otherwise, set it as `False`
        """

        if verbose == 'true':
            self.verbose = True
            return

        self.verbose = False

    def setOptionCasting(self, castingRules):
        """
            Conversion rules are received as a string, for example: '{"key": "FLOAT64", "datetime": "DATE"}'

            The string will be converted to a dict
        """

        if castingRules:
            # Cast string as a dict
            try:
                import ast
                self.castingRules = ast.literal_eval(castingRules)
            except Exception as e:
                log_to_postgres(
                    "fdw_casting conversion failed: `" + str(e) + "`", ERROR)

            # For security reasons, ensure that the string was correctly casted as a dict
            try:
                if type(self.castingRules) is not dict:
                    raise ValueError('fdw_casting format is incorrect.')
            except Exception as e:
                log_to_postgres(
                    "fdw_casting conversion failed: `" + str(e) + "`", ERROR)

    def getClient(self):
        """
            Manage a pool of instances of BqClient
            If the user uses different private keys in different tables, this method will return
               the correct instance of BqClient class associated to the table private key
        """

        # Returns an existing instance
        if self.client:
            return self.client

        # Or create a new instance
        return self.setClient()

    def setClient(self):
        """
            Attempt to connect to BigQuery client
        """

        try:
            # Attempt connection
            bq = BqClient()
            bq.setClient()

            # Verbose log
            if self.verbose:
                log_to_postgres(
                    "Connection to BigQuery client with BqClient instance ID " + str(id(bq)), INFO)

            # Add to pool
            self.client = bq

            return bq
        except RuntimeError:
            log_to_postgres(
                "Connection to BigQuery client failed", ERROR)

    def execute(self, quals, columns):
        """
            Executes a query
        """

        # # Verbose log
        # if self.verbose:
        #     log_to_postgres('Quals...', INFO)
        #     log_to_postgres(quals, INFO)
        #     log_to_postgres('Columns...', INFO)
        #     log_to_postgres(columns, INFO)

        # Returns instance of BqClient
        client = self.getClient()

        # Prepare query
        query, parameters = self.buildQuery(quals, columns)

        # Run query
        client.runQuery(query, parameters, self.dialect)

        # Return query output
        for row in client.readResult():
            # Create an ordered dict with the column name and value
            # Example: `OrderedDict([('column1', 'value1'), ('column2', value2)])`
            line = OrderedDict()
            for column in columns:
                line[column] = row[column]

            yield line

    def buildQuery(self, quals, columns):
        """
            Builds a BigQuery query
        """

        # Set query var
        query = ''

        # Add SELECT clause
        query += 'SELECT ' + self.buildColumnList(columns)

        # Add FROM clause
        query += " FROM `" + self.dataset + "." + self.table + "` "

        # Add WHERE clause
        clause, parameters = self.buildWhereClause(quals)
        query += clause

        # Add group by
        if self.groupBy:
            groupByColumns = self.buildColumnList(columns, 'GROUP_BY')
            if groupByColumns:
                query += ' GROUP BY ' + \
                    self.buildColumnList(columns, 'GROUP_BY')

        # Verbose log
        if self.verbose:
            log_to_postgres("Prepared query: `" + query + "`", INFO)

        return query, parameters

    def buildColumnList(self, columns, usage='SELECT'):
        """
            Build the SELECT clause of the SQL query
        """

        clause = ''

        # Disable aliases for Group By
        useAliases = True
        if usage == 'GROUP_BY':
            useAliases = False

        if columns:  # If we have columns
            for column in columns:
                if column == self.countPseudoColumn:  # Pseudo column to count grouped rows
                    if usage == 'SELECT':
                        clause += "count(*) " + \
                            self.addColumnAlias(column, useAliases) + ", "
                elif column == self.partitionPseudoColumn:  # Partition pseudo column
                    clause += "_PARTITIONTIME " + \
                        self.addColumnAlias(column, useAliases) + ", "
                else:  # Any other column
                    # Get column data type
                    dataType = self.getBigQueryDatatype(column)

                    # Save column original name
                    columnOriginalName = column

                    # If the data type is a date or a timestamp
                    if dataType in ['DATE', 'TIMESTAMP']:
                        column = self.setTimeZone(column, dataType)

                    # Data type casting
                    column = self.castColumn(
                        column, columnOriginalName, dataType)

                    clause += column + " " + \
                        self.addColumnAlias(
                            columnOriginalName, useAliases) + ", "

            # Remove final `, `
            clause = clause.strip(', ')
        elif usage == 'SELECT':  # Otherwise fetch all
            clause += "*"

        return clause

    def setTimeZone(self, column, dataType):
        """
            If the option `fdw_convert_tz` is used, convert the time zone automatically from UTC to the desired time zone
        """

        # Option is set
        if self.convertToTz:
            if dataType == 'DATE':  # BigQuery column type is `DATE`
                return 'DATE(' + column + ', "' + self.convertToTz + '") '
            else:  # BigQuery column type is `TIMESTAMP`
                return 'DATETIME(' + column + ', "' + self.convertToTz + '") '

        # Option is not set
        return column

    def castColumn(self, column, columnOriginalName, dataType):
        """
            If the option `fdw_casting` is used, this method will attempt to cast the column to the new type
        """

        if self.castingRules and columnOriginalName in self.castingRules:  # If we have casting rule for this column
            # Get desired casting
            castTo = self.castingRules[columnOriginalName]

            # Find if we have a matching rule
            rule = [
                conversionRule for conversionRule in self.conversionRules if conversionRule.bq_standard_from == dataType.upper()]

            if rule:
                # Check if casting from the original data type to the new one is supported
                if castTo.upper() in rule[0].bq_standard_to:
                    return 'CAST(' + column + ' as ' + castTo.upper() + ')'
                else:
                    log_to_postgres("Casting from the data type `" + dataType.upper(
                    ) + "` to the data type `" + castTo.upper() + "` is not permitted.", ERROR)
            else:
                log_to_postgres(
                    "Casting from the data type `" + dataType.upper() + "` is not permitted.", ERROR)

        # Option is not set
        return column

    def addColumnAlias(self, alias, useAliases=True):
        """
            Returns a string "as `alias`" if `useAliases` is `True`
        """

        if useAliases:
            return " as " + alias

        return ''

    def buildWhereClause(self, quals):
        """
            Build the WHERE clause of the SQL query
        """

        clause = ''
        parameters = []

        # Add WHERE clause
        # `quals` example: `[Qual('test', '=', 'test 2'), Qual('test', '~~', '3')]`
        if quals:
            clause += "WHERE "
            for qual in quals:
                if qual.field_name == self.partitionPseudoColumn:
                    clause += "_PARTITIONTIME " + \
                        str(self.getOperator(qual.operator)) + " ?"

                    # Format date as a timestamp
                    value = qual.value.strftime("%Y-%m-%d 00:00:00")

                    # Force data type to `TIMESTAMP`
                    parameters.append(self.setParameter(
                        qual.field_name, 'TIMESTAMP', value))
                else:
                    clause += str(qual.field_name) + " " + \
                        str(self.getOperator(qual.operator)) + " ?"
                    parameters.append(self.setParameter(
                        qual.field_name, self.getBigQueryDatatype(qual.field_name), qual.value))

                # Add ` AND `
                clause += " AND "

        # Remove final ` AND `
        clause = clause.strip(' AND ')

        return (clause, parameters)

    def getOperator(self, operator):
        """
            Validate operator
        """

        # List of BigQuery operators supported
        # Exhaustive list: https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-and-operators#operators
        # Non listed operators may or may not work
        operators = ['=', '<', '>', '<=', '>=', '!=', '<>', 'LIKE', 'NOT LIKE']

        # Mapping between multicorn operators and BigQuery operators
        mapping = {}
        mapping['~~'] = 'LIKE'
        mapping['!~~'] = 'NOT LIKE'

        if operator in operators:  # Operator is natively supported
            return operator
        elif operator in mapping:  # Multicorn operator has a BigQuery equivalent
            return mapping[operator]
        else:  # Operator is not supported
            log_to_postgres(
                "Operator `" + operator + "` is not currently supported", ERROR)

    def getBigQueryDatatype(self, column, dialect='standard'):
        """
            Returns the BigQuery standard SQL data type of a PostgreSQL column

            Example: `column1` has the PostgreSQL type `bigint` which is called `INT64` in BigQuery standard SQL
        """

        # Get PostgreSQL column type
        # Example: `timestamp without time zone`
        pgDatatype = self.columns[column].base_type_name

        for datatype in self.datatypes:  # For each known data types
            if datatype.postgres == pgDatatype:  # If the PostgreSQL data type matches the known data type
                # Returns equivalent BigQuery data type
                if dialect == 'legacy':
                    return datatype.bq_legacy
                else:
                    return datatype.bq_standard

        # Return a default data type in an attempt to save the day
        return 'STRING'

    def setParameter(self, column, type_, value):
        """
            Set a parameter in BigQuery client
        """

        # Verbose log
        if self.verbose:
            log_to_postgres(
                "Add query parameter `" + self.client.varToString(value) + "` for column `" + column + "` with the type `" + type_ + "`", INFO)

        return self.client.setParameter(type_, value)


# https://softeer.ai/practice/info.do?idx=1&eid=394
# 우물 안 개구리: DP

n, m = map(int, input().split())
weights = list(map(int, input().split()))
weights.insert(0, 0)

# 친분이 있는 인물 간 연결 테이블 작성
related = [[] for _ in range(n + 1)]
for _ in range(m):
    p_1, p_2 = map(int, input().split())
    related[p_1].append(p_2)
    related[p_2].append(p_1)

# -1: 패배, 0: 중립, 1: 승리
winner = [0] * (n + 1)
# 각 인물의 생각 조사
for i in range(1, n + 1):
    # 친분이 없는 경우
    if len(related[i]) == 0:
        winner[i] = 1
        continue

    # 앞선 경우에서 패배한 경우
    if winner[i] == -1:
        continue

    for j in related[i]:
        # 내가 이긴 사람 카운트
        if weights[i] > weights[j]:
            winner[i] = 1
            winner[j] = -1
        elif weights[i] == weights[j]:
            winner[i] = -1
            winner[i] = -1
            break
        else:
            winner[i] = -1
            break

print(winner.count(1))


class Solution:
    def change(self, amount: int, coins: List[int]) -> int:

        @cache
        def solve(i, amount):
            if amount == 0:
                return 1
            if amount < 0 or i >= len(coins):
                return 0
            res = solve(i, amount-coins[i]) + solve(i+1, amount)
            return res

        return solve(0, amount)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Aug 19 16:14:16 2018

@author: memoherrera

Selection Sort sorting algorithm
"""

def selectionSort(lista):
    for i in range(len(lista)):
        minI = i
        for j in range(minI, len(lista)):
            if lista[minI] > lista[j] :
                minI = j

        lista[i],lista[minI] = lista[minI], lista[i]

lista= [2, 3, 5, -23, 45, 35, 445, -345, 4532, 0]
print(lista)
selectionSort(lista)
print(lista)


from copy import deepcopy
import logging
from os.path import join
import time

import hydra
from hydra.utils import to_absolute_path
from icecream import ic
import matplotlib.pyplot as plt
import numpy as np
from omegaconf import OmegaConf
import pickle as pkl

from stat_utils import sample_from_distrib as sample
from robosuite.wrappers import VisualizationWrapper
from recovery_skills.envs.franka_door_env import FrankaDoorEnv
from recovery_skills.envs.franka_door_subtask_env import FrankaDoorSubtaskEnv
from recovery_skills.envs.improved_gym_wrapper import ImprovedGymWrapper
from recovery_skills.graph import State
from recovery_skills.utils import *
import robosuite.utils.transform_utils as T

logger = logging.getLogger(__name__)
logger.setLevel("DEBUG")

@hydra.main(config_path="../cfg", config_name="visualize_sim_states.yaml")
def main(cfg):
    if cfg.seed is None:
        seed = int(time.time())
    else:
        seed = cfg.seed

    controller_cfg = OmegaConf.to_container(cfg.env.controller)
    env_cfg = cfg.env

    # dummy
    # goal_state = State(FrankaDoorEnv.state_vars,
                       # FrankaDoorEnv.state_var_ndims).from_array([0])

    # goal_constraint = GoalConstraint(goal_state)

    env = FrankaDoorSubtaskEnv(
            goal_constraint=None,
            controller_configs=controller_cfg,
            has_renderer=cfg.render,
            horizon=env_cfg.horizon,
            context_cfg=env_cfg.context,
            obs_uncertainty=env_cfg.obs_uncertainty,
            timestep=env_cfg.timestep,
            eef_start_region_cfg=env_cfg.eef_start_region,
            )
    env.set_obs_corruption(False)

    path_to_file = to_absolute_path(join(cfg.path_to_dir, cfg.filename))
    data = pkl.load(open(path_to_file, 'rb'))

    if cfg.view_term_states:
        logger.info("Visualizing skill terminal states")
        nskills = len(data[0])
        term_states = [[] for _ in range(nskills)]
        for datum in data:
            for i in range(nskills):
                term_states[i].append(datum[i])

    elif cfg.view_subgoals:
        opts = cfg['subgoals']
        all_subgoals = pkl_load(opts.subgoal_file, True)
        labels = np.where(pkl_load(opts.label_file, True))

        logger.info("Visualizing task subgoals")
        subgoal_id = 1

        subgoals = np.array(all_subgoals[subgoal_id])

        if opts.viz_pos:
            subgoals = subgoals[labels]
            logger.info("Visualizing positive subgoals")
        elif opts.viz_neg:
            subgoals = subgoals[np.logical_not(labels)]
            logger.info("Visualizing negative subgoals")
        else:
            pass

        for state in subgoals:
            env.reset_from_state(state)
            for _ in range(50):
                env.render()

    elif cfg.view_skill_failures:
        logger.info("Visualizing failures")
        skill_id = 2
        all_failures = data
        failures = all_failures[skill_id]
        for i, failure in enumerate(failures):
            obs = failure['obs']
            env.reset_from_state(obs)
            logger.info("Start")
            env.render_by(25)

    elif cfg.view_task_failures:
        logger.info("Visualizing task failures")
        task_failures = data['tasks']
        for failure in task_failures:
            start, skill_chain = failure
            env.reset()
            obs = env.reset_from_state(start)
            for skill in skill_chain:
                env.env.goal_constraint = skill.goal
                obs, rew, done, info = skill.apply(env, obs, None, render=cfg.render)
                success = info['is_solved']
                logger.info(f"Skill success: {success}")
            task_success = env.check_task_success()
            logger.info("-------")
            logger.info(f"Task success: {task_success}")
            logger.info("-------")

    elif cfg.view_learnt_subgoals:
        train_skill_chains, _ = load_demos(to_absolute_path('./data/door_opening/debug/demos_1'))
        skill_chains, _ = load_demos(to_absolute_path('./data/door_opening/debug/demos_2'))
        reg = data
        nskills= len(skill_chains[0])

        # context of the task
        contexts = [[] for _ in range(nskills)]
        for chain in skill_chains:
            for i in range(nskills):
                contexts[i].append(State(FrankaDoorEnv.context_vars,
                                         FrankaDoorEnv.context_var_ndims).from_dict(
                                             chain[i].start_state.as_ordered_dict()).as_array())
        contexts = np.array(contexts)

        # subgoals
        subgoals =  [[] for  _ in range(nskills)]
        subgoal_arr =  [[] for  _ in range(nskills)]
        for chain in skill_chains:
            for i in range(nskills):
                subgoal_arr[i].append(chain[i].goal.goal.as_array())
                subgoals[i].append(chain[i].goal.goal)
        subgoal_arr = np.array(subgoal_arr)

        skill_id = 0
        X, y = contexts[skill_id], subgoal_arr[skill_id]
        preds = reg.predict(X)
        pred_states = [State(FrankaDoorEnv.state_vars,
                             FrankaDoorEnv.state_var_ndims).from_array(pred)
                       for pred in preds]
        __import__('ipdb').set_trace()

        for i, (subgoal, pred_state) in enumerate(zip(subgoals[skill_id], pred_states)):
            ic(i)
            ic("-----------")
            # viz actual subgoal
            env.reset()
            obs = env.reset_from_state(subgoal)
            ic("Ground truth subgoal")
            for _ in range(100):
                env.render()

            # viz predcted subgoal
            env.reset()
            obs = env.reset_from_state(pred_state)
            ic("Predicted subgoal")
            for _ in range(100):
                env.render()

    elif cfg.view_learnt_rl_subgoals:
        train_skill_chains, _ = load_demos(to_absolute_path('./data/door_opening/debug/demos_train'))
        skill_chains, _ = load_demos(to_absolute_path('./data/door_opening/debug/demos_test'))
        nskills = len(skill_chains[0])

        skill_id = 0
        env = env.env

        # train
        subgoals =  [[] for  _ in range(nskills)]
        subgoal_arr =  [[] for  _ in range(nskills)]
        for chain in train_skill_chains:
            for i in range(nskills):
                rl_goal = env.state_to_rl_state(chain[i].goal.goal)
                subgoals[i].append(rl_goal)
                subgoal_arr[i].append(rl_goal.as_array())

        subgoal_arr = np.array(subgoal_arr)

        learnt_goal_constraint = LearntGoalConstraint(subgoals[skill_id])

        # test
        subgoals =  [[] for  _ in range(nskills)]
        rl_subgoals =  [[] for  _ in range(nskills)]
        for chain in train_skill_chains:
            for i in range(nskills):
                subgoals[i].append(chain[i].goal.goal)
                rl_goal = env.state_to_rl_state(chain[i].goal.goal)
                rl_subgoals[i].append(rl_goal)

        viz_env = VisualizationWrapper(env, 'default')

        for i, (subgoal, rl_subgoal) in enumerate(zip(subgoals[skill_id],
                                                      rl_subgoals[skill_id])):
            ic(i)
            ic("-----------")
            # viz actual subgoal
            env.reset()
            obs = env.reset_from_state(subgoal)
            # ee_pos = rl_subgoal.as_ordered_dict()['robot_eef:pose/position']
            ee_pos = obs['robot_eef:pose/position']
            viz_env.set_indicator_pos('indicator0', ee_pos)
            env.sim.forward()
            ic("Ground truth subgoal")
            ic(ee_pos)
            for _ in range(100):
                env.render()

            # __import__('ipdb').set_trace()

            # viz predcted subgoal
            ic("Predicted subgoal")
            rl_goal = learnt_goal_constraint.goal(subgoal)
            # ee_pos = rl_goal.as_ordered_dict()['robot_eef:pose/position']
            fk_goal = env.state_to_rl_state(subgoal)
            ee_pos = fk_goal.as_ordered_dict()['robot_eef:pose/position']
            ic(ee_pos)
            viz_env.set_indicator_pos('indicator0', ee_pos)
            env.sim.forward()
            for _ in range(100):
                env.render()

    elif cfg.view_backchaining_states:
        states = pkl_load(cfg.backchaining.obs_file, True)
        labels = pkl_load(cfg.backchaining.label_file, True)

        def plot_states(states, ax=None, **kwargs):
            eef_poss = [state['robot_eef:pose/position'] for state in states]
            handle_poss = [state['handle:pose/position'] for state in states]
            eef_to_handle = np.array([eef - handle for eef, handle in zip(eef_poss, handle_poss)])
            if ax is None:
                ax = plt.axes(projection='3d')
            # ax.plot([-0.03, 0.07], [0.0, 0.0], [0.0, 0.0], color='k', linewidth=5)
            ax.plot([0], [0], [0], 'kx')
            scatter = ax.scatter(eef_to_handle[:, 0], eef_to_handle[:, 1], eef_to_handle[:, 2],
                    **kwargs)
            return scatter

        ax = plt.axes(projection='3d')
        ax.set_xlim3d(-0.25, 0.25)
        ax.set_ylim3d(-0.25, 0.25)
        ax.set_zlim3d(-0.25, 0.25)
        ax.set_xlabel('x')
        ax.set_ylabel('y')
        ax.set_zlabel('z')
        states = np.array(states)
        labels = np.array(labels).astype(bool)
        pos_states = states[labels]
        neg_states = states[np.logical_not(labels)]

        plot_states(pos_states, ax=ax, c='g', alpha=1)
        plot_states(neg_states, ax=ax, c='r', alpha=0.1)
        plt.tight_layout()
        plt.show()

    elif cfg.view_learnt_preconds:
        # env = VisualizationWrapper(
            # FrankaDoorEnv(
                # # goal_constraint=None,
                # controller_configs=controller_cfg,
                # has_renderer=cfg.render,
                # horizon=env_cfg.horizon,
                # context_cfg=env_cfg.context,
                # obs_uncertainty=env_cfg.obs_uncertainty,
                # timestep=env_cfg.timestep,
                # eef_start_region_cfg=env_cfg.eef_start_region,
            # ),
            # 'default')

        # sample a state
        # sample end-effector states around the handle
        skill_id = 3
        precond = data[skill_id]
        __import__('ipdb').set_trace()
        # __import__('ipdb').set_trace()
        # states = precond.sample(FrankaDoorEnv, 25, env)
        states = precond.sample(FrankaDoorEnv, 50)
        # states = precond.sample(FrankaDoorEnv, 25)
        for state in states:
            obs = env.reset_from_state(state)
            print(f"Success prob: {np.around(precond.prob(obs), 3)}")
            env.render_by(25)

    elif cfg.view_learnt_preconds_probs:
        # skill_id = 0
        # skill_ids = [0, 1, 2]
        skill_ids = [0]
        ax = plt.axes(projection='3d')
        ax.set_xlim3d(-0.25, 0.25)
        ax.set_ylim3d(-0.25, 0.25)
        ax.set_zlim3d(-0.25, 0.25)
        ax.set_xlabel('x')
        ax.set_ylabel('y')
        ax.set_zlabel('z')

        def sample_states_around_handle(ref_state, n):
            samples_x = np.random.uniform(low=-0.1, high=0.1, size=n)
            samples_y = np.random.uniform(low=-0.1, high=0.05, size=n)
            samples_z = np.random.uniform(low=-0.1, high=0.1, size=n)
            X = np.column_stack([samples_x, samples_y, samples_z])
            # X[:, 2] = 0.05
            handle_pos = ref_state['handle:pose/position']

            states = []
            for x in X:
                state = deepcopy(ref_state)
                state['robot_eef:pose/position'] = handle_pos + x
                states.append(state)

            return np.array(states)

        for skill_id in skill_ids:
            precond = data[skill_id]
            # sample end-effector states around the handle
            # sample the joint space dist
            # states = precond.sample(FrankaDoorEnv, 500)
            ref_state = precond.sample(FrankaDoorEnv, 1, env)[0]
            ref_rl_state = env.state_to_rl_state(ref_state)

            rl_states = sample_states_around_handle(ref_rl_state, 500)
            # rl_states = np.array([env.state_to_rl_state(state) for state in states])
            precond_probs = np.array([precond.prob(state) for state in rl_states])
            # rl_states = rl_states[precond_probs > 0.7]

            def plot_states(states, ax=None, **kwargs):
                eef_poss = [state['robot_eef:pose/position'] for state in states]
                handle_poss = [state['handle:pose/position'] for state in states]
                eef_to_handle = np.array([eef - handle for eef, handle in zip(eef_poss, handle_poss)])
                if ax is None:
                    ax = plt.axes(projection='3d')
                # ax.plot([-0.03, 0.07], [0.0, 0.0], [0.0, 0.0], color='k', linewidth=5)
                ax.plot([0], [0], [0], 'kx')
                scatter = ax.scatter(eef_to_handle[:, 0], eef_to_handle[:, 1], eef_to_handle[:, 2],
                        **kwargs)
                return scatter

            scatter = plot_states(rl_states, ax=ax, c=precond_probs, cmap='RdYlGn', vmin=0, vmax=1)
            # scatter = plot_states(rl_states, ax=ax) #, vmin=0, vmax=1)
        # plt.colorbar(scatter)
        plt.show()

    elif cfg.view_precond_prediction:
        clfs = data
        subgoal_id = 1
        clf = clfs[subgoal_id]

        pos_site_config = {
            "name": 'pos',
            "type": "sphere",
            "size": [0.03],
            "rgba": [0, 0, 1, 0.5],
        }
        neg_site_config = {
            "name": 'neg',
            "type": "sphere",
            "size": [0.03],
            "rgba": [1, 0, 0, 0.5],
        }

        env = VisualizationWrapper(
            FrankaDoorEnv(
                controller_configs=controller_cfg,
                has_renderer=cfg.render,
                horizon=env_cfg.horizon,
                context_cfg=env_cfg.context,
                obs_uncertainty=env_cfg.obs_uncertainty,
                timestep=env_cfg.timestep,
                eef_start_region_cfg=env_cfg.eef_start_region,
            ),
            [pos_site_config, neg_site_config])
        env.set_obs_corruption(False)

        pos_recoveries = pkl_load(
            'data/door_opening/debug/recovery_skills/recovery_skills.pkl', True)[subgoal_id]
        neg_recoveries = pkl_load(
            'data/door_opening/debug/recovery_skills/failed_recovery_skills.pkl', True)[subgoal_id]
        pos_recovery_states = [recovery.start for recovery in pos_recoveries]
        neg_recovery_states = [recovery.start for recovery in neg_recoveries]
        recovery_states = pos_recovery_states + neg_recovery_states
        np.random.shuffle(recovery_states)

        pos_X = [state.as_array() for state in pos_recovery_states]
        neg_X = [state.as_array() for state in neg_recovery_states]
        X = [state.as_array() for state in recovery_states]

        pos_preds = clf.predict(pos_X)
        neg_preds = clf.predict(neg_X)
        preds = clf.predict(X)

        # for state, pred in zip(pos_recovery_states, pos_preds):
        for state, pred in zip(recovery_states, preds):
            obs = env.reset_from_state(state)
            if pred == 1:
                env.set_indicator_pos('pos', obs['robot_eef:pose/position'])
            else:
                env.set_indicator_pos('neg', obs['robot_eef:pose/position'])

            env.sim.step()
            for _ in range(100):
                env.render()

        # ref_state = recovery_states[0]

        # eef_region_cfg = cfg.eef_region

        # def sample_eef_perturbation():
            # x = sample(eef_region_cfg.x)
            # y = sample(eef_region_cfg.y)
            # z = sample(eef_region_cfg.z)

            # roll = sample(eef_region_cfg.roll)
            # pitch = sample(eef_region_cfg.pitch)
            # yaw = sample(eef_region_cfg.yaw)

            # eef_pos = [x, y, z]
            # eef_quat = T.mat2quat(T.euler2mat([roll, pitch, yaw]))
            # return eef_pos, eef_quat

        # # generate states with same door pose and
        # states = []
        # for i in range(cfg.nevals):
            # state = deepcopy(ref_state)
            # eef_pos, eef_quat = sample_eef_perturbation()
            # state['robot_eef:pose/position'] = eef_pos
            # state['robot_eef:pose/quat'] = eef_quat

    elif cfg.view_failure_clusters:
        clusters = data

        __import__('ipdb').set_trace()
        for cluster_id, cluster in enumerate(clusters):
        # for cluster_id, cluster in enumerate(clusters[-1:]):
            logger.info(f"Cluster {cluster_id}")
            for state in cluster[:5]:
            # for state in cluster:
                obs = env.reset_from_state(state)
                logger.info(f"Hinge: {obs['hinge:pose/theta']}")
                env.render_by(40)

if __name__ == "__main__":
    main()


import os

root_dir = os.path.dirname(os.path.abspath('.'))

#  dataSet 使用的数据集在项目中的位置 root_dir为项目当前绝对地址
dataSet = root_dir + "/DataSets/AthletePlaysForTeam"

# 查询关系语句(在制作GRAPH-net时剔除)
query = "concept:athleteplaysforteam"

#  MCTS 是否采用蒙特卡洛树搜索
use_MCTS = True

# graph-net中的编码方式,one-hot,node2vec,
node_encode = "node2vec"
node_encode_dim = 128
edge_encode = "one-hot"
# embedding 存放目录
node_encode_filename = root_dir + "/Embeddings/01/node2vec.emb"
edge_encode_filename = root_dir + "/Embeddings/01/edge2emb.json"
# embedding模型存放目录
node_model_filename = root_dir + "/Embeddings/01/node2vec.model"
edge_model_filename = root_dir + "/Embeddings/01/edge2vec.model"

# 训练用的path list 存放路径
pathlist_file_dir = root_dir + "/TrainData/AthletePlaysForTeam1000/"

# 模型的可视化Summary 存放路径
Summary_dir = root_dir+"/Model/summary/"
# 模型参数 存放路径
act_net_model_dir = root_dir+"/Model/actnet_model/"


from PyQt5 import uic
from PyQt5.QtWidgets import QSizePolicy, QDialog, QMessageBox
from PyQt5.QtCore import pyqtSignal, QDate
from datetime import datetime
import model.main as model

DB_PATH = './data/'

Ui_NewWell, QtBaseClass = uic.loadUiType("./resources/newWell.ui")

class NewWell(QDialog, Ui_NewWell):
    sessionChanged = pyqtSignal(model.Session)

    def __init__(self, parent=None):
        super(NewWell, self).__init__(parent)
        Ui_NewWell.__init__(self)
        self.setupUi(self)
        # controller handling
        today = QDate(datetime.today())
        self.dateEdit.setDate(today)
        self.session = model.Session()
        # connections
        self.okButton.clicked.connect(self.store_data)
        self.cancelButton.clicked.connect(self.close)

    def store_data(self):
        # Pass data from view to model
        self.model = model.Well()
        self.model.well['name'] = self.nameEdit.text()
        self.model.well['field'] = self.fieldEdit.text()
        self.model.well['area'] =  self.areaEdit.text()
        self.model.well['province'] = self.provinceEdit.text()
        self.model.well['country'] = self.countryEdit.text()
        self.model.well['company'] = self.companyEdit.text()
        self.model.well['driller'] = self.drillerEdit.text()
        self.model.well['operator'] = self.operatorEdit.text()
        self.model.well['witness'] = self.witnessEdit.text()
        self.model.well['date'] = self.dateEdit.date().toString("yyyy-MM-dd")
        self.model.well['service'] = self.serviceEdit.text()
        self.model.well['comments'] = self.commentsEdit.toPlainText()
        self.model.well['footNote'] = self.footNoteEdit.toPlainText()
        self.model.well['inDate'] = datetime.today().\
                                        strftime("%Y-%m-%d %H:%M:%S")

        self.session.active['well'] = self.model.well['name']
        self.session.active['run'] = 1
        self.session.active['DBpath'] = "{}{}.db".format(
                                            DB_PATH,
                                            self.model.well['field'])
        passTable = model.Pass().\
                create_table(self.session.active['DBpath'])
        self.session.active['pass'] = passTable
        self.session.active['mode'] = 'realtime'

        print(self.model.well.values()) # DEBUG

        # Store well data, emit session parameters and link well_run_pass table
        error = self.model.write()
        if error:
            self.dialog_critical(error)
        else:
            self.sessionChanged.emit(self.session)

        error  = model.WellRunTable().write(self.session)
        if error:
            self.dialog_critical(error)
        else:
            self.close()

    def dialog_critical(self, s):
        dlg = QMessageBox(self)
        dlg.setText(s)
        dlg.setIcon(QMessageBox.Critical)
        dlg.show()


import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
from django.utils import timezone
from chat.models import ChatMessage, Chat
class ChatConsumer(WebsocketConsumer):
    def connect(self):
        self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
        self.room_group_name = "chat_%s" % self.room_name

        # Join room group
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name, self.channel_name
        )

        self.accept()

    def disconnect(self, close_code):
        # Leave room group
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name, self.channel_name
        )

    # Receive message from WebSocket
    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]
        print("data: ",text_data_json)
        user=self.scope["user"]
        chat = Chat.objects.get(id=text_data_json["roomName"])

        ChatMessage.objects.create(text=message, sender=chat.first_user, receiver=chat.second_user, chat_id=chat, timestamp=timezone.now())
        # Send message to room group
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name, {"type": "chat_message", "message": message,"user":user.username}
        )

    # Receive message from room group
    def chat_message(self, event):
        message = event["message"]
        user=event["user"]
        print(event)
        # Send message to WebSocket
        self.send(text_data=json.dumps({"message": message,"user":user}))


import sys
import os
import glob
import re
import numpy as np

from fastai import *
from fastai.vision import *

import cv2

from flask import Flask, render_template, Response, request, redirect, url_for
import pickle
from werkzeug.utils import secure_filename
from gevent.pywsgi import WSGIServer

app = Flask(__name__)

# import our model with pickle
#model = pickle.load(open('templates/app_home/export.pkl', 'rb'))

def model_predict(img_path):
    original = cv2.imread(img_path)
    numpy_image = np.asarray(original)
    preds = learn.predict(original)
    os.remove(img_path)

    return preds

@app.route("/")
def home():
    return render_template("app_home/index.html")

@app.route("/upload_file", methods=['GET'])
def upload_file():
    return render_template("app_home/predict.html")

@app.route('/predict', methods=['GET', 'POST'])
def upload():
    if request.method == 'POST':
        f = request.files['image']
        print(f)
        print(f.filename)
        # save file to uploads
        basepath = os.path.dirname(__file__)
        file_path = os.path.join(
            basepath, 'templates/uploads', secure_filename(f.filename))
        f.save(file_path)

        preds = model_predict(file_path)

        print(f"preds: {preds}")
        return preds
        #pred_class = decode_predictions(preds, top=1)
    else:
        return 'ok'

if __name__ == "__main__":
    app.run(debug=True)


from rich.console import Console
from rich.columns import Columns
from rich.panel import Panel
from rich.table import Table
from rich import box
from classes.character.character import Character

from factories.character.character_factory import CharacterFactory
from factories.character.life_path_factory import LifePathFactory
from factories.stats_factory import StatsFactory
from factories.weapon.melee_weapon_factory import MeleeWeaponFactory
from factories.weapon.weapon_factory import WeaponFactory
from factories.weapon.guns_factory import GunFactory
from utils.dises import get_random_d10, get_random_d6

# print('STATS:    ', character._stats.__dict__)
# print('SKILLS:   ', character._skills.__dict__)
# print('LIFE PATH:   ', character._life_path.__dict__)

def character_list(character: Character):
    console = Console()
    name = Panel('[red]' + character._name, style="red",)
    role = Panel(character._role, style="red",)
    hits = Panel(
        f'[red]{character._hit_points}[/red] / [green]{character._max_hit_points}[/green]', style="red",)

    # STATS
    stats = []
    for key, value in character._stats.__dict__.items():
        stats.append(f'{key[1:].capitalize()}: {value} ')

    # Skills
    skills = []
    for key, value in character._skills.__dict__.items():
        skills.append(f'{key[1:].capitalize()}: [cyan bold]{value} ')

    skills_table = Table(title='Skills', show_header=False,
                         width=70, min_width=70, box=box.ROUNDED)
    for i in range(0, 21, 3):
        skills_table.add_row(skills[i], skills[i+1], skills[i+2])

    # Life path
    life_path = []
    for key, value in character._life_path.__dict__.items():
        if type(value) == str:
            life_path.append([key, value])
        elif type(value) == list and len(value) == 0:
            life_path.append([key, 'None'])
        else:
            actors = ''
            for i in value:
                actors += i + '\n'
            life_path.append([key, actors[:-1]])
    # print(life_path)
    console.print(Columns((name, role, hits)))
    console.print(Panel(Columns(stats), style="red",
                        title='[red]STATS', width=70, ))
    console.print(skills_table, style="red")
    for i in life_path:
        console.print(Panel(i[1], style="red", title=i[0], width=70))

if __name__ == '__main__':
    stats_factory = StatsFactory()
    life_path_factory = LifePathFactory()

    melee_weapon_factory = MeleeWeaponFactory()
    gun_factory = GunFactory()
    weapon_factory = WeaponFactory(
        melee_weapon_factory, gun_factory)
    character_factory = CharacterFactory(
        stats_factory, life_path_factory, weapon_factory)

    character = character_factory.create_random_fixer('Hero')

    character_list(character)


"""
# DO EMISSION SYSTEM TO AVOID COLLISIONS!

ADDITIONAL: path option available for forest and field and desert

custom specification of num of particles for each object in the respective environment collection

set up perspective options (dropdown list options)
if near & top left
if moderate & top left
...
if far & bottom right

# ADDITIONAL CHALLENGE:
perspective options: (continuous)
    distance specification (in metres) & top left
    ...
    distance specification (in metres) & bottom right)

# ADDITIONAL:
CUSTOM ENVIRONMENT from CUSTOM objects you want to import to be included in the custom physical environment collection

# Available feature: variable camera angle + the rest of the params adjusted
"""

bl_info = {
    "name":"Perspective Scene addon",
    "author": "Solarspaceclouds",
    "version": (1,0),
    "blender": (3, 1,2),
    "location":"View3D > N",
    "description": "Creates Custom Scene with Subject in Camera Perspective",
    "warning": "",
    "doc_url":"",
    "category": "Add Mesh",
}

import bpy
from math import inf
from mathutils.geometry import intersect_line_plane as ilp

from mathutils import Vector
from mathutils.bvhtree import BVHTree

import bpy
from bpy import context, data, ops
from math import radians, pi, sin, cos, sqrt
import os
import random
import pickle
import sys
import json

from bpy_extras.object_utils import world_to_camera_view
from bpy_extras.view3d_utils import location_3d_to_region_2d
import random
from mathutils import Vector
import glob
import datetime, calendar

class generateScenePanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Perspective Scene Generation"
    bl_idname = "persp_scene_gen"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Perspective Scene"

    def draw(self, context):
        layout = self.layout

        layout.operator("wm.template_operator")

class ADDONOPTIONS(bpy.types.Operator):
    """
    <subjejct> in a <env>
    """

    bl_label = "Generate Scene"
    bl_idname = "wm.template_operator"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Perspective Scene"

    min_x_rotation = 30
    max_x_rotation = 60
    camera_x_rotation : bpy.props.IntProperty(
        name = "camera x rotation",
        description = "camera rotation from verticle downwards default state",
        min = min_x_rotation,
        max = max_x_rotation,
        default = 45,
    )

    particle_count : bpy.props.IntProperty(
        name = "particles count",
        description = "number of environment particles",
        min = 10,
        max = 500,
        default = 50,
    )

    particle_scale : bpy.props.FloatProperty(
        name = "particle scale",
        description = "size of environment particles",
        min = 0.2,
        max = 5.0,
        default = 0.2,
    )

    dist_enum : bpy.props.EnumProperty(
        # further refine in future: let the scene take more than 1 subject object (i.e. duplicate the subject object? or make particle system)

        name = "Dist From Camera",
        description = "Shortest distance from plane to Camera",
        items = [ # list of available objects to use as SUBJECT mesh
            ('NEAR', 'Near', 'Adjust camera to be near the plane'),
            ('MODERATE', 'Moderate', 'Adjust camera to be moderate distance from the plane'),
            ('FAR', 'Far', 'Adjust camera to be far from the plane')
        ]
    )

    min_subject_qty = 1
    max_subject_qty = 3
    subject_qty : bpy.props.IntProperty(
        name = "Subject Quantity",
        description = "Number of subject mesh object in scene",
        min = min_subject_qty,
        max = max_subject_qty,
        default = 1
    )

    quadrant_enum : bpy.props.EnumProperty(
        name = "Quadrant",
        description = "Select an option",
        items = [
            ('TL', 'top left', 'Shift camera view to position subject in top left quadrant (Q1)'),
            ('TM', 'top middle', 'Shift camera view to position subject in top middle quadrant (Q2)'),
            ('TR', 'top right', 'Shift camera view to position subject in top left quadrant (Q3)'),
            ('ML', 'middle left', 'Shift camera view to position subject in middle left quadrant (Q4)'),
            ('MM', 'middle middle', 'Shift camera view to position subject in middle middle quadrant (Q5)'),
            ('MR', 'middle right', 'Shift camera view to position subject in middle left quadrant (Q6)'),
            ('BL', 'bottom left', 'Shift camera view to position subject in bottom left quadrant (Q4)'),
            ('BM', 'bottom middle', 'Shift camera view to position subject in bottom middle quadrant (Q5)'),
            ('BR', 'bottom right', 'Shift camera view to position subject in bottom left quadrant (Q6)'),
        ]
    )

    generate_sample_coll : bpy.props.BoolProperty(
        name="Generate Example Collection",
        description="Generate an example collection",
        default = False
        )
    use_sample_coll : bpy.props.BoolProperty(
        name="Use Example Collection",
        description="Use the example collection",
        default = False
        )
    render_image : bpy.props.BoolProperty(
        name="Render image",
        description="Render an image of the generated camera view/scene",
        default = True
        )
    colour_1 : bpy.props.FloatVectorProperty(
        name = "Colour 1",
        description = "Colour of the ground",
        default = (0.0, 0.0313828,0.0) #dark green
#        (0,0.0313828,0,1) # dark green
#        (0.045,0,0,1) # dark brown
        )
    colour_2 : bpy.props.FloatVectorProperty(
        name = "Colour 2",
        description = "Colour of the ground",
        default = (0.045, 0.0,0.0)
        )

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self)

    def draw(self, context): # the UI of the panel
        scene = bpy.context.scene
        layout = self.layout
        layout.label(text=" Subject properties:")
        layout.prop_search(scene, "theChosenObject", scene, "objects")
        layout.prop(self, "quadrant_enum")
        layout.prop(self, "subject_qty")
        layout.prop(self, "dist_enum")

        layout.label(text=" Environment properties:")
        layout.prop(context.scene, "env_coll")
        row = layout.row()
        row.prop(self, "particle_count") # SUBJECT distance from camera (in metres)
        row.prop(self, "particle_scale") # SUBJECT distance from camera (in metres)
        layout.prop(self, "generate_sample_coll")
        layout.prop(self, "use_sample_coll")

        layout.label(text=" Perspective properties:")
        layout.prop(self, "camera_x_rotation") # SUBJECT distance from camera (in metres)

        layout.label(text= " Render Image:")
        layout.prop(self, "render_image")

        layout.label(text= " Base Plane Colours:")
        layout.prop(self, "colour_1")
        layout.prop(self, "colour_2")

    def remove_camera_markers(self, torus_coll):
        if bpy.context.object.mode == 'EDIT':
            bpy.ops.object.mode_set(mode='OBJECT')
        # deselect all objects
        bpy.ops.object.select_all(action='DESELECT')

#        objects = bpy.data.objects
        for obj in torus_coll:
            if obj.name.startswith("Torus"):
                obj.select_set(True)
                bpy.ops.object.delete()
        return

    def set_camera(self):
        d=0
        d_near_range = random.randint(75,126)
        d_mid_range = random.randint(126,176)
        d_far_range = random.randint(176,226)
#
        camera = bpy.context.scene.objects.get("Camera",None)
        if camera == None:
            bpy.ops.object.camera_add(enter_editmode=False, align='VIEW', location=(100, 0, 70), rotation=(0, 0, 90), scale=(1, 1, 1))
        camera = bpy.context.scene.camera
        camera.rotation_euler = (radians(self.camera_x_rotation),0,radians(90))
#
        if self.dist_enum =='NEAR':
            d = d_near_range
            print("NEAR\n")

        elif self.dist_enum == "MODERATE":
            d = d_mid_range
            print("MODERATE\n")

        elif self.dist_enum=="FAR":
            d = d_far_range
            print("FAR\n")

        z_dist = d*cos(radians(self.camera_x_rotation))
        x_dist = sqrt(d**2 - z_dist**2)

        # Adjust camera x location
        camera.location[0] = x_dist
        # Adjust camera z location
        camera.location[2] = z_dist

        # set camera clipping values
        camera.data.clip_start = 0.001
        camera.data.clip_end = 10000

        return

    def mark_cam_edges(self, context):
        context = bpy.context
        scene = context.scene
        # Global XY plane
        plane_co = (0, 0, 0)
        plane_no = (0, 0, 1)

        cam = scene.camera
        mw = cam.matrix_world
        o = mw.translation

        camdata = cam.data

        tr, br, bl, tl = [
        mw @ f for f in camdata.view_frame(scene=scene)]
        # offset vectors from bl.
        # eg middle would be bl + 0.5 * (x + y)
        x = tr - tl
        y = tr - br

        coords = []
        for image_coord in ((0, 0), (1, 0), (1, 1), (0, 1)):
            cx, cy = image_coord
            # vector pointing from cam origin thru image point (PERSP)
            v = (bl + (cx * x + cy * y)) - o

            pt = ilp(o, o +  v, plane_co, plane_no, True)
            if pt:
#                print("Adding cube at ", pt)
#                bpy.ops.mesh.primitive_cube_add(
#                        location=pt,
#                        )
                coords.append(pt)

        print("cam_view_coords:", coords)

        x_set = set()
        y_set = set()
        for coord in coords:
            x_set.add(round(coord[0],3))
            y_set.add(round(coord[1],3))

        print("x_set:", x_set)
        print("y_set:", y_set)
        x_set = sorted(x_set)
        y_set = sorted(y_set)

        print("sorted x", x_set)
        print("sorted y", y_set)

#        return x_set, y_set, camera_viewer
        return x_set, y_set

    def create_camera_viewplane(self, coords):
        context = bpy.context
        cam = bpy.context.scene.camera
        name = f"{cam.name}_ViewPlane"
        me = bpy.data.meshes.new(name)
        faces = [range(len(coords))] if len(coords) > 2 else []
        me.from_pydata(coords, [], faces)
        ob = bpy.data.objects.new(name, me)
        context.collection.objects.link(ob)

        camera_viewer = ob
        return camera_viewer

    def create_subject_instances(self, subject_choice, subject_x_loc_min_lim, subject_x_loc_max_lim, subject_y_loc_min_lim, subject_y_loc_max_lim):
        objects = bpy.data.objects
        #CREATE SUBJECT INSTANCE COLLECTION
        si_coll = bpy.data.collections.new("Subject instance collection")
        bpy.context.scene.collection.children.link(si_coll)

        # note: duplicate subjects were instantiated in 'Subject choices' collection initially
        # want to move the duplicated subjects to 'Subject instance collection' collection

        bpy.ops.object.select_all(action='DESELECT')

        subject_instances = []
        for i in range(len(objects)):
            if (objects[i].name.startswith(subject_choice.name) and objects[i].name!= subject_choice.name): # want a match but don't want the exact match
                objects[i].select_set(True)
#                sc_coll.objects.unlink(objects[i])
#                bpy.ops.object.move_to_collection(si_coll)

        for obj in bpy.context.selected_objects:
            for other_col in obj.users_collection:
                other_col.objects.unlink(obj)
            if obj.name not in si_coll.objects:
                si_coll.objects.link(obj)

        return si_coll

    def prevent_subjects_collision(self, si_coll,subject_x_loc_min_lim, subject_x_loc_max_lim, subject_y_loc_min_lim, subject_y_loc_max_lim):
        # to instantiate all the subject choice instances in the same quadrant
        for sub in si_coll.objects:
            sub.location[0] = random.uniform(subject_x_loc_min_lim, subject_x_loc_max_lim)
            sub.location[1] = random.uniform(subject_y_loc_min_lim, subject_y_loc_max_lim)

        faces = [(0, 1, 2, 3), (4, 7, 6, 5), (0, 4, 5, 1), (1, 5, 6, 2), (2, 6, 7, 3), (4, 0, 3, 7)]

        for i in range(len(si_coll.objects)):
            j = (i+1) % len(si_coll.objects)
            obj1 = si_coll.objects[i]
            obj2 = si_coll.objects[j]

            vert1 = [obj1.matrix_world @ Vector(corner) for corner in obj1.bound_box]
            vert2 = [obj2.matrix_world @ Vector(corner) for corner in obj2.bound_box]

            bvh1 = BVHTree.FromPolygons(vert1, faces)
            bvh2 = BVHTree.FromPolygons(vert2, faces)

            if bool(bvh1.overlap(bvh2)) == True: # if have at least 1 collision (i.e. 2 subject instances colliding with each other)
                self.prevent_subjects_collision(self, si_coll)
        return

    def rotate_subject_instances(self):
        """ rotate all subjects in scene about the z axis"""
        # angle to rotate tank about z axis
        col = bpy.data.collections["Subject instance collection"].objects
        for obj in col:
            deg = random.randrange(10,350,1)
            obj.rotation_euler[2] += deg
        return

    def generate_sample_collection(self):
        bpy.ops.object.select_all(action='DESELECT')

        if self.generate_sample_coll == True:
            env_coll = bpy.data.collections.get("Environment Collection", None)
            if env_coll == None:
                env_coll = bpy.data.collections.new("Environment Collection")
                bpy.context.scene.collection.children.link(env_coll)
                bpy.ops.mesh.primitive_uv_sphere_add(radius=1, enter_editmode=False, align='WORLD', location=(-3.48966, -0.885505, 0.001297), scale=(1, 1, 1))
                bpy.context.selected_objects[-1].name = "EnvObj1"
                bpy.ops.mesh.primitive_cone_add(radius1=1, radius2=0, depth=2, enter_editmode=False, align='WORLD', location=(-3.48966, -0.885505, 0.001297), scale=(1, 1, 1))
                bpy.context.selected_objects[-1].name = "EnvObj2"

                for obj in bpy.data.objects:
                    if obj.name in ["EnvObj1", "EnvObj2"]:
                        for other_col in obj.users_collection:
                            other_col.objects.unlink(obj)
                        if obj.name not in env_coll.objects:
                            env_coll.objects.link(obj)

            # Use the generate sample collection as the environment collection:
            if self.use_sample_coll == True:
                bpy.data.scenes["Scene"].env_coll = env_coll
        bpy.ops.object.select_all(action='DESELECT')
        return

    def execute(self, context): # here is what happens when the 'Generate Scene' button is pressed
        # INITIAL SET-UP : Clear default objects in blender3D (General Scene)

        ## set up render settings
        scene = bpy.context.scene
        scene.render.image_settings.file_format = 'JPEG' # set output format to .jpg
        """
        START OF GENERATING BASE MODEL FOR THE FIRST TIME (3D MODEL INSTANTIATION)
        """

        hasGPU = bpy.context.preferences.addons["cycles"].preferences.has_active_device()
        if hasGPU == True:
            bpy.data.scenes["Scene"].render.engine = 'CYCLES'
            bpy.data.scenes["Scene"].cycles.device = 'GPU'
        else:
            bpy.data.scenes["Scene"].render.engine = "BLENDER_EEVEE"
            bpy.data.scenes["Scene"].eevee.use_ssr = True # enable screen space reflections for rendering
            bpy.context.scene.eevee.use_gtao = True
        # needed to rescale 2d pixel coordinates later

        render = bpy.context.scene.render
        render.resolution_x = 1920
        render.resolution_y = 1080

        # to change viewport clip end value
        for a in bpy.context.screen.areas:
            if a.type == 'VIEW_3D':
                for s in a.spaces:
                    if s.type == 'VIEW_3D':
                        s.clip_start = 0.01
                        s.clip_end =600

        bpy.data.scenes["Scene"].cursor.rotation_mode = 'QUATERNION'

        # to remove all unused materials/particle systems
        bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=False)
        bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)

        bpy.ops.object.select_all(action='DESELECT')

        sun = bpy.data.objects.get("Sun", None)
        if sun==None:
            bpy.ops.object.light_add(type='SUN', radius=1, align='WORLD', location=(0, 0, 100), scale=(1, 1, 1))
        bpy.data.objects["Sun"].location[2] = 100

        # if have existing Camera_ViewPlane object, delete it
        for obj in bpy.data.objects:
            if obj.name == "Camera_ViewPlane":
                bpy.data.objects.remove(obj, do_unlink=True)

        # if have an existing 'Subject instances collection', delete it
        for col in bpy.data.collections:
            if col.name == "Subject instance collection":
                for obj in col.objects:
                    bpy.data.objects.remove(obj, do_unlink=True)

                bpy.data.collections.remove(col)

        collection = bpy.data.collections["Collection"].objects

        self.set_camera()

        #subject instantiation
        subject_name = bpy.data.scenes["Scene"].theChosenObject
        subject_choice = bpy.data.objects[subject_name]

        x_set, y_set = self.mark_cam_edges(context)
#        # get relevant ranges for subject instantiation

        x_top_lim = x_set[0]
        x_bot_lim = x_set[1]
        x_range = x_bot_lim - x_top_lim
        x_one_third = x_top_lim + x_range/3
        x_two_thirds = x_top_lim + 2 * x_range/3

        y_left_top_lim = y_set[0]
        y_left_bot_lim = y_set[1]
        y_right_bot_lim = y_set[2]
        y_right_top_lim = y_set[3]

        # continue x and y definitions for relevant ranges (9-section grid)
        y_left_range = y_left_bot_lim - y_left_top_lim
        y_right_range = y_right_bot_lim - y_right_top_lim # note this is -ve

        y_left_one_third = y_left_top_lim + y_left_range/3
        y_left_two_thirds = y_left_top_lim + 2 * y_left_range/3
        y_right_one_third = y_right_top_lim + y_right_range/3
        y_right_two_thirds = y_right_top_lim + 2 * y_right_range/3
        y_right_bot_lim = y_right_top_lim + y_right_range
        y_left_bot_lim = y_left_top_lim + y_left_range

        top_x_min = x_top_lim
        top_x_max = x_one_third
        mid_x_min = x_one_third
        mid_x_max = x_two_thirds
        bot_x_min = x_two_thirds
        bot_x_max = x_bot_lim

        y_top_range = y_right_top_lim - y_left_top_lim
        y_one_third_range = y_right_one_third - y_left_one_third
        y_two_thirds_range = y_right_two_thirds - y_left_two_thirds
        y_bot_range = y_right_bot_lim - y_left_bot_lim

        tl_y_min = y_left_one_third
        tl_y_max = y_left_top_lim + y_top_range/3
        tm_y_min = y_one_third_range/3 + y_left_one_third
        tm_y_max = 2 * y_one_third_range/3 + y_left_one_third
        tr_y_min = 2 * y_top_range/3 + y_left_top_lim
        tr_y_max = y_right_one_third

        ml_y_min = y_left_two_thirds
        ml_y_max = y_one_third_range/3 + y_left_one_third
        mm_y_min = y_two_thirds_range/3 + y_left_two_thirds
        mm_y_max = 2 * y_two_thirds_range /3 + y_left_two_thirds
        mr_y_min = 2 * y_one_third_range/3 + y_left_one_third
        mr_y_max = y_right_two_thirds

        bl_y_min = y_left_bot_lim
        bl_y_max = y_two_thirds_range/3 + y_left_two_thirds
        bm_y_min = y_bot_range/3 + y_left_bot_lim
        bm_y_max = 2 * y_bot_range/3 + y_left_bot_lim
        br_y_min = 2 * y_two_thirds_range /3 + y_left_two_thirds
        br_y_max = y_right_bot_lim

        # rescale the main plane
        subject_x_loc_min_lim = 0
        subject_x_loc_max_lim = 0
        subject_y_loc_min_lim = 0
        subject_y_loc_max_lim = 0

        # subject instantiation position
        if self.quadrant_enum == "TL":
            # TOP LEFT
            # subject is on top left
            subject_x_loc_min_lim = top_x_min
            subject_x_loc_max_lim = top_x_max
            subject_y_loc_min_lim = tl_y_min
            subject_y_loc_max_lim = tl_y_max

        if self.quadrant_enum == "TM":
            # TOP MIDDLE
            # subject is on top middle
            subject_x_loc_min_lim = top_x_min
            subject_x_loc_max_lim = top_x_max
            subject_y_loc_min_lim = tm_y_min
            subject_y_loc_max_lim = tm_y_max

        if self.quadrant_enum == "TR":
            # TOP RIGHT
            # subject is on top right
            subject_x_loc_min_lim = top_x_min
            subject_x_loc_max_lim = top_x_max
            subject_y_loc_min_lim = tr_y_min
            subject_y_loc_max_lim = tr_y_max

        if self.quadrant_enum == "ML":
            # MIDDLE LEFT
            # subject is on middle left
            subject_x_loc_min_lim = mid_x_min
            subject_x_loc_max_lim = mid_x_max
            subject_y_loc_min_lim = ml_y_min
            subject_y_loc_max_lim = ml_y_max

        if self.quadrant_enum == "MM":
            # MIDDLE MIDDLE
            # subject is on middle middle
            subject_x_loc_min_lim = mid_x_min
            subject_x_loc_max_lim = mid_x_max
            subject_y_loc_min_lim = mm_y_min
            subject_y_loc_max_lim = mm_y_max

        if self.quadrant_enum == "MR":
            # MIDDLE RIGHT
            # subject is on middle right
            subject_x_loc_min_lim = mid_x_min
            subject_x_loc_max_lim = mid_x_max
            subject_y_loc_min_lim = mr_y_min
            subject_y_loc_max_lim = mr_y_max

        if self.quadrant_enum == "BL":
            # BOTTOM LEFT
            # subject is on bottom left
            subject_x_loc_min_lim = bot_x_min
            subject_x_loc_max_lim = bot_x_max
            subject_y_loc_min_lim = bl_y_min
            subject_y_loc_max_lim = bl_y_max

        if self.quadrant_enum == "BM":
            # BOTTOM MIDDLE
            # subject is on bottom middle
            subject_x_loc_min_lim = bot_x_min
            subject_x_loc_max_lim = bot_x_max
            subject_y_loc_min_lim = bm_y_min
            subject_y_loc_max_lim = bm_y_max

        if self.quadrant_enum == "BR":
            # BOTTOM RIGHT
            # subject is on bottom right
            subject_x_loc_min_lim = bot_x_min
            subject_x_loc_max_lim = bot_x_max
            subject_y_loc_min_lim = br_y_min
            subject_y_loc_max_lim = br_y_max

        objects = bpy.data.objects

        # deselect all objects
        for i in range(len(bpy.data.objects)):
            bpy.data.objects[i].select_set(False)

        # to select the object in the 3D viewport/outliner,
        bpy.data.objects[subject_choice.name].select_set(True)
        # to make plane as active object
        bpy.context.view_layer.objects.active = bpy.data.objects[subject_choice.name]

        for i in range(self.subject_qty):
               bpy.ops.object.duplicate_move()

        context = bpy.context
        scene = context.scene
        # Global XY plane
        plane_co = (0, 0, 0)
        plane_no = (0, 0, 1)
        cam = scene.camera
        mw = cam.matrix_world
        o = mw.translation
        camdata = cam.data
        tr, br, bl, tl = [
        mw @ f for f in camdata.view_frame(scene=scene)]
        # offset vectors from bl.
        # eg middle would be bl + 0.5 * (x + y)
        x = tr - tl
        y = tr - br
        #        # roll around in CCW direction
        coords_2 = []
        counter = 0
        torus_coll = []
        for image_coord in ((0, 0), (0, 1), (1, 0), (1, 1)):
            counter+=1
            cx, cy = image_coord
            # vector pointing from cam origin thru image point (PERSP)
            v = (bl + (cx * x + cy * y)) - o
            pt = ilp(o, o +  v, plane_co, plane_no, True)
            if pt and (pt - o).dot(v) > 0:
                print("Adding torus at ", pt)
                bpy.ops.mesh.primitive_torus_add(
                        location=pt,
                        )
                torus = bpy.context.view_layer.objects.active
                torus_coll.append(torus)
                coords_2.append(pt)
            elif pt and (pt - o).dot(v) < 0:
                print("BEHIND THE CAMERA")
            else:
                print("NOT POSSIBLE")
        print("COORDS_2:", coords_2)

        x_set_2 = set()
        y_set_2 = set()
        for coord in coords_2:
            x_set_2.add(round(coord[0],3))
            y_set_2.add(round(coord[1],3))

        print("x_set:", x_set_2)
        print("y_set:", y_set_2)
        x_set_2 = sorted(x_set_2)
        y_set_2 = sorted(y_set_2)

        print("sorted x", x_set_2)
        print("sorted y", y_set_2)

        x_set = x_set_2
        y_set = y_set_2

        # instantiate subjects within the context of/using the camera view!
        x_top_lim = x_set[0]
        x_bot_lim = x_set[1]
        x_range = x_bot_lim - x_top_lim
        x_one_third = x_top_lim + x_range/3
        x_two_thirds = x_top_lim + 2 * x_range/3

        y_left_top_lim = y_set[0]
        y_left_bot_lim = y_set[1]
        y_right_bot_lim = y_set[2]
        y_right_top_lim = y_set[3]

        # continue x and y definitions for relevant ranges (9-section grid)
        y_left_range = y_left_bot_lim - y_left_top_lim
        y_right_range = y_right_bot_lim - y_right_top_lim # note this is -ve

        y_left_one_third = y_left_top_lim + y_left_range/3
        y_left_two_thirds = y_left_top_lim + 2 * y_left_range/3
        y_right_one_third = y_right_top_lim + y_right_range/3
        y_right_two_thirds = y_right_top_lim + 2 * y_right_range/3
        y_right_bot_lim = y_right_top_lim + y_right_range
        y_left_bot_lim = y_left_top_lim + y_left_range

        top_x_min = x_top_lim
        top_x_max = x_one_third
        mid_x_min = x_one_third
        mid_x_max = x_two_thirds
        bot_x_min = x_two_thirds
        bot_x_max = x_bot_lim

        y_top_range = y_right_top_lim - y_left_top_lim
        y_one_third_range = y_right_one_third - y_left_one_third
        y_two_thirds_range = y_right_two_thirds - y_left_two_thirds
        y_bot_range = y_right_bot_lim - y_left_bot_lim

        tl_y_min = y_left_one_third
        tl_y_max = y_left_top_lim + y_top_range/3
        tm_y_min = y_one_third_range/3 + y_left_one_third
        tm_y_max = 2 * y_one_third_range/3 + y_left_one_third
        tr_y_min = 2 * y_top_range/3 + y_left_top_lim
        tr_y_max = y_right_one_third

        ml_y_min = y_left_two_thirds
        ml_y_max = y_one_third_range/3 + y_left_one_third
        mm_y_min = y_two_thirds_range/3 + y_left_two_thirds
        mm_y_max = 2 * y_two_thirds_range /3 + y_left_two_thirds
        mr_y_min = 2 * y_one_third_range/3 + y_left_one_third
        mr_y_max = y_right_two_thirds

        bl_y_min = y_left_bot_lim
        bl_y_max = y_two_thirds_range/3 + y_left_two_thirds
        bm_y_min = y_bot_range/3 + y_left_bot_lim
        bm_y_max = 2 * y_bot_range/3 + y_left_bot_lim
        br_y_min = 2 * y_two_thirds_range /3 + y_left_two_thirds
        br_y_max = y_right_bot_lim

        subject_x_loc_min_lim = 0
        subject_x_loc_max_lim = 0
        subject_y_loc_min_lim = 0
        subject_y_loc_max_lim = 0

        # subject instantiation position
        if self.quadrant_enum == "TL":
            # TOP LEFT
            # subject is on top left
            subject_x_loc_min_lim = top_x_min
            subject_x_loc_max_lim = top_x_max
            subject_y_loc_min_lim = tl_y_min
            subject_y_loc_max_lim = tl_y_max

        if self.quadrant_enum == "TM":
            # TOP MIDDLE
            # subject is on top middle
            subject_x_loc_min_lim = top_x_min
            subject_x_loc_max_lim = top_x_max
            subject_y_loc_min_lim = tm_y_min
            subject_y_loc_max_lim = tm_y_max

        if self.quadrant_enum == "TR":
            # TOP RIGHT
            # subject is on top right
            subject_x_loc_min_lim = top_x_min
            subject_x_loc_max_lim = top_x_max
            subject_y_loc_min_lim = tr_y_min
            subject_y_loc_max_lim = tr_y_max

        if self.quadrant_enum == "ML":
            # MIDDLE LEFT
            # subject is on middle left
            subject_x_loc_min_lim = mid_x_min
            subject_x_loc_max_lim = mid_x_max
            subject_y_loc_min_lim = ml_y_min
            subject_y_loc_max_lim = ml_y_max

        if self.quadrant_enum == "MM":
            # MIDDLE MIDDLE
            # subject is on middle middle
            subject_x_loc_min_lim = mid_x_min
            subject_x_loc_max_lim = mid_x_max
            subject_y_loc_min_lim = mm_y_min
            subject_y_loc_max_lim = mm_y_max

        if self.quadrant_enum == "MR":
            # MIDDLE RIGHT
            # subject is on middle right
            subject_x_loc_min_lim = mid_x_min
            subject_x_loc_max_lim = mid_x_max
            subject_y_loc_min_lim = mr_y_min
            subject_y_loc_max_lim = mr_y_max

        if self.quadrant_enum == "BL":
            # BOTTOM LEFT
            # subject is on bottom left
            subject_x_loc_min_lim = bot_x_min
            subject_x_loc_max_lim = bot_x_max
            subject_y_loc_min_lim = bl_y_min
            subject_y_loc_max_lim = bl_y_max

        if self.quadrant_enum == "BM":
            # BOTTOM MIDDLE
            # subject is on bottom middle
            subject_x_loc_min_lim = bot_x_min
            subject_x_loc_max_lim = bot_x_max
            subject_y_loc_min_lim = bm_y_min
            subject_y_loc_max_lim = bm_y_max

        if self.quadrant_enum == "BR":
            # BOTTOM RIGHT
            # subject is on bottom right
            subject_x_loc_min_lim = bot_x_min
            subject_x_loc_max_lim = bot_x_max
            subject_y_loc_min_lim = br_y_min
            subject_y_loc_max_lim = br_y_max

        objects = bpy.data.objects

        # deselect all objects
        for i in range(len(bpy.data.objects)):
            bpy.data.objects[i].select_set(False)

        # to select the object in the 3D viewport/outliner,
        bpy.data.objects[subject_choice.name].select_set(True)
        # to make plane as active object
        bpy.context.view_layer.objects.active = bpy.data.objects[subject_choice.name]

        si_coll = self.create_subject_instances(subject_choice, subject_x_loc_min_lim, subject_x_loc_max_lim, subject_y_loc_min_lim, subject_y_loc_max_lim )
        self.prevent_subjects_collision(si_coll, subject_x_loc_min_lim, subject_x_loc_max_lim, subject_y_loc_min_lim, subject_y_loc_max_lim)

        self.rotate_subject_instances()

        self.generate_sample_collection()

        # choose environment template for background objects collection instantiation

        col = bpy.data.scenes["Scene"].env_coll

        # deselect all objects
        for i in range(len(bpy.data.objects)):
            bpy.data.objects[i].select_set(False)

        coords_2 = [coords_2[0], coords_2[1], coords_2[3], coords_2[2]]

        camera_viewer = self.create_camera_viewplane(coords_2)

        camera_viewer.select_set(True)
        bpy.context.view_layer.objects.active = camera_viewer

        """
        TO MAKE MATERIAL SHADER FOR FOREST BASE
        """
        forest_base_colorramp_pos_0 = random.uniform(0.2,0.91) # has to be < pos1
        forest_base_colorramp_pos_1 = forest_base_colorramp_pos_0 + random.uniform(0.1,1-forest_base_colorramp_pos_0)

        forest_base_noise_scale = 7.0 # TO MAKE RANGES for this section
        forest_base_noise_detail = 4.0
        forest_base_noise_roughness = 1.0
        forest_base_noise_distortion = 0.0

        # (not recommended to vary much)
        forest_base_specular = 0.08
        col1  =self.colour_1
        col2 = self.colour_2
        forest_base_colorramp_col_0 = (col1[0],col1[1],col1[2],1.0) # dark green
        forest_base_colorramp_col_1 = (col2[0],col2[1],col2[2],1.0) # dark brown

        material_basic = bpy.data.materials.new(name="Basic")
        material_basic.use_nodes = True

        bpy.context.object.active_material = material_basic

        # Access default shader node
        principled_node = material_basic.node_tree.nodes.get("Principled BSDF")
        principled_node.inputs[0].default_value = (0,0,1,1)
        principled_node.inputs[7].default_value = forest_base_specular

        # Create colorramp node
        colorramp_node = material_basic.node_tree.nodes.new("ShaderNodeValToRGB")
        colorramp_node.location = (-250,0) # left of principledBSDF node

        # Create noise texture node
        noise_node = material_basic.node_tree.nodes.new("ShaderNodeTexNoise")
        noise_node.location = (-500, 0) # left of principledBSDF and ColorRamp nodes

        # shortcut to make links between nodes :D
        link = material_basic.node_tree.links.new

        # make links between nodes
        link(colorramp_node.outputs[0], principled_node.inputs[0])
        link(colorramp_node.outputs[0], principled_node.inputs[0])
        link(noise_node.outputs[1], colorramp_node.inputs[0])

        # Set color
        colorramp_node.color_ramp.elements[0].color = forest_base_colorramp_col_0 # dark green
        colorramp_node.color_ramp.elements[1].color = forest_base_colorramp_col_1 # dark brown

        # Set relative proportion of colors (amount)
        colorramp_node.color_ramp.elements[0].position = forest_base_colorramp_pos_0 # value approach pos of elem[1] for more green
        colorramp_node.color_ramp.elements[1].position = forest_base_colorramp_pos_1 # value approach pos of elem[0] for more brown

        # Set shape of color distribution
        material_basic.node_tree.nodes["Noise Texture"].inputs[2].default_value = forest_base_noise_scale  # scale
        material_basic.node_tree.nodes["Noise Texture"].inputs[3].default_value = forest_base_noise_detail # detail
        material_basic.node_tree.nodes["Noise Texture"].inputs[4].default_value = forest_base_noise_roughness # roughness
        material_basic.node_tree.nodes["Noise Texture"].inputs[5].default_value = forest_base_noise_distortion # distortion

        material_basic.node_tree.nodes["Noise Texture"].noise_dimensions = '2D'

        bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=False)
        bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)

        bpy.ops.object.particle_system_add()

        particle_count = self.particle_count
        particle_scale = self.particle_scale

        #ps_name = "ps1"
        bpy.data.particles[-1].type = "HAIR"
        bpy.data.particles[-1].use_advanced_hair = True
        # EMISSION
        bpy.data.particles[-1].count = particle_count # param
        camera_viewer.particle_systems[-1].seed = random.randint(0,10000)  # param(?) # seed to change random locations of particles

        bpy.data.particles[-1].use_scale_instance = True
        bpy.data.particles[-1].use_rotation_instance = True
#        bpy.data.particles[-1].use_global_instance = True
        bpy.data.particles[-1].use_global_instance=False

        #RENDER
        bpy.data.particles[-1].render_type = "COLLECTION"
        bpy.data.particles[-1].instance_collection = col
        bpy.data.particles[-1].use_collection_count = True

#                bpy.data.particles[-1].render_type = "OBJECT"
#                bpy.data.objects["Plane"].show_instancer_for_render = True # show emitter i.e. forest base plane
#                bpy.data.particles[-1].instance_object = obj #"tree1.001" #obj name will be the foilage object
        bpy.data.particles[-1].particle_size = particle_scale # param (0.1 to 0.5?)
#                bpy.data.particles[-1].size_random = 0.5 # param (0 to 0.5)
        # ROTATION
        bpy.data.particles[-1].use_rotations = True # param
        bpy.data.particles[-1].rotation_mode = "GLOB_Z"
        bpy.data.particles[-1].phase_factor_random = 2.0 # param (0 to 2.0)

        self.remove_camera_markers(torus_coll) # remove Torus corner markers

        # code to render image

        if self.render_image:

            # to create folder, named by current timestamp, to store labelsjson & rendered images
            current_datetime = datetime.datetime.utcnow()
            current_timetuple = current_datetime.utctimetuple()
            current_timestamp = str(calendar.timegm(current_timetuple))

            fp = bpy.data.scenes["Scene"].render.filepath
    #        path = fp + current_timestamp + "/"
    #        imgs_folder_path = path + "data/"
            imgs_folder_path = fp + "render_images/"

    #        scene.render.filepath = imgs_folder_path + str(i+1) + ".jpg"
            scene.render.filepath = imgs_folder_path + current_timestamp+".jpg"
            filepath = scene.render.filepath
            # image files are saved for each frame specified
            bpy.ops.render.render(write_still=True)

            # restore the filepath # NOT SURE IF NECESSARY
            bpy.data.scenes["Scene"].render.filepath = fp

        return {"FINISHED"}

classes = (
    generateScenePanel,

#    ADDONNAME_PT_TemplatePanel,
#    InitMyPropOperator,
    ADDONOPTIONS,
#    CustomPanel,
)

def register():
#    bpy.utils.register_module(__name__)

    from bpy.utils import register_class

    # not sure if the next lines are necessary...
    bpy.types.Scene.theChosenObject = bpy.props.StringProperty()
    bpy.types.Scene.env_coll = bpy.props.PointerProperty(type=bpy.types.Collection)

    for cls in classes:
        register_class(cls)
    # Register QueryProps
#    bpy.types.Scene.QueryProps = bpy.props.PointerProperty(type=QueryProps)
#    bpy.utils.register_class(CustomPanel)
#    bpy.types.Scene.CustomPanel = bpy.props.PointerProperty(type = Panel)
#    bpy.types.Scene.CustomPanel = bpy.props.PointerProperty(type=CustomPanel)

#    del bpy.types.Scene.my_prop
    bpy.types.Scene.my_prop = bpy.props.StringProperty(default="default value")

def unregister():
#    bpy.utils.unregister_module(__name__)

    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)
#    for cls in classes:
#        unregister_class(cls)
    # $ delete QueryProps on unregister
    # del(bpy.types.Scene.QueryProps)

    # not sure if the next lines are necessary
#    del bpy.types.Object.theChosenObject

#    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    register()


"""
Input: a csv file containing disease-index relationship
        a csv file containing cluster points(the ith row represents the ith disease)
        a csv file containing cluster id and cluster points
    # Fields:
    # GOName,GOID,DiseaseName,DiseaseID,InferenceChemicalQty,InferenceChemicalNames,InferenceGeneQty,InferenceGeneSymbols
Output: a csv file containing disease-disease-similarity pairs(use Jaccard similarity)
"""
import sys
import numpy as np
import random

random.seed(3)

def point_index_dict(pointfile):
    point_dict = {}
    i = 0
    with open(pointfile, 'r') as f:
        line = f.readline()
        while line:
            if line[-1] == '\n':
                line = line[:-1]
            point_dict[i] = line.split(',')[0]
            i += 1
            line = f.readline()
    print("i: ", i)
    return point_dict # index: point

def get_disease_id(didfile):
    did = {}
    with open(didfile, 'r') as f:
        line = f.readline()
        while line:
            if line[-1] == '\n':
                line = line[:-1]
            did[line.split(',')[1]] = line.split(',')[0]
            line = f.readline()
    return did # id: disease

def read_point_cluster(clusterfile):
    cluster_dict = {}
    with open(clusterfile, 'r') as f:
        line = f.readline()
        while line:
            if line[-1] == '\n':
                line = line[:-1]
            point = line.split(',')[1]
            cluster = line.split(',')[0]
            cluster_dict[point] = int(cluster)
            line = f.readline()
    print(len(cluster_dict))
    return cluster_dict # point: cluster

def disease_index(indexfile):
    index_dict = {}
    with open(indexfile, 'r') as f:
        line = f.readline()
        while line:
            if line[-1] == '\n':
                line = line[:-1]
            index_dict[line.split(',')[1]] = int(line.split(',')[0])
            line = f.readline()
    print(len(index_dict))
    return index_dict # disease: index

def disease_cluster(point_dict, cluster_dict, index_dict, did):
    disease_cluster_dict = {}
    for d in did:
        if did[d] in index_dict:
            disease_cluster_dict[d] = cluster_dict[point_dict[index_dict[did[d]]]]
    return disease_cluster_dict

def disease_c(index_point_dict, point_cluster_dict, disease_index_dict):
    dis_c = {}
    for disease in disease_index_dict:
        dis_c[disease] = point_cluster_dict[index_point_dict[disease_index_dict[disease]]]
    return dis_c

def write_result(outfile="out", disease_cluster_dict=None):
    if disease_cluster_dict is None:
        disease_cluster_dict = {1: {1}}
    sorted_dcd = {k: v for k, v in sorted(disease_cluster_dict.items(), key=lambda item: item[1])}
    res = ''
    for disease in sorted_dcd:
        res += disease
        res += ','
        res += str(sorted_dcd[disease])
        res += '\n'
    res = res[: -1]
    print(res)
    with open(outfile, "w") as f:
        f.write(res)

def main():
    index_point_dict = point_index_dict('disease_cluster_data/index_point.csv')
    point_cluster_dict = read_point_cluster('disease_cluster_data/point_cluster.csv')
    disease_index_dict = disease_index('disease_cluster_data/disease_index.csv')
    did = get_disease_id('disease_cluster_data/disease_id.csv')
    dcd = disease_cluster(index_point_dict, point_cluster_dict, disease_index_dict, did)
    d = disease_c(index_point_dict, point_cluster_dict, disease_index_dict)
    write_result('o5_diseaseID_cluster.csv', dcd)
    write_result('o5_disease_cluster.csv', d)

if __name__ == '__main__':
    main()