import sys # for arguments from terminal pass
import subprocess # for terminal usage
import os # for terminal usage
import enum # for custom types
#subprocess.Popen('cmd.exe')
# https://stackoverflow.com/questions/70308288/open-a-terminal-with-python-but-keep-it-running
# https://stackoverflow.com/questions/19308415/execute-terminal-command-from-python-in-new-terminal-window
#subprocess.call('cmd.exe')
#os.system('dir')

# https://stackoverflow.com/questions/58608361/string-based-enum-in-python
class YTType(str, enum.Enum):
    p360 = "18"
    p720 = "22"

class VKType(str, enum.Enum):
    p240 = "url240"
    p360 = "url360"
    p480 = "url480"
    p720 = "url720"
    #p1080 = "url1080"

class UNK1Type(str, enum.Enum):
    p240 = "240p"
    p480 = "480p"
    p720 = "720p"
    #p1080 = "1080p"

class UNK2Type(str, enum.Enum):
    p240 = "240p-1000k-1"
    p480 = "480p-2000k-1"
    p720 = "720p-4000k-1"
    #p1080 = "1080p-4000k-1"

def fileRead():

    # https://pythonworld.ru/tipy-dannyx-v-python/spiski-list-funkcii-i-metody-spiskov.html
    link = []

    # https://pythonz.net/references/named/file.read/
    # https://pythonz.net/references/named/file.readline/
    with open('links.txt') as f:
        while True:
            str = f.readline()
            if not str:
                break
            link.append(str)
        #str = f.read(256) # read 256 symbols

    return link

def linkDownload(link, t, n):

    i = 1

    for l in link:
        if l != "\n": # for skipping empty strings if there are some (it often happens in notes)
            print("\n---------- LINK n" + str(i) + " ----------\n")
            if n == True: # numbering is enabled
                # https://superuser.com/questions/1313121/how-to-add-track-numbers-to-the-filename-with-youtube-dl
                # https://superuser.com/a/1366344
                print("yt-dlp --no-check-certificate -f " + t.p720 + " " + "-o" + " \"" + str(i) + " %(title)s.%(ext)s" + "\" " + l)
                # https://unix.stackexchange.com/questions/154427/unexpected-eof-while-looking-for-matching-bash-script
                # https://unix.stackexchange.com/a/154430
                os.system("yt-dlp --no-check-certificate -f " + t.p720 + " " + "-o" + " \"" + str(i) + " %(title)s.%(ext)s" + "\" " + l)
            elif n == False: # numbering is disabled
                print("yt-dlp --no-check-certificate -f " + t.p720 + " " + l)
                # https://stackoverflow.com/questions/11443011/running-terminal-within-a-python-script?rq=3
                # https://stackoverflow.com/a/11443169
                os.system("yt-dlp --no-check-certificate -f " + t.p720 + " " + l)
            i = i + 1

# https://stackoverflow.com/questions/4041238/why-use-def-main
# https://stackoverflow.com/a/4041253
def main():
    # https://www.geeksforgeeks.org/command-line-arguments-in-python/
    if len(sys.argv) >= 5:
        # https://stackoverflow.com/questions/664294/is-it-possible-only-to-declare-a-variable-without-assigning-any-value-in-python
        t = None
        n = None
        if sys.argv[1] in ("--type", "-t"):
            if sys.argv[2] in ("yt", "vk", "unk1", "unk2"):
                if sys.argv[2] == "yt":
                    t = YTType
                elif sys.argv[2] == "vk":
                    t = VKType
                elif sys.argv[2] == "unk1":
                    t = UNK1Type
                elif sys.argv[2] == "unk2":
                    t = UNK2Type
            else:
                raise ArgumentError("Incorrect argument \"-t/--type\": expected \"yt\", \"vk\", \"unk1\", \"unk2\", etc.!")
        else:
            raise ArgumentError("Incorrect argument: expected \"-t/--type\"!")
        if sys.argv[3] in ("--numbering", "-n"):
            if sys.argv[4] in ("enabled", "disabled"):
                if sys.argv[4] == "enabled":
                    n = True
                elif sys.argv[4] == "disabled":
                    n = False
            else:
                raise ArgumentError("Incorrect argument \"-n/--numbering\": expected \"enabled\" or \"disabled\"!")
        else:
            raise ArgumentError("Incorrect argument: expected \"-n/--numbering\"!")
        link = fileRead()
        linkDownload(link, t, n)
    else:
        raise ArgumentError("Incorrect argument: expected 5, got " + str(len(sys.argv)) + "!")

if __name__ == "__main__":
    main()


from werkzeug.exceptions import Conflict, Forbidden

from flask_mail import Message

from datetime import date

from flask_app import mail
from flask_app.arrangements_blueprint.services.arrangement_abstract_service \
    import ArrangementAbstractService
from flask_app.common_blueprint.services import GenericService
from flask_login import current_user

from data_layer.dao.arrangement.implementation.arrangement_dao \
    import ArrangementDao
from data_layer.dao.user.implementation.user_dao import UserDao
from data_layer.dao.application.implementation.application_dao import \
    ApplicationDao
from data_layer.dao.reservation.implementation.reservation_dao import \
    ReservationDao

from data_layer.models import Arrangement, Application, Reservation

# daos
arrangement_dao = ArrangementDao()
user_dao = UserDao()
application_dao = ApplicationDao()
reservation_dao = ReservationDao()

# CONSTANTS
DESCRIPTION = ['description']

class ArrangementService(ArrangementAbstractService, GenericService):

    @staticmethod
    def _check_if_received_data_is_invalid(keys):
        if not set(keys).intersection(set(DESCRIPTION)):
            raise Conflict(
                description='Guide can only update description'
            )

    @staticmethod
    def _check_if_admin_made_arrangement(arrangement):
        if arrangement.admin_id != current_user.id:
            raise Conflict(
                description='Admin can only change arrangements '
                            'that he created '
            )

    @staticmethod
    def _check_if_guide_is_guiding_arrangement(arrangement):
        if arrangement.travel_guide_id != current_user.id:
            raise Conflict(
                description='Travel guide can change description of '
                            'arrangement only if he is guiding arrangement'
            )

    @staticmethod
    def _check_if_application_already_exist(user_id, arrangement_id):
        application = application_dao.get_application_by_arrangement_user_id(
            user_id=user_id,
            arrangement_id=arrangement_id
        )
        if application:
            raise Conflict(
                description='Application already exists'
            )

    @staticmethod
    def _check_if_reservation_already_exist(user_id, arrangement_id):
        reservation = reservation_dao.get_reservation_by_arrangement_user_id(
            user_id=user_id,
            arrangement_id=arrangement_id
        )
        if reservation:
            raise Conflict(
                description='Reservation already exists'
            )

    @staticmethod
    def _check_if_reservation_exist(reservation_id):
        reservation = reservation_dao.get_reservation_by_id(
            reservation_id=reservation_id
        )
        if not reservation:
            raise Conflict(
                description='Reservation does not exist'
            )
        return reservation

    @staticmethod
    def _check_if_arrangement_is_in_future(arrangement):
        if arrangement.start_date < date.today():
            raise Conflict(
                description='Arrangement start date has passed'
            )

    @staticmethod
    def _check_if_arrangement_has_free_places(arrangement,
                                              number_of_reservations):
        if arrangement.free_places < number_of_reservations:
            raise Conflict(
                description='Arrangement have only ' +
                            str(arrangement.free_places) +
                            ' free places left'
            )

    @staticmethod
    def _update_application_status(user_id, arrangement_id):
        application = application_dao.get_application_by_arrangement_user_id(
            user_id=user_id,
            arrangement_id=arrangement_id
        )
        application_dao.update_application_status(
            application=application,
            arrangement_id=arrangement_id,
            user_id=user_id
        )

    @staticmethod
    def _check_if_guide_is_available(arrangement_id, user):
        guide_ids = \
            GenericService.get_all_available_travel_guides(
                arrangement_id=arrangement_id
            )
        if user not in guide_ids:
            raise Conflict(
                description='Travel guide is occupied'
            )

    @staticmethod
    # flag is used to determine the error message
    # first date is either now() or start_date from arrangement\
    # second date is either new start_date or new end_date
    def _check_if_date_is_invalid(first_date, second_date, flag):
        if first_date > second_date and flag:
            raise Conflict(
                description='Start date is in the past'
            )
        elif first_date > second_date:
            raise Conflict(
                description='Start date is after end date'
            )

    @staticmethod
    def _check_if_destination_with_same_dates_exist(new_arrangement):
        arrangement = arrangement_dao.get_arrangement_by_destination_and_dates(
            new_arrangement=new_arrangement
        )
        if arrangement:
            raise Conflict(
                description='Arrangement already exists'
            )

    @staticmethod
    def _check_if_number_is_positive(number, flag):
        if number < 0 and flag:
            raise Conflict(
                description='Free places must be positive value'
            )
        elif number < 0:
            raise Conflict(
                description='Price must be positive value'
            )

    @staticmethod
    def _send_email_of_cancellation(arrangement_id):
        users = arrangement_dao.get_users_from_reservation(
            arrangement_id=arrangement_id
        )
        if len(users) > 0:
            msg = Message('Cancellation',
                          sender='lovacportal.podrska@gmail.com',
                          recipients=[u.User.email for u in users])

            msg.body = f'You arrangement {users[0].destination} ' \
                       f'has been cancelled.'
            mail.send(msg)

    def get_arrangement_by_id(self, arrangement_id):
        arrangement = self.check_if_arrangement_exist(
            arrangement_id=arrangement_id
        )
        return arrangement

    def get_all_arrangements(self):
        data = arrangement_dao.get_all_arrangements()
        return data

    def delete_arrangement(self, arrangement_id):
        arrangement = self.check_if_arrangement_exist(
            arrangement_id=arrangement_id
        )
        self.check_if_user_is_admin(current_user.id)
        self._check_if_admin_made_arrangement(
            arrangement=arrangement
        )
        self._send_email_of_cancellation(
            arrangement_id=arrangement_id
        )
        reservation_dao.delete_reservations_for_arrangement(
            arrangement_id=arrangement_id
        )
        message = arrangement_dao.delete_arrangement(
            arrangement_id=arrangement_id
        )
        return message

    def create_arrangement(self, data):
        self.check_if_user_is_admin(current_user.id)
        new_arrangement = Arrangement(
            start_date=data.get('start_date'),
            end_date=data.get('end_date'),
            description=data.get('description'),
            destination=data.get('destination'),
            price=data.get('price'),
            free_places=data.get('free_places'),
            admin_id=current_user.id
        )
        self._check_if_destination_with_same_dates_exist(
            new_arrangement=new_arrangement
        )
        arrangement = arrangement_dao. \
            create_arrangement(new_arrangement=new_arrangement)
        return arrangement

    def update_arrangement(self, id, data):
        if not data:
            raise Conflict(
                description="You must send data for update"
            )
        arrangement = self.check_if_arrangement_exist(
            arrangement_id=id
        )
        if current_user.user_type == 1:
            self._check_if_admin_made_arrangement(
                arrangement=arrangement
            )
            if data.get('start_date') and data.get('end_date'):
                self._check_if_date_is_invalid(
                    first_date=data.get('start_date'),
                    second_date=data.get('end_date'),
                    flag=False
                )
                arrangement.start_date = data.get('start_date')
                arrangement.end_date = data.get('end_date')
            else:
                if data.get('end_date'):
                    self._check_if_date_is_invalid(
                        first_date=arrangement.start_date,
                        second_date=data.get('end_date'),
                        flag=False
                    )
                    arrangement.end_date = data.get('end_date')
                if data.get('start_date'):
                    self._check_if_date_is_invalid(
                        first_date=date.today(),
                        second_date=data.get('start_date'),
                        flag=True
                    )
                    self._check_if_date_is_invalid(
                        first_date=data.get('start_date'),
                        second_date=arrangement.end_date,
                        flag=False
                    )
                    arrangement.start_date = data.get('start_date')
            if data.get('free_places'):
                self._check_if_number_is_positive(
                    number=data.get('free_places'),
                    flag=True
                )
                arrangement.free_places = data.get('free_places')
            if data.get('price'):
                self._check_if_number_is_positive(
                    number=data.get('price'),
                    flag=False
                )
                arrangement.price = data.get('price')
            # this has to be last check,
            # because i need to send object arrangement
            if data.get('travel_guide_id'):
                user = self.check_if_user_is_guide(
                    travel_guide_id=data.get('travel_guide_id')
                )
                self._check_if_guide_is_available(
                    arrangement_id=id,
                    user=user
                )
                arrangement.travel_guide_id = data.get('travel_guide_id')
                self._update_application_status(
                    user_id=user.id,
                    arrangement_id=id
                )
        elif current_user.user_type == 2:
            self._check_if_received_data_is_invalid(
                keys=data.keys()
            )
            self._check_if_guide_is_guiding_arrangement(
                arrangement=arrangement
            )
        else:
            raise Forbidden(
                description='Tourist can\'t update arrangement'
            )

        if data.get('description'):
            arrangement.description = data.get('description')

        arrangement = arrangement_dao.update_arrangement(
            arrangement=arrangement,
            id=id
        )

        return arrangement

    def search_all_arrangements(self, query_params):

        data = arrangement_dao.search_all_arrangements(
            query_params=query_params
        )
        return data

    def create_application(self, travel_guide_id, arrangement_id):
        arrangement = self.check_if_arrangement_exist(arrangement_id)
        self._check_if_arrangement_is_in_future(arrangement)
        user = self.check_if_user_is_guide(travel_guide_id)
        self._check_if_application_already_exist(
            user_id=travel_guide_id,
            arrangement_id=arrangement_id
        )
        self._check_if_guide_is_available(
            arrangement_id=arrangement_id,
            user=user
        )

        application = Application(
            user_id=travel_guide_id,
            arrangement_id=arrangement_id
        )

        application = application_dao.create_application(
            application=application
        )

        return application

    def get_all_guides_with_application(self, arrangement_id):
        arrangement = self.check_if_arrangement_exist(
            arrangement_id=arrangement_id
        )
        self.check_if_user_is_admin(current_user.id)
        available_guides = self.get_all_available_travel_guides(
            arrangement_id=arrangement_id
        )
        guides = user_dao.get_all_travel_guides_with_application(
            arrangement=arrangement,
            available_guides=available_guides
        )
        return guides

    def create_reservation(self, arrangement_id, data, tourist_id):
        arrangement = self.check_if_arrangement_exist(arrangement_id)
        self._check_if_arrangement_is_in_future(
            arrangement=arrangement
        )
        self._check_if_reservation_already_exist(
            arrangement_id=arrangement_id,
            user_id=tourist_id
        )
        self.check_if_user_is_tourist(tourist_id)
        number_of_reservations = data.get('number_of_reservations')
        self._check_if_arrangement_has_free_places(
            arrangement=arrangement,
            number_of_reservations=number_of_reservations
        )

        reservation = Reservation(
            user_id=tourist_id,
            arrangement_id=arrangement_id,
            num_of_places=number_of_reservations
        )
        message = reservation_dao.create_reservation(
            user_id=tourist_id,
            arrangement=arrangement,
            reservation=reservation
        )
        return message

    def update_reservation(self,
                           arrangement_id,
                           data,
                           tourist_id,
                           reservation_id):
        arrangement = self.check_if_arrangement_exist(arrangement_id)
        self._check_if_arrangement_is_in_future(
            arrangement=arrangement
        )
        reservation = self._check_if_reservation_exist(
            reservation_id=reservation_id
        )
        self.check_if_user_is_tourist(tourist_id)
        number_of_reservations = data.get('number_of_reservations')
        self._check_if_arrangement_has_free_places(
            arrangement=arrangement,
            number_of_reservations=number_of_reservations
        )

        message = reservation_dao.update_reservation(
                reservation=reservation,
                arrangement=arrangement,
                num_of_places=number_of_reservations
            )

        return message


