from . import utils
import json
import os.path

def set_single_val(val, unit=None,
                   special={'true': True, 'false': False, 'none': None, 'null': None}):
    """Set a single value from config input."""
    if isinstance(val, str):
        if val.lower() in special.keys():
            val = special[str(val).lower()]
        elif ',' in val:
            val = val.split(',')
        elif '.' in val or 'e' in val or 'E' in val:
            try:
                val = float(val)
            except ValueError:
                pass
        else:
            try:
                val = int(val)
            except ValueError:
                pass
    if isinstance(val, float):
        val = utils.convert_unit(val, unit)

    return val

default_config_files = [os.path.join(os.path.dirname(__file__), 'default_config.json'),
                        os.path.join(os.path.dirname(__file__), 'default_state.json')]

class planetConfig:
    """Initializes, reads in, updates and displays the configuration."""

    def __init__(self, planet, configFile):
        """Read in config file."""
        planet = planet.capitalize()
        self.planet = planet
        self.filename = configFile
        self.path = planet

        # Set defaults
        self.toks = {}
        for json_file in default_config_files:
            with open(json_file, 'r') as f:
                config_data = json.load(f)
            for tok, fval in config_data['toks'].items():
                self.toks[tok] = fval['name']
                pval = fval['default'][self.planet]
                if isinstance(pval, (str, float)):
                    pval = set_single_val(pval, fval['unit'])
                setattr(self, fval['name'], pval)
        self.setConfig(configFile)

    def __str__(self):
        """Return string."""
        return self.show()

    def setConfig(self, configFile):
        """
        Read in config files and update after defaults set in __init__.

        These are all shown in print(<instance>)
        """
        if configFile is None:
            print('Config file not provided.  Using defaults.')
            return 0
        try:
            fp = open(configFile, 'r')
        except IOError:
            print(configFile, ' not found.  Using defaults.')
            return 0

        for line in fp:
            if line[0] in utils.commentChars or len(line) < 4:
                continue
            if '#' in line:
                line = line[:line.index('#')]
            data = line.split()
            tok = data[0].lower()
            del(data[0])
            try:
                preval = getattr(self, self.toks[tok])
            except ValueError:
                print('token {} ({}) not found'.format(tok, self.toks[tok]))
                continue
            if isinstance(preval, (str, float, int)):
                unit = 'none'
                if len(data) == 2:
                    unit = data[1]
                if len(data):
                    val = set_single_val(data[0], unit)
                else:
                    print("{} didn't have an associated argument in config file.".format(tok))
                    print("Retaining  {}".format(preval))
                    continue
            elif isinstance(preval, list):
                if len(data) == 1 and ',' in data[0]:
                    data = data[0].split(',')
                val = [set_single_val(x) for x in data]
            elif isinstance(preval, dict):
                val = preval
                for i, v in enumerate(data):
                    if ':' in v:
                        frml = v.split(':')
                        val[frml[0].lower().strip()] = set_single_val(frml[1].lower().strip())
                    else:
                        val[v.strip()] = i
            else:
                print("Incorrect type:  {} <{}>".
                      format(tok, type(preval)))
                continue
            setattr(self, self.toks[tok], val)
        fp.close()

        if 'DZ' not in self.C.keys():
            self.C['DZ'] = len(self.C.keys())
        if 'DZ' not in self.Cl.keys():
            self.Cl['DZ'] = len(self.Cl.keys())

        try:
            fp = open(self.zonal, 'r')
            self.vwlat = []
            self.vwdat = []
            for line in fp:
                data = line.split()
                self.vwlat.append(float(data[0]))
                self.vwdat.append(float(data[1]))
            fp.close()
        except IOError:
            self.vwlat = [0.0, 90.0]
            self.vwdat = [0.0, 0.0]

    def update_config(self, key=None, value=None, **kwargs):
        """
        Update the config file in a variety of ways.

        Parameters
        ----------
        key : any
            If dict: updates based on key/value
            If list: updates assuming value is same length list
            If str: updates based on key, value
        value : any
            See above for key
        override : bool
            Allow for new config entry to be made.
        kwargs : key = val
        """
        proc_key = []
        proc_val = []
        if key is None:
            pass
        elif isinstance(key, dict):
            for k, v in key.items():
                proc_key.append(k)
                proc_val.append(v)
        elif not isinstance(key, list):
            proc_key.append(key)
            proc_val.append(value)
        else:
            if len(key) != len(value):
                print("key/value pairs not matched.")
            else:
                for k, v in zip(key, value):
                    proc_key.append(k)
                    proc_val.append(v)
        for k, v in kwargs.items():
            proc_key.append(k)
            proc_val.append(v)

        for k, v in zip(proc_key, proc_val):
            if k in self.toks.keys():
                tok = self.toks[k]
            else:
                tok = k
            try:
                preval = getattr(self, tok)  # noqa
            except ValueError:
                print("{} not in config keys and override is False".format(tok))
                continue
            setattr(self, tok, set_single_val(v))

    def show(self, print_it=True):
        """Return string containing configuration."""
        s = 'Run parameters:\n'
        keys = list(self.toks.keys())
        keys.sort()
        for key in keys:
            s += '\t{:20s}:  {}\n'.format(key, str(getattr(self, self.toks[key])))
        if print_it:
            print(s)
        return s