# Caesar Cipher
import pyperclip

#message = 'abcdef This is my secret message.'
#message = 'nopqrsJguv6Jv6Jz?J6rp5r7Jzr66ntrM'
message = input('Enter: ')

# encryption/decryption-key:
key = 0
key = int(input('\nEnter encryption-key [1 - 26]: '))
if key == 0:
    key = 13
# set mode - encrypt / decrypt:
mode = 'encrypt'
mode = input("Enter 'e' for encrypt, 'd' for decrypt: ")
if (mode == 'd'):
    mode = 'decrypt'
else:
    mode = 'encrypt'
SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.'
#SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789 \
#    !?.`~@#$%^&*()_+-=[]{}|;:<>,/'

translated = ''
for symbol in message:
    if symbol in SYMBOLS:
        symbol_index = SYMBOLS.find(symbol)
        if mode == 'encrypt':
            translated_index = symbol_index + key
        elif mode == 'decrypt':
            translated_index = symbol_index - key

        if translated_index >= len(SYMBOLS):
            translated_index = translated_index - len(SYMBOLS)
        elif translated_index < 0:
            translated_index = translated_index + len(SYMBOLS)

        translated = translated + SYMBOLS[translated_index]
    else:
        translated = translated + symbol

print('\n[ KEY: ' + str(key) + ' ] [ MODE: ' + mode + ' ]:\n')
print(translated + '\n')
pyperclip.copy(translated)


from flask.views import MethodView
from flask_smorest import Blueprint

from ....db import ItalyRegion
from ....extension import ma

class ItalyRegionSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = ItalyRegion
        load_instance = True

def register_italy_regions_api(bp: Blueprint):
    """Register the API for italy region

    Arguments:
        bp {Blueprint} -- Smorest Blueprint
    """

    @bp.route("italy/regions")
    class RegionView(MethodView):
        # @bpb.arguments(ItalyRegionSchema, location="query")
        @bp.response(
            schema=ItalyRegionSchema(many=True),
            description="List of Italian regions",
            example={
                "codice_regione": 20,
                "denominazione_regione": "Sardegna",
                "lat": 39.2153,
                "long": 9.11062,
            },
        )
        def get(self):
            """Get the list of Italian regions

            Return a list of Italian regions
            """
            return ItalyRegion.query


import requests
from urllib import request
import os
import multiprocessing
headers = {
    'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36 Edg/112.0.1722.68'
}

def get_bili_meizi(page=1):
    url = f'https://api.live.bilibili.com/xlive/web-interface/v1/second/getList?platform=web&parent_area_id=1&area_id=145&sort_type=online&page={page}'
    respone = requests.get(url, headers=headers)
    content = respone.json()
    meizi_list = content['data']['list']
    for mz in meizi_list:
        nick = mz['uname']  # 昵称
        img = mz['face']    # 图片
        # 获取图片
        try:
            request.urlretrieve(img,f'bili/{page}-{nick}.png')
            request.urlcleanup()
            print(f'{page}-{nick}.png 下载完成！')
        except:
            print('error', nick)

if __name__ == '__main__':
    if not os.path.exists('bili'):
        os.mkdir('bili')

    # 使用多进程
    for i in range(1, 5):
        multiprocessing.Process(target=get_bili_meizi, args=(i,)).start()


"""
Module for creating valid OPM files as specified in the Orbit Data Message
Recommended Standard CCSDS 502.0-B-2

Recommended import syntax:
import odmpy.opm as opm
"""
import re
import textwrap
from datetime import datetime
from enum import Enum
from math import floor, log10
from numbers import Number

# from odmpy.opm import * considered harmful
# Even so, make sure only core functionality gets imported
__all__ = [
    'Opm',
    'Header',
    'Metadata',
    'Data',
    'DataBlockCovarianceMatrix',
    'DataBlockKeplerianElements',
    'DataBlockManeuverParameters',
    'DataBlockSpacecraftParameters',
    'DataBlockStateVector',
    'RefFrame',
    'TimeSystem',
]

def prefix(prefix, iterable):
    """Add prefix to each item of an iterable."""
    for x in iterable:
        yield '{prefix}{x!s}'.format(prefix=prefix, x=x)

def suffix(suffix, iterable):
    """Add suffix to each item of an iterable."""
    for x in iterable:
        yield '{x!s}{suffix!s}'.format(suffix=suffix, x=x)

def _mant_exp(num):
    """
    Return the mantissa and (base 10) exponent of num.

    num -- float or int.
    """
    try:
        exponent = floor(log10(abs(num)))
    except ValueError:  # Case of log10(0)
        return (0, 0)  # Convention: 0 = 0*10^0
    mantissa = num / 10**exponent
    return (mantissa, int(exponent))

def _align_decimal(number):
    """Format number for ODM output.

    Number is truncated to MAX_DIGITS if required. Numbers are given leading
    space such that any number passed in will have its decimal point in the
    same character position.
    """
    MAX_DIGITS = 16  # From ODM spec

    _, exponent = _mant_exp(number)

    if exponent > MAX_DIGITS - 2:
        return ' ' * (MAX_DIGITS - 1) + '{: .15e}'.format(number)
    elif exponent < -4:
        return ' ' * (MAX_DIGITS - 1) + '{: .15e}'.format(number)
    else:
        if exponent > 0:
            prec = '{: {padding}.{precision}f}'.format(
                number, precision=MAX_DIGITS - 1 - exponent,
                padding=MAX_DIGITS * 2)
            ind = exponent - 1
            prec = prec[ind:]
        elif exponent <= 0:
            prec = '{: {padding}.{precision}f}'.format(
                number, precision=MAX_DIGITS - 2, padding=MAX_DIGITS * 2)
        return prec

def validate_string(string):
    """Check string is non-empty."""
    return True if string else False

def validate_date(date):
    return date.utcoffset() is None

def validate_object_id(object_id):
    match = re.match('^[0-9]{4}\-[0-9]{3}[A-Z]{1,3}$', object_id)
    return True if match else False

def format_date(date):
    return date.isoformat(sep='T')

def format_date_yyyyddd(date):
    if date.microsecond == 0:
        return date.strftime('%Y-%jT%H:%M:%S')
    else:
        return date.strftime('%Y-%jT%H:%M:%S.%f')

class TimeSystem(Enum):
    """Time system.

    GMST
      Greenwich Mean Sidereal Time
    GPS
      Global Positioning System
    MET
      Mission Elapsed Time (note)
    MRT
      Mission Relative Time (note)
    SCLK
      Spacecraft Clock (receiver) (requires rules for interpretation in ICD)
    TAI
      International Atomic Time
    TCB
      Barycentric Coordinate Time
    TDB
      Barycentric Dynamical Time
    TCG
      Geocentric Coordinate Time
    TT
      Terrestrial Time
    UT1
      Universal Time
    UTC
      Coordinated Universal Time
    """
    GMST = 'GMST'
    GPS  = 'GPS'
    MET  = 'MET'
    MRT  = 'MRT'
    SCLK = 'SCLK'
    TAI  = 'TAI'
    TCB  = 'TCB'
    TDB  = 'TDB'
    TCG  = 'TCG'
    TT   = 'TT'
    UT1  = 'UT1'
    UTC  = 'UTC'

class RefFrame(Enum):
    """Reference frame.

    EME2000
      Earth Mean Equator and Equinox of J2000
    GCRF
      Geocentric Celestial Reference Frame
    GRC
      Greenwich Rotating Coordinates
    ICRF
      International Celestial Reference Frame
    ITRF2000
      International Terrestrial Reference Frame 2000
    ITRF-93
      International Terrestrial Reference Frame 1993
    ITRF-97
      International Terrestrial Reference Frame 1997
    MCI
      Mars Centered Inertial
    TDR
      True of Date, Rotating
    TEME
      True Equator Mean Equinox (see below)
    TOD
      True of Date
    RSW
      Another name for RTN
    RTN
      Radial, transverse, normal
    TNW
      A local orbital coordinate frame that has the x-axis along the velocity
      vector, W along the orbital angular momentum vector, and N completes
      the right handed system.
    """
    EME2000  = 'EME2000'
    GCRF     = 'GCRF'
    GRC      = 'GRC'
    ICRF     = 'ICRF'
    ITRF2000 = 'ITRF2000'
    ITRF_93  = 'ITRF_93'
    ITRF_97  = 'ITRF_97'
    MCI      = 'MCI'
    TDR      = 'TDR'
    TEME     = 'TEME'
    TOD      = 'TOD'
    RSW      = 'RSW'
    RTN      = 'RTN'
    TNW      = 'TNW'

class MissingKeywordError(Exception):
    pass

class MissingBlockError(Exception):
    pass

class DuplicateKeywordError(Exception):
    pass

class Keyword:

    """ODM keyword object.

    Apart from the keyword name and value, the 'mandatory',
    'formatter', and 'validator' properties ensure that only a valid Keyword
    can be written to file, formatted appropriately.
    """

    def __init__(self, keyword, value=None, mandatory=True,
                 formatter=lambda x: x, validator=lambda x: True):
        """Initialise keyword with sane defaults.

        Note that keywords are mandatory by default, i.e. optional keywords
        are opt-out.

        formatter and validator are instance variables so that this information
        is associated directly with the keyword. This also allows individual
        keywords to have either method overriden for special cases.
        """
        self.keyword = keyword
        self.mandatory = mandatory
        self.value = value
        self.formatter = formatter
        self.validator = validator

    def __repr__(self):
        return ('{name}('
                'keyword={keyword!r}, '
                'value={value!r}, '
                'mandatory={mandatory!r})'
               ).format(
                    name=self.__class__.__name__,
                    keyword=self.keyword,
                    value=self.value,
                    mandatory=self.mandatory)

    @property
    def formatted_value(self):
        """Format keyword value for writing to file."""
        return self.formatter(self.value)

    def is_valid(self):
        """Check if keyword is valid."""
        return self.validator(self.value)

class DataKeyword(Keyword):

    """Subclass of Keyword for keywords with units."""

    def __init__(self, keyword, value=None, units=None, mandatory=True,
                 formatter=lambda x: x, validator=lambda x: True):
        """Initialise super & set instance variables unique to DataKeyword."""
        super().__init__(keyword=keyword, value=value, mandatory=mandatory,
                         formatter=formatter, validator=validator)
        self.units = units

    def __repr__(self):
        return ('{name}('
                'keyword={keyword!r}, '
                'value={value!r}, '
                'mandatory={mandatory!r}, '
                'units={units!r})'
               ).format(
                    name=self.__class__.__name__,
                    keyword=self.keyword,
                    value=self.value,
                    mandatory=self.mandatory,
                    units=self.units)

class KeywordContainer:

    """Base class of OPM keyword sections.

    The standard splits an orbital parameter message file into different
    sections, which will subclass KeywordContainer.

    The main purpose of the base class is to implement the methods for
    validating and formatting the list of keywords provided by each sublcass.
    """

    def __init__(self):
        """Initialise with empty keyword list.

        Initialise super() for co-operative subclassing.
        """
        super().__init__()
        self.keywords = list()

    def validate_keywords(self):
        """Ensures keywords are valid and set (if mandatory).

        :raises odmpy.opm.MissingKeywordError: if a mandatory keyword `is None`
        :raises ValueError: if keyword validation fails

        This method should be called internally before data meant for output
        is produced.
        """
        for keyword in self.keywords:
            if keyword.mandatory and keyword.value is None:
                raise MissingKeywordError(keyword.keyword)
            if keyword.value is not None:
                if not keyword.is_valid():
                    raise ValueError('%s failed validation.' % keyword.keyword)

    def create_output_align_equals(self):
        """Align keywords by equal sign."""
        self.validate_keywords()
        longest_keyword_len = len(max(self.keywords,
                                      key=lambda x: len(x.keyword)).keyword)
        output = list()
        for keyword in (key for key in self.keywords if key.value is not None):
            keyword_name = keyword.keyword
            aligned_keyword = keyword_name.ljust(longest_keyword_len)
            value = keyword.formatted_value
            if keyword.keyword == 'COMMENT':
                for comment_line in prefix('COMMENT ', value.splitlines()):
                    yield comment_line
            else:
                yield '{keyword} = {value}'.format(keyword=aligned_keyword,
                                                   value=value)

    def create_output_align_decimal(self):
        """.

        Adapted from a StackOverflow answer by Alex Martelli,
        http://stackoverflow.com/a/1025528
        """
        self.validate_keywords()

        ut0 = re.compile(r'(\d)0+$')

        # Get all numerical keyword values for formatting.
        numbers = (keyword.value for keyword in self.keywords
                   if isinstance(keyword.value, Number))

        # Remove leading space
        aligned_numbers = iter(textwrap.dedent(
            '\n'.join(
                ut0.sub(r'\1', _align_decimal(x)) for x in numbers
            )).splitlines())

        longest_keyword_len = len(
            max(self.keywords, key=lambda x: len(x.keyword)).keyword)
        output = list()

        # Already validated, so ignore unset keywords.
        for keyword in (key for key in self.keywords if key.value is not None):
            keyword_name = keyword.keyword

            if keyword_name == 'COMMENT':
                value = keyword.formatted_value
                for comment_line in prefix('COMMENT ', value.splitlines()):
                    yield comment_line
            else:
                aligned_keyword = keyword_name.ljust(longest_keyword_len)

                # Loop through all keywords, consuming the decimal-aligned number
                # from the iterator we made earlier.
                if isinstance(keyword.value, Number):
                    # If number has not been formatted manually, use
                    # decimal-aligned number. In this case, formatted means that
                    # the formatted value is a string.
                    if keyword.value == keyword.formatted_value:
                        value = next(aligned_numbers)
                    else:
                        # Consume next value from iterator, but use formatted value
                        _ = next(aligned_numbers)
                        value = keyword.formatted_value
                else:
                    value = keyword.formatted_value

                yield '{keyword} = {value}'.format(keyword=aligned_keyword,
                                                   value=value)

class Header(KeywordContainer):

    """OPM Header object.

    :param str opm_version: CCSDS OPM version.
    :param creation_date: Creation date. Defaults to current time.
    :type creation_date: :py:class:`~datetime.datetime`-like object
    :param str originator: Creating agency or operator.
    :param str comment: Single or multi-line comment.
    """

    def __init__(self, originator, opm_version='2.0',
                 creation_date=None, comment=None):
        """Initialise OPM Header.

        Required keywords:
        - opm_version
        - creation_date
        - originator

        Optional keywords:
        - comment
        """
        super().__init__()

        if creation_date is None:
            creation_date = datetime.utcnow()

        self._opm_version = Keyword(
            'CCSDS_OPM_VERS', opm_version, validator=validate_string)

        self._comment = Keyword('COMMENT', comment, mandatory=False)

        self._creation_date = Keyword(
            'CREATION_DATE', creation_date,
            formatter=format_date, validator=validate_date)

        self._originator = Keyword(
            'ORIGINATOR', originator, validator=validate_string)

        self.keywords = [
            self._opm_version,
            self._comment,
            self._creation_date,
            self._originator
        ]

    @property
    def opm_version(self):
        return self._opm_version

    @opm_version.setter
    def opm_version(self, value):
        self._opm_version.value = value

    @property
    def comment(self):
        return self._comment

    @comment.setter
    def comment(self, value):
        self._comment.value = value

    @property
    def creation_date(self):
        return self._creation_date

    @creation_date.setter
    def creation_date(self, value):
        self._creation_date.value = value

    @property
    def originator(self):
        return self._originator

    @originator.setter
    def originator(self, value):
        self._originator.value = value

class Metadata(KeywordContainer):

    """OPM Metadata object.

    :param str object_name: Spacecraft name.
    :param str object_id: Object identifier. International Designator recommended.
    :param str center_name: Origin of reference frame.
    :param ref_frame: Reference frame in which state is defined.
    :type ref_frame: :py:class:`~odmpy.opm.RefFrame`
    :param time_system: Time system used for state vector, maneuver, and covariance data.
    :type time_system: :py:class:`~odmpy.opm.TimeSystem`
    :param ref_frame_epoch: Epoch of reference frame.
    :type ref_frame_epoch: :py:class:`~datetime.datetime`-like object
    :param str comment: Single or multi-line comment.
    """

    def __init__(self, object_name, object_id, center_name, ref_frame,
                 time_system, ref_frame_epoch=None, comment=None):
        """Initialise OPM Metadata section.

        Required keywords:
        - object_name
        - object_id
        - center_name
        - ref_frame
        - time_system

        Optional keywords:
        - comment
        - ref_frame_epoch
        """
        super().__init__()
        self._comment = Keyword('COMMENT', comment, mandatory=False)
        self._object_name = Keyword(
            'OBJECT_NAME', object_name, validator=validate_string)

        self._object_id = Keyword(
            'OBJECT_ID', object_id, validator=validate_object_id)

        self._center_name = Keyword(
            'CENTER_NAME', center_name, validator=validate_string)

        self._ref_frame = Keyword(
            'REF_FRAME', ref_frame, formatter=lambda x: x.value)

        self._ref_frame_epoch = Keyword(
            'REF_FRAME_EPOCH', ref_frame_epoch, mandatory=False,
            formatter=format_date, validator=validate_date)

        self._time_system = Keyword(
            'TIME_SYSTEM', time_system, formatter=lambda x: x.value)

        self.keywords = [
            self._comment,
            self._object_name,
            self._object_id,
            self._center_name,
            self._ref_frame,
            self._ref_frame_epoch,
            self._time_system
        ]

    @property
    def comment(self):
        return self._comment

    @comment.setter
    def comment(self, value):
        self._comment.value = value

    @property
    def object_name(self):
        return self._object_name

    @object_name.setter
    def object_name(self, value):
        self._object_name.value = value

    @property
    def object_id(self):
        return self._object_id

    @object_id.setter
    def object_id(self, value):
        self._object_id.value = value

    @property
    def center_name(self):
        return self._center_name

    @center_name.setter
    def center_name(self, value):
        self._center_name.value = value

    @property
    def ref_frame(self):
        return self._ref_frame

    @ref_frame.setter
    def ref_frame(self, value):
        self._ref_frame.value = value

    @property
    def ref_frame_epoch(self):
        return self._ref_frame_epoch

    @ref_frame_epoch.setter
    def ref_frame_epoch(self, value):
        self._ref_frame_epoch.value = value

    @property
    def time_system(self):
        return self._time_system

    @time_system.setter
    def time_system(self, value):
        self._time_system.value = value

class DataBlock:

    """Named block for part of OPM data section."""

    def __init__(self, name=None):
        """name parameter can be set by user, otherwise
        default set at write time.
        """
        super().__init__()
        self.name = name

class DataBlockStateVector(DataBlock, KeywordContainer):

    """State vector block for OPM data section.

    :param epoch: Epoch of state vector.
    :type epoch: :py:class:`~datetime.datetime`-like object
    :param float x: :math:`x` [km].
    :param float y: :math:`y` [km].
    :param float z: :math:`z` [km].
    :param float x_dot: :math:`\dot{x}` [km/s].
    :param float y_dot: :math:`\dot{y}` [km/s].
    :param float z_dot: :math:`\dot{z}` [km/s].
    :param str comment: Single or multi-line comment.
    """

    def __init__(self, epoch, x, y, z, x_dot, y_dot, z_dot, comment=None):
        """Initialise state vector data block.

        Required keywords:
        - epoch
        - x
        - y
        - z
        - x_dot
        - y_dot
        - z_dot

        Optional keywords:
        - comment
        """
        super().__init__()
        self._comment = DataKeyword('COMMENT', comment, mandatory=False)
        self._epoch   = DataKeyword(
            'EPOCH', epoch, formatter=format_date,
            validator=validate_date)
        self._x       = DataKeyword('X', x, units='km')
        self._y       = DataKeyword('Y', y, units='km')
        self._z       = DataKeyword('Z', z, units='km')
        self._x_dot   = DataKeyword('X_DOT', x_dot, units='km/s')
        self._y_dot   = DataKeyword('Y_DOT', y_dot, units='km/s')
        self._z_dot   = DataKeyword('Z_DOT', z_dot, units='km/s')

        self.keywords = [
            self._comment,
            self._epoch,
            self._x,
            self._y,
            self._z,
            self._x_dot,
            self._y_dot,
            self._z_dot
        ]

    @property
    def comment(self):
        return self._comment

    @comment.setter
    def comment(self, value):
        self._comment.value = value

    @property
    def epoch(self):
        return self._epoch

    @epoch.setter
    def epoch(self, value):
        self._epoch.value = value

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x.value = value

    @property
    def y(self):
        return self._y

    @y.setter
    def y(self, value):
        self._y.value = value

    @property
    def z(self):
        return self._z

    @z.setter
    def z(self, value):
        self._z.value = value

    @property
    def x_dot(self):
        return self._x_dot

    @x_dot.setter
    def x_dot(self, value):
        self._x_dot.value = value

    @property
    def y_dot(self):
        return self._y_dot

    @y_dot.setter
    def y_dot(self, value):
        self._y_dot.value = value

    @property
    def z_dot(self):
        return self._z_dot

    @z_dot.setter
    def z_dot(self, value):
        self._z_dot.value = value