import random
import sys
import time
import pygame
import heapq
import math
from pygame.locals import *
from heapq import *

LIGHT = (100, 100, 100)
DARK = (200, 200, 200)      #
BLACK = (0, 0, 0)           #
RED = (200, 30, 30)         #
BGCOLOR = (40, 40, 60)      #
WIDTH = 800
HEIGHT = 600
NUMBER = 3

class Board:
    def __init__(self,titles):
        if titles is None or len(titles) <= 0:
            return False
        self.blocks = []
        self.dimension = len(titles)
        for i in range(self.dimension):
            row = []
            for j in range(self.dimension):
                row.append(titles[i][j])
            self.blocks.append(row)

    def toString(self):
        res = ""
        for i in range(len(self.blocks)):
            for j in range(len(self.blocks[i])):
                res += str(self.blocks[i][j])
            res += "\n"
        return res

    def isBlank(self,x,y):
        if x < 0 or x >= self.dimension:
            return True
        if y < 0 or y >= self.dimension:
            return True
        return self.blocks[x][y] == 0

    def hamming(self):
        count = 0
        for i in range(self.dimension):
            for j in range(self.dimension):
                if self.blocks[i][j] != (i*self.dimension + j + 1) and not self.isBlank(i,j):
                    count += 1
        return count

    def manhattan(self):
        count = 0
        for i in range(self.dimension):
            for j in range(self.dimension):
                correctrow = (self.blocks[i][j] - 1)//self.dimension
                correctcol = (self.blocks[i][j] - 1)%self.dimension
                if not self.isBlank(i,j):
                    count += abs(correctrow-i) + abs(correctcol-j)
        return count

    def isGoal(self):
        return self.hamming() == 0

    def equals(self,other):
        if other is None:
            return False
        if self.dimension != other.dimension:
            return False
        return self.isArrayEqual(self.blocks,other.blocks)

    def isArrayEqual(self,a,b):
        if len(a) != len(b):
            return False

        for i in range(len(a)):
            if len(a[i]) != len(b[i]):
                return False
            for j in range(len(a[i])):
                if a[i][j] != b[i][j]:
                    return False

        return True

    def setBlocks(self,x,y,val):
        self.blocks[x][y] = val

    def getBlocks(self,x,y):
        return self.blocks[x][y]

    def neighbors(self):
        res = []
        blankRow = 0
        blankCol = 0

        for i in range(self.dimension):
            for j in range(self.dimension):
                if self.isBlank(i,j):
                    blankRow = i
                    blankCol = j
                    break

        d = [-1, 0, 1, 0, -1]
        for i in range(4):
            x = blankRow + d[i]
            y = blankCol + d[i + 1]
            if x < 0 or x >= self.dimension:
                continue
            if y < 0 or y >= self.dimension:
                continue
            self.setBlocks(blankRow, blankCol,self.getBlocks(x,y))
            self.setBlocks(x, y,0)
            res.append(Board(self.blocks))
            self.setBlocks(x, y, self.getBlocks(blankRow,blankCol))
            self.setBlocks(blankRow, blankCol, 0)

        return res

    def twin(self):
        for i in range(self.dimension):
            for j in range(self.dimension):
                if not self.isBlank(i,j) and not self.isBlank(i,j+1):
                    v1 = self.blocks[i][j]
                    v2 = self.blocks[i][j+1]
                    self.setBlocks(i,j,v2)
                    self.setBlocks(i,j+1,v1)
                    newboard = Board(self.blocks)
                    self.setBlocks(i,j,v1)
                    self.setBlocks(i,j+1,v2)
                    return newboard

        return None

class SearchNode:
    def __init__(self,state,moves,previous):
        self.state = Board(state.blocks)
        self.previous = previous
        self.moves = moves
        self.priority = self.moves + state.manhattan()

    def __gt__(self,other):
        return self.priority > other.priority

    def __ge__(self,other):
        return self.priority >= other.priority

    def __le__(self,other):
        return self.priority <= other.priority

    def __lt__(self,other):
        return self.priority < other.priority

class Slover:
    def __init__(self,initial):
        self.canSlove = True
        self.seqlist = []
        primaryQ = []
        twinQ = []

        primaryStart = SearchNode(initial,0,None)
        twinStart = SearchNode(initial.twin(),0,None)
        heappush(primaryQ,primaryStart)
        heappush(twinQ,twinStart)

        while not primaryQ[0].state.isGoal() and not twinQ[0].state.isGoal():
            primaryCurr = heappop(primaryQ)
            self.addNeigborsToQ(primaryCurr,primaryQ)
            twinCurr = heappop(twinQ)
            self.addNeigborsToQ(twinCurr,twinQ)

        if twinQ[0].state.isGoal():
            self.canSlove = False
            return

        if primaryQ[0].state.isGoal():
            self.canSlove = True
            node = primaryQ[0]
            while node is not None:
                self.seqlist.append(node.state)
                node = node.previous

    def addNeigborsToQ(self,curr,pq):
        prev = None
        if curr.previous is not None:
            prev = curr.previous.state
        for neg in curr.state.neighbors():
            if neg.equals(prev):
                continue
            heappush(pq,SearchNode(neg,curr.moves + 1,curr))

    def isSolvable(self):
        return self.canSlove

    def moves(self):
        if not self.isSolvable():
            return -1
        return len(self.seqlist)-1

    def solution(self):
        return self.seqlist

initial = [[1,2,0],[4,8,3],[7,6,5]]
if __name__ == '__main__':
    start = Board(initial)
    s = Slover(start)
    for x in s.solution():
        print(x.toString())
    #for i in range(len(seqlist)):
    #    print(seqlist[i].state.blocks)


from rich.panel import Panel
from rich.console import RenderableType, StyleType

from textual.app import App
from textual.reactive import Reactive
from textual.widgets import Button
from textual.events import Click

class ButtonBox(Button):

    def __init__(
        self,
        label: RenderableType,
        name: str | None = None,
        style: StyleType = "bold white",
        hover_style: StyleType = "bold white on dark_green",
        press_style: StyleType = "bold white on green",
        disabled_style: StyleType = "bold grey50",
        enabled: bool = True,
    ):
        super().__init__(label=label, name=name, style=style)
        self.hover_style = hover_style
        self.press_style = press_style
        self.disabled_style = disabled_style
        self.enabled = enabled

    mouse_over = Reactive(False)
    mouse_press = Reactive(False)

    def render(self) -> Panel:
        border_style = None
        if self.enabled:
            if self.mouse_over:
                if self.mouse_press:
                    style = self.press_style
                else:
                    style = self.hover_style
            else:
                style = self.button_style
        else:
            style = self.disabled_style
            border_style = "gray70"

        return Panel(self.label, style=style)

    def on_enter(self) -> None:
        self.mouse_over = True

    def on_leave(self) -> None:
        self.mouse_over = False
        self.mouse_press = False

    def on_mouse_down(self) -> None:
        self.mouse_press = True

    def on_mouse_up(self) -> None:
        self.mouse_press = False

    def disable(self) -> None:
        self.enabled = False

    def enable(self) -> None:
        self.enabled = True

    async def on_click(self, event: Click) -> None:
        if self.enabled:
            return await super().on_click(event)


#! /usr/bin/env python
"""
Learning Series: Network Programmability Basics
Module: Programming Fundamentals
Lesson: Python Part 1
Author: Hank Preston <hapresto@cisco.com>

example2.py
Illustrate the following concepts:
- Reading from and writing to files
- The "with" statement
- Writing to command line
- Requesting interactive user input
"""

__author__ = "Hank Preston"
__author_email__ = "hapresto@cisco.com"
__copyright__ = "Copyright (c) 2016 Cisco Systems, Inc."
__license__ = "MIT"

from datetime import datetime

log_file = "example2.log"

def read_log(log):
    """
    Open the logfile and print contents to the terminal
    """
    with open(log, "r") as f:
        print(f.read())