class DataBlockKeplerianElements(DataBlock, KeywordContainer):

    """Keplerian elements block for OPM data section.

    :param float semi_major_axis: Semimajor axis [km].
    :param float eccentricity: Eccentricity [--].
    :param float inclination: Inclination [deg].
    :param float ra_of_asc_node: Right ascension of the ascending node [deg].
    :param float arg_of_pericenter: Argument of pericenter [deg].
    :param float true_anomaly: True anomaly [deg].
    :param float mean_anomaly: Mean anomaly [deg].
    :param float gm: Gravitational coefficient [km\ :sup:`3`\ s\ :sup:`-2`]
    :param str comment: Single or multi-line comment.

    .. note::

       Either `true_anomaly` or `mean_anomaly` must be set before the block is
       validated (usually instigated by :py:class:`odmpy.opm.Opm`)
    """

    def __init__(self, semi_major_axis, eccentricity, inclination,
                 ra_of_asc_node, arg_of_pericenter, gm, true_anomaly=None,
                 mean_anomaly=None, comment=None):
        """Initialise keplerian elements data block.

        Required keywords:
        - semi_major_axis
        - eccentricity
        - inclination
        - ra_of_asc_node
        - arg_of_pericenter
        - true_anomaly or mean_anomaly
        - gm

        Optional keywords:
        - comment
        """
        super().__init__()
        self._comment = DataKeyword(
            'COMMENT', comment, mandatory=False)

        self._semi_major_axis = DataKeyword(
            'SEMI_MAJOR_AXIS', semi_major_axis, units='km')

        self._eccentricity = DataKeyword(
            'ECCENTRICITY', eccentricity)

        self._inclination = DataKeyword(
            'INCLINATION', inclination, units='deg')

        self._ra_of_asc_node = DataKeyword(
            'RA_OF_ASC_NODE', ra_of_asc_node, units='deg')

        self._arg_of_pericenter = DataKeyword(
            'ARG_OF_PERICENTER', arg_of_pericenter, units='deg')

        if true_anomaly is not None and mean_anomaly is not None:
            raise DuplicateKeywordError(
                'mean_anomaly and true_anomaly cannot both be set')

        self._true_anomaly = DataKeyword(
            'TRUE_ANOMALY', true_anomaly, units='deg')

        self._mean_anomaly = DataKeyword(
            'MEAN_ANOMALY', mean_anomaly, units='deg')

        self._gm = DataKeyword(
            'GM', gm, units='km**3/s**2')

        self.keywords = [
            self._comment,
            self._semi_major_axis,
            self._eccentricity,
            self._inclination,
            self._ra_of_asc_node,
            self._arg_of_pericenter,
            self._true_anomaly,
            self._mean_anomaly,
            self._gm
        ]

    def validate_keywords(self):
        """Ensures keywords are valid and set (if mandatory).

        :raises odmpy.opm.MissingKeywordError: if a mandatory keyword `is None`
        :raises ValueError: if keyword validation fails

        This method should be called internally before data meant for output
        is produced.

        This method overrides KeywordContainer.validate_keywords for special
        case anomaly keywords.
        """
        missing_anomaly = 0
        for keyword in self.keywords:
            if keyword.mandatory and keyword.value is None:
                if keyword.keyword == 'MEAN_ANOMALY' or keyword.keyword == 'TRUE_ANOMALY':
                    missing_anomaly += 1
                else:
                    raise MissingKeywordError(keyword.keyword)
            if keyword.value is not None:
                if not keyword.is_valid():
                    raise ValueError('%s failed validation.' % keyword.keyword)

        if missing_anomaly != 1:
            raise MissingKeywordError('MEAN_ANOMALY or TRUE_ANOMALY')

    @property
    def true_anomaly(self):
        return self._true_anomaly

    @true_anomaly.setter
    def true_anomaly(self, value):
        if self._mean_anomaly.value is not None:
            raise DuplicateKeywordError('mean_anomaly already set')
        self._true_anomaly.value = value

    @property
    def mean_anomaly(self):
        return self._mean_anomaly

    @mean_anomaly.setter
    def mean_anomaly(self, value):
        if self._true_anomaly.value is not None:
            raise DuplicateKeywordError('true_anomaly already set')
        self._mean_anomaly.value = value

    @property
    def comment(self):
        return self._comment

    @comment.setter
    def comment(self, value):
        self._comment.value = value

    @property
    def semi_major_axis(self):
        return self._semi_major_axis

    @semi_major_axis.setter
    def semi_major_axis(self, value):
        self._semi_major_axis.value = value

    @property
    def eccentricity(self):
        return self._eccentricity

    @eccentricity.setter
    def eccentricity(self, value):
        self._eccentricity.value = value

    @property
    def inclination(self):
        return self._inclination

    @inclination.setter
    def inclination(self, value):
        self._inclination.value = value

    @property
    def ra_of_asc_node(self):
        return self._ra_of_asc_node

    @ra_of_asc_node.setter
    def ra_of_asc_node(self, value):
        self._ra_of_asc_node.value = value

    @property
    def arg_of_pericenter(self):
        return self._arg_of_pericenter

    @arg_of_pericenter.setter
    def arg_of_pericenter(self, value):
        self._arg_of_pericenter.value = value

    @property
    def gm(self):
        return self._gm

    @gm.setter
    def gm(self, value):
        self._gm.value = value

class DataBlockSpacecraftParameters(DataBlock, KeywordContainer):

    """Spacecraft parameters block for OPM data section.

    :param float mass: Spacecraft mass [kg].
    :param float solar_rad_area: Solar radiation pressure area [m\ :sup:`2`].
    :param float solar_rad_coeff: Solar radiation pressure coefficient [--].
    :param float drag_area: Drag area [m\ :sup:`2`].
    :param float drag_coeff: Drag coefficient [--].
    :param str comment: Single or multi-line comment.
    """

    def __init__(self, mass, solar_rad_area, solar_rad_coeff, drag_area,
                 drag_coeff, comment=None):
        """Initialise spacecraft parameters data block.

        Required keywords:
        - mass
        - solar_rad_area
        - solar_rad_coeff
        - drag_area
        - drag_coeff

        Optional keywords:
        - comment
        """
        super().__init__()
        self._comment = DataKeyword('COMMENT', comment, mandatory=False)

        self._mass = DataKeyword('MASS', mass, units='kg', mandatory=False)

        self._solar_rad_area = DataKeyword(
            'SOLAR_RAD_AREA', solar_rad_area, units='m**2', mandatory=False)

        self._solar_rad_coeff = DataKeyword(
            'SOLAR_RAD_COEFF', solar_rad_coeff, mandatory=False)

        self._drag_area = DataKeyword(
            'DRAG_AREA', drag_area, units='m**2', mandatory=False)

        self._drag_coeff = DataKeyword(
            'DRAG_COEFF', drag_coeff, mandatory=False)

        self.keywords = [
            self._comment,
            self._mass,
            self._solar_rad_area,
            self._solar_rad_coeff,
            self._drag_area,
            self._drag_coeff
        ]

    @property
    def comment(self):
        return self._comment

    @comment.setter
    def comment(self, value):
        self._comment.value = value

    @property
    def mass(self):
        return self._mass

    @mass.setter
    def mass(self, value):
        self._mass.value = value

    @property
    def solar_rad_area(self):
        return self._solar_rad_area

    @solar_rad_area.setter
    def solar_rad_area(self, value):
        self._solar_rad_area.value = value

    @property
    def y(self):
        return self._y

    @y.setter
    def y(self, value):
        self._y.value = value

    @property
    def solar_rad_coeff(self):
        return self._solar_rad_coeff

    @solar_rad_coeff.setter
    def solar_rad_coeff(self, value):
        self._solar_rad_coeff.value = value

    @property
    def drag_area(self):
        return self._drag_area

    @drag_area.setter
    def drag_area(self, value):
        self._drag_area.value = value

    @property
    def drag_coeff(self):
        return self._drag_coeff

    @drag_coeff.setter
    def drag_coeff(self, value):
        self._drag_coeff.value = value