def write_log(log, name):
    """
    Add new logfile entry with datestamp
    """
    # Get current date and time
    log_time = str(datetime.now())
    with open(log, "a") as f:
        f.writelines("Entry logged at: {} by {}\n".format(log_time, name))

# Entry point for program
if __name__ == '__main__':
    # Get users name
    name = input("What is your name? ")

    # Add entry to log file
    print("Adding new log entry")
    write_log(log_file, name)
    print("")

    # Access Starting Log File
    print("Log File Contents")
    print("-----------------")
    read_log(log_file)


import sys
import gzip
import json

infile = sys.argv[1]
vocabfile = sys.argv[2]
n = int(sys.argv[3])

with open(infile) as f:
    events = json.load(f)

with gzip.open(vocabfile) as f:
    vocab = json.load(f)
reverse_vocab = {j:i for i,j in vocab.items()}

queries = '''dog barked
glass fell
glass broke
kid fell
obama spoke
congress passed law
he gave book her
hogwarts closing
astronauts launched space_shuttle
john bought milk'''.split('\n')

for key in sorted(events):
    goodEvents = 0
    print(key, queries[int(key)])
    for event,PMI in sorted(events[key], key=lambda x:x[-1])[::-1]:
        if len(event[1]) != 1 or len(event[2]) > 1 or len(event[3]) > 1:
            continue

        predicate = reverse_vocab[event[0]]
        subjects = reverse_vocab[event[1][0]]
        objects = None
        if len(event[2]):
            objects = reverse_vocab[event[2][0]]
        iobjects = None
        if len(event[3]):
            iobjects = reverse_vocab[event[3][0]]

        print(subjects, predicate, objects, iobjects, PMI)
        goodEvents += 1

        if goodEvents > n:
            break


with open('moby.txt') as f:
    wcount = 0
    lnum = 0
    for line in f:
        lnum = lnum + 1
        sentence = line.strip()
        for i in range(0, len(sentence)):
            if sentence[i:i+5] == "whale":
                wcount = wcount + 1
                print("Line " + str(lnum))
            elif sentence[i:i+5] == "WHALE":
                wcount = wcount + 1
                print("Line " + str(lnum))
            elif sentence[i:i+5] == "Whale":
                wcount = wcount + 1
                print("Line " + str(lnum))
    print("Whale count: " + str(wcount))

f.close()


import socket
import os

os.system('fuser -k 5004/tcp')

TCP_IP = "127.0.1.1"
TCP_PORT = 6009
BUFFER_SIZE = 20  # Normally 1024, but we want fast response

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
s.listen(1)

client_num = 0

while True:
    client_num += 1
    conn, addr = s.accept()
    conn.settimeout(10)
    data = conn.recv(BUFFER_SIZE)
    if data:
        conn.settimeout(None)
        conn.send(b"welcome")
        print(f"client is {client_num} active")
        try:
            while True:
                data = conn.recv(BUFFER_SIZE)
                if data == b'q':
                    conn.close()
                    print(f"client is {client_num} stop")
                    break
                if data:
                    print("here is the message:",data)
                else:
                    conn.close()
                    break
        except Exception as e:
            conn.close()
            print("error is",e)
    else:
        print("client is inactive")


import gspread
from google.oauth2.service_account import Credentials

SCOPE = [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/drive.file",
    "https://www.googleapis.com/auth/drive"
    ]

CREDS = Credentials.from_service_account_file('creds.json')
SCOPED_CREDS = CREDS.with_scopes(SCOPE)
GSPREAD_CLIENT = gspread.authorize(SCOPED_CREDS)
SHEET = GSPREAD_CLIENT.open('sweet_cake_for_all1')

def get_sales_data():
    """
     Get user input figures from the daily sales data
     a while loop will run until it get the valid data figure from the user
     which is a string separated by comma,
    """
    while True:
        print("please enter sales data from the last market day.")
        print("Data should be six figures, and separated by commas.")
        print("Example:50,55,65,40,34,40\n")

        data_str = input("Please enter your sales data here:\n")

        sales_data = data_str.split(",")

        if validate_data(sales_data):
            print("Data is valid!")
            break

    return sales_data