class DataBlockCovarianceMatrix(DataBlock, KeywordContainer):

    """Covariance matrix block for data section.

    :param str comment: Single or multi-line comment.
    :param cov_ref_frame: Covariance reference frame.
    :type cov_ref_frame: :py:class:`~odmpy.opm.RefFrame`
    :param float cx_x: :math:`x_{x}` [km\ :sup:`2`\ ].
    :param float cy_x: :math:`y_{x}` [km\ :sup:`2`\ ].
    :param float cy_y: :math:`y_{y}` [km\ :sup:`2`\ ].
    :param float cz_x: :math:`z_{x}` [km\ :sup:`2`\ ].
    :param float cz_y: :math:`z_{y}` [km\ :sup:`2`\ ].
    :param float cz_z: :math:`z_{z}` [km\ :sup:`2`\ ].
    :param float cx_dot_x: :math:`\dot{x}_{x}` [km\ :sup:`2`\ s\ :sup:`-1`].
    :param float cx_dot_y: :math:`\dot{x}_{y}` [km\ :sup:`2`\ s\ :sup:`-1`].
    :param float cx_dot_z: :math:`\dot{x}_{z}` [km\ :sup:`2`\ s\ :sup:`-1`].
    :param float cx_dot_x_dot: :math:`\dot{x}_\dot{x}` [km\ :sup:`2`\ s\ :sup:`-2`].
    :param float cy_dot_x: :math:`\dot{y}_{x}` [km\ :sup:`2`\ s\ :sup:`-1`].
    :param float cy_dot_y: :math:`\dot{y}_{y}` [km\ :sup:`2`\ s\ :sup:`-1`].
    :param float cy_dot_z: :math:`\dot{y}_{z}` [km\ :sup:`2`\ s\ :sup:`-1`].
    :param float cy_dot_x_dot: :math:`\dot{y}_\dot{x}` [km\ :sup:`2`\ s\ :sup:`-2`].
    :param float cy_dot_y_dot: :math:`\dot{y}_\dot{y}` [km\ :sup:`2`\ s\ :sup:`-2`].
    :param float cz_dot_x: :math:`\dot{z}_{x}` [km\ :sup:`2`\ s\ :sup:`-1`].
    :param float cz_dot_y: :math:`\dot{z}_{y}` [km\ :sup:`2`\ s\ :sup:`-1`].
    :param float cz_dot_z: :math:`\dot{z}_{z}` [km\ :sup:`2`\ s\ :sup:`-1`].
    :param float cz_dot_x_dot: :math:`\dot{z}_\dot{x}` [km\ :sup:`2`\ s\ :sup:`-2`].
    :param float cz_dot_y_dot: :math:`\dot{z}_\dot{y}` [km\ :sup:`2`\ s\ :sup:`-2`].
    :param float cz_dot_z_dot: :math:`\dot{z}_\dot{z}` [km\ :sup:`2`\ s\ :sup:`-2`].

    .. note::

       Due to the large number of covariance parameters, the method signature
       accepts them as `**cargs`.
    """

    def __init__(self, comment=None, cov_ref_frame=None, **cargs):
        """Initialise covariance matrix data block.

        Required keywords:
        - cx_x
        - cy_x
        - cy_y
        - cz_x
        - cz_y
        - cz_z
        - cx_dot_x
        - cx_dot_y
        - cx_dot_z
        - cx_dot_x_dot
        - cy_dot_x
        - cy_dot_y
        - cy_dot_z
        - cy_dot_x_dot
        - cy_dot_y_dot
        - cz_dot_x
        - cz_dot_y
        - cz_dot_z
        - cz_dot_x_dot
        - cz_dot_y_dot
        - cz_dot_z_dot

        Optional keywords:
        - comment
        - cov_ref_frame
        """
        super().__init__()
        self._comment = DataKeyword('COMMENT', comment, mandatory=False)
        self._cov_ref_frame = DataKeyword('COV_REF_FRAME', cov_ref_frame,
                                          mandatory=False, formatter=lambda x: x.value)
        self._cx_x = DataKeyword('CX_X', cargs['cx_x'], units='km**2')
        self._cy_x = DataKeyword('CY_X', cargs['cy_x'], units='km**2')
        self._cy_y = DataKeyword('CY_Y', cargs['cy_y'], units='km**2')
        self._cz_x = DataKeyword('CZ_X', cargs['cz_x'], units='km**2')
        self._cz_y = DataKeyword('CZ_Y', cargs['cz_y'], units='km**2')
        self._cz_z = DataKeyword('CZ_Z', cargs['cz_z'], units='km**2')
        self._cx_dot_x = DataKeyword(
            'CX_DOT_X', cargs['cx_dot_x'], units='km**2/s')
        self._cx_dot_y = DataKeyword(
            'CX_DOT_Y', cargs['cx_dot_y'], units='km**2/s')
        self._cx_dot_z = DataKeyword(
            'CX_DOT_Z', cargs['cx_dot_z'], units='km**2/s')
        self._cx_dot_x_dot = DataKeyword(
            'CX_DOT_X_DOT', cargs['cx_dot_x_dot'], units='km**2/s**2')
        self._cy_dot_x = DataKeyword(
            'CY_DOT_X', cargs['cy_dot_x'], units='km**2/s')
        self._cy_dot_y = DataKeyword(
            'CY_DOT_Y', cargs['cy_dot_y'], units='km**2/s')
        self._cy_dot_z = DataKeyword(
            'CY_DOT_Z', cargs['cy_dot_z'], units='km**2/s')
        self._cy_dot_x_dot = DataKeyword(
            'CY_DOT_X_DOT', cargs['cy_dot_x_dot'], units='km**2/s**2')
        self._cy_dot_y_dot = DataKeyword(
            'CY_DOT_Y_DOT', cargs['cy_dot_y_dot'], units='km**2/s**2')
        self._cz_dot_x = DataKeyword(
            'CZ_DOT_X', cargs['cz_dot_x'], units='km**2/s')
        self._cz_dot_y = DataKeyword(
            'CZ_DOT_Y', cargs['cz_dot_y'], units='km**2/s')
        self._cz_dot_z = DataKeyword(
            'CZ_DOT_Z', cargs['cz_dot_z'], units='km**2/s')
        self._cz_dot_x_dot = DataKeyword(
            'CZ_DOT_X_DOT', cargs['cz_dot_x_dot'], units='km**2/s**2')
        self._cz_dot_y_dot = DataKeyword(
            'CZ_DOT_Y_DOT', cargs['cz_dot_y_dot'], units='km**2/s**2')
        self._cz_dot_z_dot = DataKeyword(
            'CZ_DOT_Z_DOT', cargs['cz_dot_z_dot'], units='km**2/s**2')

        self.keywords = [
            self._comment,
            self._cov_ref_frame,
            self._cx_x,
            self._cy_x,
            self._cy_y,
            self._cz_x,
            self._cz_y,
            self._cz_z,
            self._cx_dot_x,
            self._cx_dot_y,
            self._cx_dot_z,
            self._cx_dot_x_dot,
            self._cy_dot_x,
            self._cy_dot_y,
            self._cy_dot_z,
            self._cy_dot_x_dot,
            self._cy_dot_y_dot,
            self._cz_dot_x,
            self._cz_dot_y,
            self._cz_dot_z,
            self._cz_dot_x_dot,
            self._cz_dot_y_dot,
            self._cz_dot_z_dot
        ]

    @property
    def comment(self):
        return self._comment

    @comment.setter
    def comment(self, value):
        self._comment.value = value

    @property
    def cov_ref_frame(self):
        return self._cov_ref_frame

    @cov_ref_frame.setter
    def cov_ref_frame(self, value):
        self._cov_ref_frame.value = value

    @property
    def cx_x(self):
        return self._cx_x

    @cx_x.setter
    def cx_x(self, value):
        self._cx_x.value = value

    @property
    def cy_x(self):
        return self._cy_x

    @cy_x.setter
    def cy_x(self, value):
        self._cy_x.value = value

    @property
    def cy_y(self):
        return self._cy_y

    @cy_y.setter
    def cy_y(self, value):
        self._cy_y.value = value

    @property
    def cz_x(self):
        return self._cz_x

    @cz_x.setter
    def cz_x(self, value):
        self._cz_x.value = value

    @property
    def cz_y(self):
        return self._cz_y

    @cz_y.setter
    def cz_y(self, value):
        self._cz_y.value = value

    @property
    def cz_z(self):
        return self._cz_z

    @cz_z.setter
    def cz_z(self, value):
        self._cz_z.value = value

    @property
    def cx_dot_x(self):
        return self._cx_dot_x

    @cx_dot_x.setter
    def cx_dot_x(self, value):
        self._cx_dot_x.value = value

    @property
    def cx_dot_y(self):
        return self._cx_dot_y

    @cx_dot_y.setter
    def cx_dot_y(self, value):
        self._cx_dot_y.value = value

    @property
    def cx_dot_z(self):
        return self._cx_dot_z

    @cx_dot_z.setter
    def cx_dot_z(self, value):
        self._cx_dot_z.value = value

    @property
    def cx_dot_x_dot(self):
        return self._cx_dot_x_dot

    @cx_dot_x_dot.setter
    def cx_dot_x_dot(self, value):
        self._cx_dot_x_dot.value = value

    @property
    def cy_dot_x(self):
        return self._cy_dot_x

    @cy_dot_x.setter
    def cy_dot_x(self, value):
        self._cy_dot_x.value = value

    @property
    def cy_dot_y(self):
        return self._cy_dot_y

    @cy_dot_y.setter
    def cy_dot_y(self, value):
        self._cy_dot_y.value = value

    @property
    def cy_dot_z(self):
        return self._cy_dot_z

    @cy_dot_z.setter
    def cy_dot_z(self, value):
        self._cy_dot_z.value = value

    @property
    def cy_dot_x_dot(self):
        return self._cy_dot_x_dot

    @cy_dot_x_dot.setter
    def cy_dot_x_dot(self, value):
        self._cy_dot_x_dot.value = value

    @property
    def cy_dot_y_dot(self):
        return self._cy_dot_y_dot

    @cy_dot_y_dot.setter
    def cy_dot_y_dot(self, value):
        self._cy_dot_y_dot.value = value

    @property
    def cz_dot_x(self):
        return self._cz_dot_x

    @cz_dot_x.setter
    def cz_dot_x(self, value):
        self._cz_dot_x.value = value

    @property
    def cz_dot_y(self):
        return self._cz_dot_y

    @cz_dot_y.setter
    def cz_dot_y(self, value):
        self._cz_dot_y.value = value

    @property
    def cz_dot_z(self):
        return self._cz_dot_z

    @cz_dot_z.setter
    def cz_dot_z(self, value):
        self._cz_dot_z.value = value

    @property
    def cz_dot_x_dot(self):
        return self._cz_dot_x_dot

    @cz_dot_x_dot.setter
    def cz_dot_x_dot(self, value):
        self._cz_dot_x_dot.value = value

    @property
    def cz_dot_y_dot(self):
        return self._cz_dot_y_dot

    @cz_dot_y_dot.setter
    def cz_dot_y_dot(self, value):
        self._cz_dot_y_dot.value = value

    @property
    def cz_dot_z_dot(self):
        return self._cz_dot_z_dot

    @cz_dot_z_dot.setter
    def cz_dot_z_dot(self, value):
        self._cz_dot_z_dot.value = value

class DataBlockManeuverParameters(DataBlock, KeywordContainer):

    """Maneuver parameters block for data section.

    :param man_epoch_ignition: Epoch of ignition.
    :type man_epoch_ignition: :py:class:`~datetime.datetime`-like object
    :param float man_duration: Maneuver duration. [s]
    :param float man_delta_mass: Mass change during maneuver. [kg]
    :param man_ref_frame: Coordinate system for velocity increment vector.
    :type man_ref_frame: :py:class:`~odmpy.opm.RefFrame`
    :param float man_dv_1: 1st component of velocity increment vector [km/s].
    :param float man_dv_2: 2nd component of velocity increment vector [km/s].
    :param float man_dv_3: 3rd component of velocity increment vector [km/s].
    :param str comment: Single or multi-line comment.
    """

    def __init__(self, man_epoch_ignition, man_duration, man_delta_mass,
                 man_ref_frame, man_dv_1, man_dv_2, man_dv_3, comment=None):
        """Initialise maneuver parameters data block.

        Required keywords:
        - man_epoch_ignition
        - man_duration
        - man_delta_mass
        - man_ref_frame
        - man_dv_1
        - man_dv_2
        - man_dv_3

        Optional keywords:
        - comment
        """
        super().__init__()
        self._comment = DataKeyword('COMMENT', comment, mandatory=False)
        self._man_epoch_ignition = DataKeyword(
            'MAN_EPOCH_IGNITION', man_epoch_ignition,
            formatter=format_date,
            validator=validate_date)
        self._man_duration = DataKeyword('MAN_DURATION', man_duration,
                                         units='s')
        self._man_delta_mass = DataKeyword(
            'MAN_DELTA_MASS', man_delta_mass, units='kg',
            validator=lambda x: x < 0)

        self._man_ref_frame = DataKeyword('MAN_REF_FRAME', man_ref_frame,
                                          formatter=lambda x: x.value)
        self._man_dv_1 = DataKeyword('MAN_DV_1', man_dv_1, units='km/s')
        self._man_dv_2 = DataKeyword('MAN_DV_2', man_dv_2, units='km/s')
        self._man_dv_3 = DataKeyword('MAN_DV_3', man_dv_3, units='km/s')

        self.keywords = [
            self._comment,
            self._man_epoch_ignition,
            self._man_duration,
            self._man_delta_mass,
            self._man_ref_frame,
            self._man_dv_1,
            self._man_dv_2,
            self._man_dv_3
        ]

    @property
    def comment(self):
        return self._comment

    @comment.setter
    def comment(self, value):
        self._comment.value = value

    @property
    def man_epoch_ignition(self):
        return self._man_epoch_ignition

    @man_epoch_ignition.setter
    def man_epoch_ignition(self, value):
        self._man_epoch_ignition.value = value

    @property
    def man_duration(self):
        return self._man_duration

    @man_duration.setter
    def man_duration(self, value):
        self._man_duration.value = value

    @property
    def man_delta_mass(self):
        return self._man_delta_mass

    @man_delta_mass.setter
    def man_delta_mass(self, value):
        self._man_delta_mass.value = value

    @property
    def man_ref_frame(self):
        return self._man_ref_frame

    @man_ref_frame.setter
    def man_ref_frame(self, value):
        self._man_ref_frame.value = value

    @property
    def man_dv_1(self):
        return self._man_dv_1

    @man_dv_1.setter
    def man_dv_1(self, value):
        self._man_dv_1.value = value

    @property
    def man_dv_2(self):
        return self._man_dv_2

    @man_dv_2.setter
    def man_dv_2(self, value):
        self._man_dv_2.value = value

    @property
    def man_dv_3(self):
        return self._man_dv_3

    @man_dv_3.setter
    def man_dv_3(self, value):
        self._man_dv_3.value = value

class DataBlockContainer:
    def __init__(self, name, block, allow_multiple=False, mandatory=True,
                 prerequisite=lambda: True, prerequisite_error=None):
        self.name = name
        self.block = block
        self.allow_multiple = allow_multiple
        self.mandatory = mandatory
        self.prerequisite = prerequisite
        self.prerequisite_error = prerequisite_error