def validate_data(values):
    """
    Inside the try except statement, converts all string values into intgers.
    if strings cannot be converted into integer,raises value error,or values
    are not exactly 6 positive numbers.
    """
    try:
        [int(value)for value in values]
        if len(values) != 6:
            raise ValueError(
                f"6 values are required, you provided {len(values)}"
            )
        for value in values:
            if int(value) < 0:
                raise ValueError(
                    f"6 values should be greater than 0")
    except ValueError as e:
        print(f"Invalid data: {e}, please try again.\n")
        return False
    return True

def update_worksheet(data, worksheet):
    """
    receives a lsit of integers to be inserted to a worksheet
    update the specified worksheet with the data provided.
    """
    print(f"Updating {worksheet} worksheet...\n")
    worksheet_to_update = SHEET.worksheet(worksheet)
    worksheet_to_update.append_row(data)
    print(f"{worksheet} Work sheet updated successfully!\n")
    print(f"{worksheet} please click the below link to view the update\n")
    print(f"{worksheet} https://tinyurl.com/24wwjfce\n")

def calculate_surplus_data(sales_row):
    """
    Compare sales with stock and calculate for each value
    surplus is the difference of stock and sales
    positive surplus shows wastage
    negative surplus showes additional sales
    """
    print("Calculating surplus data...\n")
    stock = SHEET.worksheet("stock").get_all_values()
    stock_row = stock[-1]
    surplus_data = []
    for stock, sales in zip(stock_row, sales_row):
        surplus = int(stock) - sales
        surplus_data.append(surplus)
    return surplus_data

def get_last_5_entries_sales():
    """
    Collects columens of data from sales worksheet, collecting
    the last five entries for each cake and returns the data as
    list of lists
    """
    sales = SHEET.worksheet("sales")
    columns = []
    for ind in range(1, 7):
        column = sales.col_values(ind)
        columns.append(column[-5:])
    return columns

def calculate_stock_data(data):
    """
    calculate teh average stock for each item type adding 25%
    """
    print("Calculating stock data...\n")
    new_stock_data = []

    for column in data:
        int_column = [int(num) for num in column]
        average = sum(int_column)/5
        stock_num = average * 2.5
        new_stock_data.append(round(stock_num))
    return new_stock_data

def main():
    """
    Run all program function
    """
    data = get_sales_data()
    sales_data = [int(num) for num in data]
    update_worksheet(sales_data, "sales")
    new_surplus_data = calculate_surplus_data(sales_data)
    update_worksheet(new_surplus_data, "surplus")
    sales_columns = get_last_5_entries_sales()
    stock_data = calculate_stock_data(sales_columns)
    update_worksheet(stock_data, "stock")

if __name__ == "__main__":
    print("Welcome to Sweet Cake for all1 Data Automation!")
    main()


"""
Defines class Sandwich and tests it by creating sandwich objects.
The class has methods for specifying items added to the sandwich,
and for specifying the type of bread and whether the bread
should be toasted or not. The class has a method to calculate
the proce of the sandwich.
"""

class Sandwich:
    ''' One object of this class represents a sandwich
    with its ingredients'''

    def __init__(self, name):
        '''Initializes a single new Sandwich object by
        storing the name of the customer as its instance variable name.
        Assigns the initial value of None to all of the other instance variables'''
        self.name = name
        self.bread = None
        self.cheese = None
        self.meat = None
        self.condiments = None
        self.toasted = False

    def setBread(self, breadName):
        '''Sets the type of bread instance variable'''
        self.bread = breadName

    def setCheese(self, cheeseName):
        '''Sets the type of cheese instance variable'''
        self.cheese = cheeseName

    def setMeat(self, meatName):
        '''Sets the type of meat instance variable'''
        self.meat = meatName

    def addCondiment(self, additionalCondiment):
        '''Adds "additionalCondiment" to the list of condiments
        stored in the instance variable for the condiments list'''

        if self.condiments == None:
            self.condiments = [additionalCondiment]
        elif self.condiments != None:
            self.condiments.append(additionalCondiment)

    def setToasted(self, isToasted):
        '''Sets the toasted instance variable to be True
        if the parameter "isToasted" is True, False otherwise'''
        self.toasted = isToasted

    def getPrice(self):
        '''Returns the price of the sandwich based on the following:
        - Basic cheese sandwich with one condiment = $4.50
        - Meat added to the cheese sandwich + $1.00
        - Each additional condiment after the first + $0.50'''

        price = 4.5
        if self.meat is not None:
            price = price + 1.0
        if self.condiments is not None:
            numCondiments = len(self.condiments)
            price = price + 0.5 * (numCondiments - 1)
        return price

    def __str__(self):
        '''Returns a string containing the description of the sandwich
        which includes customer name, a list of all the ingredients,
        and whether the sandwich is toasted or not'''

        # example:
        # Bernie: wheat, Cheddar, turkey, ['mayo', 'mustard', 'lettuce'], not toasted
        # add 'toast' as a local variable for proper printing

        if self.toasted == True:
            toast = 'toasted'
        elif self.toasted == False:
            toast = 'not toasted'
        elif self.toasted == None:
            toast = None

        # Need to remove any None's from the sting
        # I don't know how ... si I came uo with a
        # bit of a complicated way -- it works

        sandwichList = [self.bread, self.cheese, self.meat, self.condiments]
        modSandwich = []  # remove Nones
        for i in sandwichList:
            if i != None:
                modSandwich.append(i)

        n2st = ''  # create new sub0string without the Nones
        for i in modSandwich:
            if type(i) is not list:
                n2st = n2st + i + ', '
            elif type(i) is list:
                n2st = n2st + str(i) + ', '

        # assemble output string
        sandwichOrder = f"{self.name}: {n2st}{toast}"
        return sandwichOrder