class Data:

    """OPM Data object (mandatory).

    :param state_vector: State vector block
    :type state_vector: :py:class:`~odmpy.opm.DataBlockStateVector`
    :param spacecraft_parameters: Spacecraft parameters block
    :type spacecraft_parameters: :py:class:`~odmpy.opm.DataBlockSpacecraftParameters`
    :param keplerian_elements: Keplerian elements block
    :type keplerian_elements: :py:class:`~odmpy.opm.DataBlockKeplerianElements`
    :param covariance_matrix: Covariance matrix block
    :type covariance_matrix: :py:class:`~odmpy.opm.DataBlockCovarianceMatrix`
    :param maneuver_parameters: Maneuver parameters block
    :type maneuver_parameters: :py:class:`~odmpy.opm.DataBlockManeuverParameters`
    """

    def __init__(self, state_vector, spacecraft_parameters=None,
                 keplerian_elements=None, covariance_matrix=None,
                 maneuver_parameters=None):
        """Initialise data section from constituent blocks.

        Required blocks:
        - state_vector

        Optional blocks:
        - spacecraft_parameters
        - keplerian_elements
        - covariance_matrix
        - maneuver_parameters

        Note that maneuver_parameters can be an array of
        DataBlockManeuverParameters
        """
        self._state_vector = DataBlockContainer(
            name='State Vector Components',
            block=state_vector)
        self._spacecraft_parameters = DataBlockContainer(
            name='Spacecraft Parameters',
            block=spacecraft_parameters,
            mandatory=False)
        self._keplerian_elements = DataBlockContainer(
            name='Osculating Keplerian Elements',
            block=keplerian_elements,
            mandatory=False)
        self._covariance_matrix = DataBlockContainer(
            name='Position/Velocity Covariance Matrix',
            block=covariance_matrix,
            mandatory=False)
        self._maneuver_parameters = DataBlockContainer(
            name='Maneuver Parameters',
            block=maneuver_parameters,
            mandatory=False,
            allow_multiple=True,
            prerequisite=lambda: self._spacecraft_parameters.block is not None,
            prerequisite_error=('spacecraft parameters block mandatory if any '
                                'maneuver_parameters are given'))

        self.blocks = [
            self.state_vector,
            self.spacecraft_parameters,
            self.keplerian_elements,
            self.covariance_matrix,
            self.maneuver_parameters
        ]

    def validate_blocks(self):
        """Ensure mandatory blocks are present, prerequisites fulfilled, and
        types checked.

        :raises ValueError: if a given block is a list but repetition for the
            block type is disallowed.
        :raises TypeError: if data block is not sublass of
            :py:class:`odmpy.opm.DataBlock` or a valid list of them.
        """
        for bc in self.blocks:
            if bc.mandatory and bc.block is None:
                raise MissingBlockError(bc.name)
            if bc.block is not None:
                if not bc.prerequisite():
                    raise ValueError(bc.prerequisite_error)
                if not isinstance(bc.block, DataBlock):
                    if isinstance(bc.block, list):
                        if not bc.allow_multiple:
                            raise ValueError('the ''{name}'' block cannot be '
                                             'repeated.'.format(name=bc.name))
                        for block in bc.block:
                            if not isinstance(block, DataBlock):
                                raise TypeError('data blocks must subclass '
                                                '{}.DataBlock'.format(__name__))
                    else:
                        raise TypeError('data blocks must subclass {}.'
                                        'DataBlock, or be a list containing'
                                        ' them'.format(__name__))

    @property
    def state_vector(self):
        return self._state_vector

    @state_vector.setter
    def state_vector(self, value):
        if value is None:
            raise ValueError('state vector cannot be None.')
        self._state_vector.block = value

    @property
    def spacecraft_parameters(self):
        return self._spacecraft_parameters

    @spacecraft_parameters.setter
    def spacecraft_parameters(self, value):
        if self.maneuver_parameters is not None:
            if value is None:
                raise ValueError('spacecraft parameters mandatory if '
                                 'any maneuver parameters are set.')
        self._spacecraft_parameters.block = value

    @property
    def keplerian_elements(self):
        return self._keplerian_elements

    @keplerian_elements.setter
    def keplerian_elements(self, value):
        self._keplerian_elements.block = value

    @property
    def covariance_matrix(self):
        return self._covariance_matrix

    @covariance_matrix.setter
    def covariance_matrix(self, value):
        self._covariance_matrix.block = value

    @property
    def maneuver_parameters(self):
        return self._maneuver_parameters

    @maneuver_parameters.setter
    def maneuver_parameters(self, value):
        self._maneuver_parameters.block = value

class Opm:

    """Represent complete OPM.

    :param header: Instance of :py:class:`odmpy.opm.Header`
    :param metadata: Instance of :py:class:`odmpy.opm.Metadata`
    :param data: Instance of :py:class:`odmpy.opm.Data`
    :param dict user_defined: User defined variables

    """

    def __init__(self, header, metadata, data, user_defined=None):
        """Initialise Opm

        `header`, `metadata`, and `data` are instances of Header, Metadata,
        and Data respectively.

        `user_defined` is a dictionary of parameters. The USER_DEFINED_ prefix
        is added automatically.
        """
        self.header = header
        self.metadata = metadata
        self.data = data
        self.user_defined = user_defined

        self.header.validate_keywords()
        self.metadata.validate_keywords()
        self.data.validate_blocks()

    def write(self, fp):
        """Write ASCII-formatted OPM file to `fp` (a ``.write()``-supporting
        :py:term:`file-like object`)"""
        fp.writelines(suffix('\n', self.output()))

    def output(self):
        """Return a line iterator for an ASCII-formatted OPM file."""
        for line in self.header.create_output_align_equals():
            yield line
        yield ''
        yield 'COMMENT Metadata'
        for line in self.metadata.create_output_align_equals():
            yield line
        for bc in self.data.blocks:
            if bc.block is not None:
                yield ''
                yield 'COMMENT %s' % (bc.name if bc.block.name is None
                                      else bc.block.name)
                for line in bc.block.create_output_align_decimal():
                    yield line
        if self.user_defined is not None:
            yield ''
            for key, value in self.user_defined.items():
                yield 'USER_DEFINED_{key} = {value}'.format(key=key,
                                                            value=value)


import numpy as np
import tensorflow as tf

def one_hot_encoding(labels):
    return np.array([(1.0, 0.0) if l == 1 else (0.0, 1.0) for l in labels])

def softmax(x):
     ee = np.exp(x)
     return ee / np.sum(ee)

def make_probs(l):
    return np.array([softmax(row) for row in l])

def one_hidden_layer(data, weights_h, weights_out, p_in, p_h):
    data = tf.nn.dropout(data, p_in)
    h_layer = tf.nn.relu(tf.matmul(data, weights_h))
    h_layer = tf.nn.dropout(h_layer, p_h)
    return tf.matmul(h_layer, weights_out)

def two_hidden_layer(data, weights_h1, weights_h2, weights_out, p_in, p_h):
    data = tf.nn.dropout(data, p_in)
    h1 = tf.nn.relu(tf.matmul(data, weights_h1))
    h1 = tf.nn.dropout(h1, p_h)
    h2 = tf.nn.relu(tf.matmul(h1, weights_h2))
    h2 = tf.nn.dropout(h2, p_h)
    return tf.matmul(h2, weights_out)

def init_weights(shape):
    return tf.Variable(tf.random_normal(shape, stddev=0.01))

def run_network(train_x, train_y, test_x, test_y, n_layers, layers, m_batch, learning_rate, n_epochs):

    data_holder = tf.placeholder("float", [None, 4])
    label_holder = tf.placeholder("float", [None, 2])
    p_in = tf.placeholder("float")
    p_h = tf.placeholder("float")

    if n_layers == 1:
        weights_h1 = init_weights([4, layers[0]])
        weights_out = init_weights([layers[0], 2])
        network = one_hidden_layer(data_holder, weights_h1, weights_out, p_in, p_h)
    elif n_layers == 2:
        weights_h1 = init_weights([4, layers[0]])
        weights_h2 = init_weights([layers[0], layers[1]])
        weights_out = init_weights([layers[1], 2])
        network = two_hidden_layer(data_holder, weights_h1, weights_h2,
                                   weights_out, p_in, p_h)

    error = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(network, label_holder))
    training_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(error)

    train_y = one_hot_encoding(train_y)

    sess = tf.Session()
    init = tf.initialize_all_variables()
    sess.run(init)

    for i in range(n_epochs):
        for start, end in zip(range(0, len(train_x), m_batch), range(m_batch, len(train_x), m_batch)):
            sess.run(training_step, feed_dict={data_holder: train_x[start:end],
                                          label_holder: train_y[start:end],
                                          p_in: 1.,
                                          p_h: 0.5})

    return make_probs(sess.run(network, feed_dict={data_holder: test_x,
                                                p_in: 1.0,
                                                p_h: 1.0}))[:, 0]


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
calculate BARK feature for an audio signal