if __name__ == "__main__":
    ''' test function, makes several sandwich objects'''
    s = Sandwich("Bennie")
    print(s)
    s.setBread("wheat")
    print(s)
    s.setCheese("Cheddar")
    print(s)
    s.setMeat("turkey")
    print(s)
    s.addCondiment("mayo")
    print(s)
    s.addCondiment("mustard")
    print(s)
    s.addCondiment("lettuce")
    print(s)
    s.setToasted(True)
    print(s)
    print(s.getPrice())
    #
    s2 = Sandwich("Elizabeth")
    s2.setBread("sourdough")
    s2.setCheese("swiss")
    s2.addCondiment("mayo")
    s2.addCondiment("mustard")
    s2.setToasted(True)
    print(s2)
    print(s2.getPrice())

"""
/home/jorge/csf021aWork/bin/python /home/jorge/csf021a/csf021aWork/module5/sandwich.py
Bennie: not toasted
Bennie: wheat, not toasted
Bennie: wheat, Cheddar, not toasted
Bennie: wheat, Cheddar, turkey, not toasted
Bennie: wheat, Cheddar, turkey, ['mayo'], not toasted
Bennie: wheat, Cheddar, turkey, ['mayo', 'mustard'], not toasted
Bennie: wheat, Cheddar, turkey, ['mayo', 'mustard', 'lettuce'], not toasted
Bennie: wheat, Cheddar, turkey, ['mayo', 'mustard', 'lettuce'], toasted
6.5
Elizabeth: sourdough, swiss, ['mayo', 'mustard'], toasted
5.0

Process finished with exit code 0
"""


from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Order
from .api.serializers import OrderSerializer

@api_view(['GET', 'POST', 'DELETE'])
def order(request):
  if request.method == 'GET':
    ordersSerializer = OrderSerializer
    orders = Order.objects.all()
    return Response({"results": ordersSerializer(orders, many=True).data })

  if request.method == 'POST':
    serializer = OrderSerializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save()
    return Response(serializer.data, status=status.HTTP_201_CREATED)

@api_view(['DELETE', 'GET'])
def orderDetails(request, id):
  try:
    order = Order.objects.get(id=id)
  except Order.DoesNotExist:
    return Response(status=status.HTTP_404_NOT_FOUND)

  if request.method == 'GET':
    ordersSerializer = OrderSerializer
    return Response(ordersSerializer(order).data)

  if request.method == 'DELETE':
    order.delete()
    return Response(status=status.HTTP_200_OK)


from collections import deque
import sys

M, N = map(int ,sys.stdin.readline().split())

grid = []
for i in range(N):
    grid.append(list(map(int, sys.stdin.readline().split())))

dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]

q = deque([])

for i in range(N):
    for j in range(M):
        if grid[i][j] == 1:
            q.append([i, j])

def bfs():
    while q:
        x, y = q.popleft()

        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]

            if nx >= 0 and nx < N and ny >= 0 and ny < M:
                if grid[nx][ny] == 0:
                    q.append([nx, ny])
                    grid[nx][ny] = grid[x][y] + 1

bfs()

result = 0

for i in range(N):
    if grid[i].count(0):
        print(-1)
        exit()
    else:
        result = max(result, max(grid[i]))

print(result - 1)