Reference from:
https://www.cnblogs.com/LXP-Never/p/16011229.html
https://github.com/SuperKogito/spafe
"""
import numpy as np
from functools import lru_cache
from scipy.fftpack import dct
import librosa

def hz2bark_1961(Hz):
    return 13.0 * np.arctan(0.00076 * Hz) + 3.5 * np.arctan((Hz / 7500.0) ** 2)

def hz2bark_1990(Hz):
    bark_scale = (26.81 * Hz) / (1960 + Hz) - 0.5
    return bark_scale

def hz2bark_1992(Hz):
    return 6 * np.arcsinh(Hz / 600)

def hz2bark(f):
    """ Hz to bark频率 (Wang, Sekey & Gersho, 1992.) """
    return 6. * np.arcsinh(f / 600.)

def bark2hz(fb):
    """ Bark频率 to Hz """
    return 600. * np.sinh(fb / 6.)

def fft2hz(fft, sample_rate=16000, nfft=512):
    """ FFT频点 to Hz """
    return (fft * sample_rate) / (nfft + 1)

def hz2fft(fb, sample_rate=16000, nfft=512):
    """ Bark频率 to FFT频点 """
    return (nfft + 1) * fb / sample_rate

def fft2bark(fft, sample_rate=16000, nfft=512):
    """ FFT频点 to Bark频率 """
    return hz2bark((fft * sample_rate) / (nfft + 1))

def bark2fft(fb, sample_rate=16000, nfft=512):
    """ Bark频率 to FFT频点 """
    # bin = sample_rate/2 / nfft/2=sample_rate/nfft    # 每个频点的频率数
    # bins = hz_points/bin=hz_points*nfft/ sample_rate    # hz_points对应第几个fft频点
    return (nfft + 1) * bark2hz(fb) / sample_rate

def Fm(fb, fc):
    """ 计算一个特定的中心频率的Bark filter
    :param fb: frequency in Bark.
    :param fc: center frequency in Bark.
    :return: 相关的Bark filter 值/幅度
    """
    if fc - 2.5 <= fb <= fc - 0.5:
        return 10 ** (2.5 * (fb - fc + 0.5))
    elif fc - 0.5 < fb < fc + 0.5:
        return 1
    elif fc + 0.5 <= fb <= fc + 1.3:
        return 10 ** (-2.5 * (fb - fc - 0.5))
    else:
        return 0

def safe_log(x):
    """Prevents error on log(0) or log(-1)"""
    return np.log(np.clip(x, np.finfo(float).eps, None))

def chop_array(arr, window_size, hop_size):
    """chop_array([1,2,3], 2, 1) -> [[1,2], [2,3]]"""
    return [arr[i - window_size:i] for i in range(window_size, len(arr) + 1, hop_size)]

def power_spec(audio: np.ndarray, window_stride=(160, 80), fft_size=512):
    """Calculates power spectrogram"""
    frames = chop_array(audio, *window_stride) or np.empty((0, window_stride[0]))
    fft = np.fft.rfft(frames, n=fft_size)
    return (fft.real ** 2 + fft.imag ** 2) / fft_size

@lru_cache()  # Prevents recalculating when calling with same parameters
def bark_filterbanks(nfilts=20, nfft=512, sample_rate=16000, low_freq=0, high_freq=None, scale="constant"):
    """ 计算Bark-filterbanks,(B,F)
    :param nfilts: 滤波器组中滤波器的数量 (Default 20)
    :param nfft: FFT size.(Default is 512)
    :param sample_rate: 采样率，(Default 16000 Hz)
    :param low_freq: MEL滤波器的最低带边。(Default 0 Hz)
    :param high_freq: MEL滤波器的最高带边。(Default samplerate/2)
    :param scale (str): 选择Max bins 幅度 "ascend"(上升)，"descend"(下降)或 "constant"(恒定)(=1)。默认是"constant"
    :return:一个大小为(nfilts, nfft/2 + 1)的numpy数组，包含滤波器组。
    """
    # init freqs
    high_freq = high_freq or sample_rate / 2
    low_freq = low_freq or 0

    # 按Bark scale 均匀间隔计算点数(点数以Bark为单位)
    low_bark = hz2bark(low_freq)
    high_bark = hz2bark(high_freq)
    bark_points = np.linspace(low_bark, high_bark, nfilts + 4)

    bins = np.floor(bark2fft(bark_points))  # Bark Scale等分布对应的 FFT bin number
    # [  0.   2.   5.   7.  10.  13.  16.  20.  24.  28.  33.  38.  44.  51.
    #   59.  67.  77.  88. 101. 115. 132. 151. 172. 197. 224. 256.]
    fbank = np.zeros([nfilts, nfft // 2 + 1])

    # init scaler
    if scale == "descendant" or scale == "constant":
        c = 1
    else:
        c = 0

    for i in range(0, nfilts):      # --> B
        # compute scaler
        if scale == "descendant":
            c -= 1 / nfilts
            c = c * (c > 0) + 0 * (c < 0)
        elif scale == "ascendant":
            c += 1 / nfilts
            c = c * (c < 1) + 1 * (c > 1)

        for j in range(int(bins[i]), int(bins[i + 4])):     # --> F
            fc = bark_points[i+2]   # 中心频率
            fb = fft2bark(j)        # Bark 频率
            fbank[i, j] = c * Fm(fb, fc)
    return np.abs(fbank)

def bark_spec(audio, sample_rate, window_size, hop_size, fft_size=512, num_filt=24):
    # use librosa.stft or implement from sonopy to calculate power spec
    powers = power_spec(audio, (window_size, hop_size), fft_size)
    #S = librosa.stft(audio, n_fft=fft_size, hop_length=hop_size, win_length=window_size, window="hann", center=False)
    #powers = np.abs(S).T  # power spec, refer librosa.magphase()

    # get bark filterbanks
    filterbanks = bark_filterbanks(nfilts=num_filt, nfft=fft_size, sample_rate=sample_rate, low_freq=0, high_freq=None, scale="constant")

    # bark power spectrogram (log scale)
    barks = np.dot(powers, filterbanks.T)
    #barks = 20 * np.log10(barks)  # dB
    barks = safe_log(barks)

    return barks

def bfcc_spec(audio, sample_rate, window_size, hop_size, fft_size=512, num_filt=26, num_coeffs=13):
    # use librosa.stft or implement from sonopy to calculate power spec
    powers = power_spec(audio, (window_size, hop_size), fft_size)
    #S = librosa.stft(audio, n_fft=fft_size, hop_length=hop_size, win_length=window_size, window="hann", center=False)
    #powers = np.abs(S).T  # power spec, refer librosa.magphase()
    if powers.size == 0:
        return np.empty((0, min(num_filt, num_coeffs)))

    # get bark filterbanks
    filterbanks = bark_filterbanks(nfilts=num_filt, nfft=fft_size, sample_rate=sample_rate, low_freq=0, high_freq=None, scale="constant")

    # bark power spectrogram (log scale)
    barks = np.dot(powers, filterbanks.T)
    #barks = 20 * np.log10(barks)  # dB
    barks = safe_log(barks)

    bfccs = dct(barks, norm='ortho')[:, :num_coeffs]  # machine readable spectrogram
    bfccs[:, 0] = safe_log(np.sum(powers, 1))  # Replace first band with log energies

    return bfccs

if __name__ == "__main__":
    import librosa.display
    import matplotlib.pyplot as plt
    nfilts = 22
    fft_size = 512
    sample_rate = 16000

    filterbanks = bark_filterbanks(nfilts=nfilts, nfft=fft_size, sample_rate=sample_rate, low_freq=0, high_freq=None, scale="constant")
    # ================ draw bark filterbanks ===========================
    fft_len = fft_size // 2 + 1
    freq_bin = sample_rate // 2 / (fft_size // 2)  # 一个频点多少Hz
    x = np.linspace(0, fft_len, fft_len)

    plt.plot(x * freq_bin, filterbanks.T)
    plt.title('Bark Filterbanks', fontsize=14)
    plt.tight_layout()
    plt.grid(linestyle='--')
    plt.show()

    wav, _ = librosa.load("sheila_2.wav", sr=sample_rate)
    barks = bark_spec(wav, sample_rate, window_size=fft_size, hop_size=fft_size//2, fft_size=fft_size, num_filt=nfilts)
    # ================ draw bark spectrogram ==========================
    plt.figure()
    librosa.display.specshow(barks.T, sr=sample_rate, x_axis='time', y_axis='linear', cmap="jet")
    plt.title('Bark Spectrogram', fontsize=14)
    plt.xlabel('Time/s', fontsize=14)
    plt.ylabel('Freq/kHz', fontsize=14)
    plt.xticks(fontsize=14)
    plt.yticks(fontsize=14)

    plt.tight_layout()
    plt.show()

    num_coeffs = 13
    bfccs = bfcc_spec(wav, sample_rate, window_size=fft_size, hop_size=fft_size//2, fft_size=fft_size, num_filt=nfilts, num_coeffs=num_coeffs)
    # ================ draw bfcc spectrogram ==========================
    plt.figure()
    librosa.display.specshow(bfccs.T, sr=sample_rate, x_axis='time', y_axis='linear', cmap="jet")
    plt.title('BFCC Spectrogram', fontsize=14)
    plt.xlabel('Time/s', fontsize=14)
    plt.ylabel('Freq/kHz', fontsize=14)
    plt.xticks(fontsize=14)
    plt.yticks(fontsize=14)

    plt.tight_layout()
    plt.show()


# Display perceived environment at the end of the program using matplotlib scatter plot

import agent
import slam_alg
import slam_env

import numpy as np
import matplotlib.pyplot as plt
import cv2 as cv

def main():
	length = 960
	height = 540
	thickness = 5
	room = slam_env.createMap(length, height, thickness)
	room.addBox(200, 400, 140, 160)
	room.addCircle(100, 100, 50)
	room.addCircle(100, 200, 40)
	room.addCircle(200, 100, 30)
	room.addCircle(500, 50, 40)
	room.addBox(400, 900, 140, 60)

	robot = agent.Agent(room)
	observations = robot.loadObservations("2")
	obs_tfX = []
	obs_tfY = []
	slam = slam_alg.SLAMAlg(room, robot)

	for i, p in enumerate(robot.explore()):
		robot.moveToLoc(p[0], p[1], p[2])
		robot.refresh()

		robot.deadReckon()
		tfMat = robot.tfMat

		for point in observations[i+1]:
			point_tf = np.dot(tfMat, np.array([point[0], point[1], 1]).T)
			obs_tfX.append(point_tf[0])
			obs_tfY.append(point_tf[1])

		room.display()
		cv.waitKey(0)

	plt.scatter(obs_tfX, obs_tfY)
	plt.show()

if __name__ == "__main__":
	main()


import urllib.request as req
from bs4 import BeautifulSoup
import statistics

url = "https://www.kyochon.com/menu/chicken.asp"
kyochon = req.urlopen(url)

soup = BeautifulSoup(kyochon, 'html.parser')
data1 = soup.select('dl.txt > dt')
data2 = soup.select('p.money > strong')

names = [i.string for i in data1]
prices = [i.string for i in data2]

# 가격 데이터 정제 (쉼표와 '원' 문자 제거 후 정수로 변환)
numeric_prices = [int(price.replace(',', '').replace('원', '')) for price in prices]

# 출력 상품명 및 가격
for name, price in zip(names, numeric_prices):
    print(f'name: {name}, price: {price}원')

# 가격 데이터를 기반으로 평균과 표준편차 계산
average_price = statistics.mean(numeric_prices)
std_deviation = statistics.stdev(numeric_prices)

print(f'평균 가격: {average_price:.2f}원')
print(f'표준편차: {std_deviation:.2f}')
print()

print('========== adidas 구하기 ==========')
url = "https://www.adidas.co.kr/men-trend_now"
adidas = req.urlopen(url)
print(adidas)


from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import numpy as np
from maze_animation import MazeAnimation

def softmax_convert_pi_from_theta(theta):
    beta = 1.0
    [m, n] = theta.shape
    pi = np.zeros([m, n])

    exp_theta = np.exp(beta * theta)

    for i in range(0, m):
        pi[i, :] = exp_theta[i, :] / np.nansum(exp_theta[i, :])

    return np.nan_to_num(pi)

def get_action_and_next_s(s, Q, epsilon, pi):
    direction = ['up', 'right', 'down', 'left']

    if np.random.rand() < epsilon:
        next_direction = np.random.choice(direction, p=pi[s, :])
    else:
        next_direction = direction[np.nanargmax(Q[s, :])]

    if next_direction == 'up':
       action = 0
       s_next = s - 3
    elif next_direction == 'right':
       action = 1
       s_next = s + 1
    elif next_direction == 'down':
       action = 2
       s_next = s + 3
    elif next_direction ==  'left':
       action = 3
       s_next = s - 1

    return [action, s_next]

def qlearning(s, a, r, s_next, Q, eta, gamma):
    if s_next == 8:
        Q[s, a] = Q[s, a] + eta * (r - Q[s, a])
    else:
        Q[s, a] = Q[s, a] + eta * (r + gamma * np.nanmax(Q[s_next, :]) - Q[s, a])

    return Q

def run_to_goal(Q, epsilon, eta, gamma, pi):
    s = 0
    s_history = [[0, np.nan]]

    while (1):
        [action, s_next] = get_action_and_next_s(s, Q, epsilon, pi)
        s_history[-1][1] = action

        s_history.append([s_next, np.nan])

        if s_next == 8:
            r = 1
            a_next = np.nan
        else:
            r = 0

        Q = qlearning(s, action, r, s_next, Q, eta, gamma)

        if s_next == 8:
            break
        else:
            s = s_next

    return [s_history, Q]

if __name__ == '__main__':
    np.set_printoptions(precision=3, suppress=True)

    theta0 = np.array([
       [np.nan, 1, 1, np.nan],
       [np.nan, 1, np.nan, 1],
       [np.nan, np.nan, 1, 1],
       [1, 1, 1, np.nan],
       [np.nan, np.nan, 1, 1],
       [1, np.nan, np.nan, np.nan],
       [1, np.nan, np.nan, np.nan],
       [1, 1, np.nan, np.nan],
    ])

    [a, b] = theta0.shape
    Q = np.random.rand(a, b) * theta0
    print('initial Q:')
    print(Q)

    pi0 = softmax_convert_pi_from_theta(theta0)
    print('initial π:')
    print(pi0)

    eta = 0.1
    gamma = 0.9
    epsilon = 0.5
    v = np.nanmax(Q, axis=1)
    is_continue = True
    episode = 1

    while is_continue:
        print("-----------------------------------------")
        print("episode: " + str(episode))
        epsilon = epsilon / 2
        [s_history, Q] = run_to_goal(Q, epsilon, eta, gamma, pi0)

        new_v = np.nanmax(Q, axis=1)
        # print(np.sum(np.abs(new_v - v)))
        print('\ncurrent Q:')
        print(Q)
        v = new_v

        print('\nsteps to goal: ' + str(len(s_history)))
        print('history([state, action]): {}'.format(s_history))

        episode = episode + 1
        if episode > 100:
            break

    # maze_animation = MazeAnimation()
    # ani = maze_animation.set_data(s_history)
    # maze_animation.show()


def printmatrix(matrix):
    '''
    循环打印矩阵，eg：
    1 2 3
    4 5 6
    7 8 9
    要求打印顺序为：1-2-3-6-9-8-7-4-5
    tip：
    跳出矩阵的限制，从宏观角度看这个问题，循环打印矩阵，先打印最外层一圈，按照此逻辑再打印内层的一圈
    需要处理的边界问题：
       1. 矩阵只有一行
       2. 矩阵只有一列
    '''
    if matrix is None:
        return
    l1 = 0    #矩阵左顶点坐标（l1,l2）
    l2 = 0
    r1 = len(matrix) - 1    #矩阵右顶点坐标（r1,r2）
    r2 = len(matrix[0]) - 1
    while l1 <= r1 and l2 <= r2:
        l1,l2,r1,r2 = printedge(matrix,l1,l2,r1,r2)
    return

def printedge(matrix,l1,l2,r1,r2):
    cur1 = l1
    cur2 = l2    #(cur1,cur2)为当前需要打印的坐标
    if l1 == r1:    #第一种边界问题
        for i in range(cur2,r2+1):
            print(matrix[cur1][i])
    elif l2 == r2:    #第二种边界问题
        for i in range(cur1,r1+1):
            print(matrix[i][cur2])
    else:
        while cur2 < r2:
            print(matrix[cur1][cur2])
            cur2 += 1
        while cur1 < r1:
            print(matrix[cur1][cur2])
            cur1 += 1
        while cur2 > l2:
            print(matrix[cur1][cur2])
            cur2 -= 1
        while cur1 > l1:
            print(matrix[cur1][cur2])
            cur1 -= 1
    l1 += 1
    l2 += 1
    r1 -= 1
    r2 -= 1
    return l1,l2,r1,r2
if __name__ == '__main__':
    matrix = [[1,2,3],[4,5,6],[7,8,9]]
    printmatrix(matrix)


numero = int(input('Que termo quer encontrar? '))
primeiro = 0
segundo = 1
fibo = 0
cont = 0
print(primeiro, end=' ')
for i in range(numero - 1):
    if i % 2 == 0:
        primeiro = fibo
    if i % 2 == 1:
        segundo = fibo
    fibo = primeiro + segundo
    cont += 1
    if cont < numero:
        print(fibo, end=' ')
    else:
        print(fibo)


from django.conf.urls.defaults import *
from django.conf import settings

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

from cloudwich.views import albums, album, photo, posts, post, index, about, clear, posts_tagged

urlpatterns = patterns('',
    # displays all our albums
    (r'^albums/$', albums),

    # single album with id passed in
    (r'^album/(\d+)/.*$', album),

    # single photo
    (r'^photo/(\d+)/(\d+)$', photo),

    # all posts with tag
    (r'^posts/tag/(\w+)$', posts_tagged),

    # all posts
    (r'^posts/$', posts),

    # single post
    (r'^post/(\d+)/.*$', post),

    # index page
    (r'^$', index),

    # about page
    (r'^about$', about),

    # clears our cache, displays index
    (r'^clear$', clear)
)


# -*- coding:utf8 -*-
from dojo.ast import *
from dojo.scanner import *
from functools import partial

SCANNER = Scanner('+', '-', '*', '/', '//', '**', '%', '(', ')', '[', ']', '{', '}',
                  '==', '!=', ',', '=', '@', ';', ':', '::', '..', '|>', '=>', '.',
                  '<', '<=', '>', '>=', '~', '<<', '>>', '&', '|', '^',
                  'return', 'in', 'not in', 'if', 'else', 'elif', 'and', 'or',
                  'not', 'import', 'def', 'yield',
                  INTEGER = r'[0-9]+',
                  FLOAT = r'[0-9]*\.[0-9]+',
                  IDENTIFIER = r'[_a-zA-Z][_a-zA-Z0-9]*',
                  STRING = '|'.join([r'("([^\\"]|\\.)*")',r"('([^\\']|\\.)*')"]),
                  EOF = r'$')

class Parser(TokenStream):
    def __init__(self, source):
        super(Parser, self).__init__(SCANNER, source)

    def program(self):
        ctx = LexicalContext()
        body = self.block(ctx, 'EOF')
        return Program(body.line, body, ctx.varnames('exported'), ctx.varnames('closure'))

    def block(self, ctx, until):
        exprs = []
        line = self.line
        while self.ignore(';') and not self.next_if(until):
            exprs.append(self.expr(ctx))
            self.expect_lf_or(';', until)
        return Block(line, exprs)

    def _binary(self, higher, clazz, *ops):
        e = higher()
        for op in iter(partial(self.next_if, *ops, stop_on_lf=True), None):
            e = clazz(op.line, op.name, e, higher())
        return e

    def _raw(self, higher, ops):
        e = higher()
        while self.maybe(*ops):
            e = ops[self.next(*ops).name](e.line, e, higher())
        return e

    def _unary(self, higher, *ops):
        op = self.next_if(*ops)
        if op:
            return UnaryOp(op.line, op.name, self._unary(higher, *ops))
        return higher()

    def _list_of(self, what, until = None, *rest):
        args = []
        if not self.maybe(until, *rest):
            args.append(what())
            while self.next_if(',') and not self.maybe(until, *rest):
                args.append(what())
        self.next_if(until)
        return args

    def expr(self, ctx):
        return self.if_expression(ctx)

    def if_expression(self, ctx):
        if self.next_if('if'):
            return self.if_test_and_bodies(ctx, If)
        return self.yield_expression(ctx)

    def if_test_and_bodies(self, ctx, node):
        test = self.expr(ctx)
        self.next(':')
        then_body = self.expr(ctx)

        if self.next_if('else') and self.next(':'):
            else_body = self.expr(ctx)
        elif self.next_if('elif'):
            else_body = self.if_test_and_bodies(ctx, If)
        else:
            else_body = Block(test.line)

        return node(test.line, test, then_body, else_body)

    def yield_expression(self, ctx):
        op = self.next_if('yield')
        if op:
            return Yield(op.line, self.expr(ctx))
        return self.return_expression(ctx)

    def return_expression(self, ctx):
        op = self.next_if('return')
        if op:
            return Return(op.line, self.expr(ctx))
        return self.import_expression(ctx)

    def import_expression_item(self, ctx):
        module = self.next('IDENTIFIER').image
        if self.next_if('(', stop_on_lf=True):
            names = self._list_of(lambda: self.next('IDENTIFIER').image, ')')
            return [module, names]
        return [module, None]

    def import_expression(self, ctx):
        op = self.next_if('import')
        if op:
            items = self._list_of(lambda: self.import_expression_item(ctx))
            return Import(op.line, items)

        return self.pipe_forward(ctx)

    def pipe_forward(self, ctx):
        return self._raw(partial(self.function, ctx), {'|>': PipeForward})

    def function(self, ctx):
        op = self.next_if('/')
        if op:
            args = self._list_of(lambda: self.next('IDENTIFIER').image, '=>')
            return self.function_body(op.line, ctx, None, args, self.function)

        op = self.next_if('def')
        if op:
            name = self.next('IDENTIFIER').image
            var = ctx.ensure(name, 'local')

            self.next('(')
            args = self._list_of(lambda: self.next('IDENTIFIER').image, ')')
            self.next(':')
            return SetVariable(op.line, var, self.function_body(op.line, ctx, name, args, self.expr))

        return self.assignment(ctx)

    def function_body(self, line, ctx, name, args, body_type):
        body_ctx = ctx.push(args)
        body = body_type(body_ctx)
        return Function(line, name, args, body,
                        body_ctx.varnames('exported'),
                        body_ctx.varnames('closure'))

    def assignment(self, ctx):
        to = self.operators(ctx)
        if hasattr(to, 'to_assignment') and self.next_if('='):
            value = self.expr(ctx)
            return to.to_assignment(value)
        return to

    OPS = [
        (_raw, {'::':Composition}),
        (_binary, BooleanOp, 'or'),
        (_binary, BooleanOp, 'and'),
        (_unary, 'not'),
        (_binary, CompareOp, 'in', 'not in'),
        (_binary, CompareOp, '==', '!=', '<', '>', '<=', '>='),
        (_binary, BinaryOp, '|'),
        (_binary, BinaryOp, '^'),
        (_binary, BinaryOp, '&'),
        (_binary, BinaryOp, '<<', '>>'),
        (_binary, BinaryOp, '+', '-'),
        (_binary, BinaryOp, '*', '/', '//', '%'),
        (_binary, BinaryOp, '**'),
        (_unary, '-', '+', '~'),
    ]

    def operators(self, ctx):
        current = partial(self.call, ctx)
        for op in reversed(Parser.OPS):
            current = partial(op[0], self, current, *op[1:])
        return current()

    def _named_args(self, ctx):
        self.next('@')
        name = self.next('IDENTIFIER').image
        self.next('=')
        expr = self.expr(ctx)
        return (name, expr)

    def _make_call(self, ctx, clazz, op, target, until):
        args = self._list_of(lambda: self.expr(ctx), until, '@')
        if self.maybe('@'):
            kwargs = self._list_of(lambda: self._named_args(ctx), until)
        else:
            kwargs = ()
        return clazz(op.line, target, args, kwargs)

    def call(self, ctx):
        e = self.get_attribute(ctx)
        for op in iter(partial(self.maybe, '(', '{', stop_on_lf=True), None):
            e = self.expect({
                    '(': lambda x: self._make_call(ctx, Call, op, e, ')'),
                    '{': lambda x: self._make_call(ctx, PartialCall, op, e, '}')})
        return e

    def get_attribute(self, ctx):
        e = self.get_subscript(ctx)
        for op in iter(partial(self.next_if, '.'), None):
            member = self.next('IDENTIFIER')
            e = GetAttribute(op.line, e, member.image)
        return e

    def get_subscript(self, ctx):
        e = self.primary(ctx)

        for op in iter(partial(self.next_if, '[', stop_on_lf=True), None):
            v1 = self.expr(ctx) if not self.maybe('..') else Literal(op.line, None)

            if self.next_if('..'):
                v2 = self.expr(ctx) if not self.maybe(']') else Literal(op.line, None)
                v1 = Slice(v1.line, v1, v2)

            self.next(']')
            e = GetSubscript(op.line, e, v1)

        return e

    def _key_value(self, ctx):
        key = self.expr(ctx)
        self.next(':')
        value = self.expr(ctx)
        return (key, value)

    def primary(self, ctx):
        return self.expect({
            'INTEGER': lambda x: Literal(x.line, int(x.image)),
            'FLOAT': lambda x: Literal(x.line, float(x.image)),
            'STRING': lambda x: Literal(x.line, x.image[1:-1].encode('utf-8').decode('unicode-escape')),
            'IDENTIFIER': lambda x: GetVariable(x.line, ctx.request(x.image)) if not self.next_if('=>') else self.function_body(x.line, ctx, None, [x.image], self.assignment),
            '(': lambda x: self.block(ctx, ')'),
            '[': lambda x: ListLiteral(x.line, self._list_of(lambda: self.expr(ctx), ']')),
            '{': lambda x: DictLiteral(x.line, self._list_of(lambda: self._key_value(ctx), '}')),
        })


import connector
import string
import sys
import wpp_auto.send_email as send_email
import datetime
import reportr_update as reportr
import string

def setBrand(connection, brand_id):
    brand_set_query = 'set @brand_id = %s\n;' % brand_id
    connector.update(connection, brand_set_query)
def setFiscalYear(connection, fy_custom=None):
    if fy_custom:
        fy_query = 'set @fiscal_year = %s;' % str(fy_custom)
    else:
        fy_query = 'set @fiscal_year = (select fiscal_year from fiscal_calendar where calendar_date = date(now()) - interval 1 day);'
    connector.update(connection, fy_query)
    get_fy_query = 'select @fiscal_year;'
    fy = connector.getRows(connection, get_fy_query, connection.cursor())
    return fy
def setCalendarYear(connection, custom_year=None):
    if custom_year:
        query = 'set @fiscal_year = %s;' % str(custom_year)
    else:
        query = 'set @fiscal_year = %s;' % str(calendar_year)
    connector.update(connection, query)
def getBrandInfo(connection):
    query = 'select m.att_id as brand_id,av.att_name,coalesce(m.merchant_id,0) as genuser_id from attribute_brand_values m inner join attributes_values av on m.att_id = av.att_id where av.att_status & 1 = 1;'
    brand_info = connector.getRows(connection, query, connection.cursor())
    return brand_info
def main():
    connection = connector.liveConnect()
    brand_info = getBrandInfo(connection)
    for i in range(0, len(brand_info)):
        # for i in range(0,1):
        setBrand(connection, brand_info[i][0])
        fy = 2020
        setCalendarYear(connection, fy)
        brand_name = brand_info[i][1]
        brand_name = brand_name.replace('.', '')
        brand_name = brand_name.replace(' ', '')
        brand_name = brand_name.replace('-', '')
        brand_name = brand_name.replace("'", '')
        brand_id = brand_info[i][0]
        print(fy)
        reportr.executeReportr(connection, 39, custom_file_name='%s_%s_%s' % (brand_name, brand_id, fy))
    if __name__ == '__main__':
        sys.exit(main())