import logging
from flask import Flask, request, jsonify, Response, stream_with_context
from flask_cors import CORS
from dotenv import load_dotenv
import os
import subprocess
import requests

app = Flask(__name__)
load_dotenv('/home/gds/printer/.env')  # .env 파일 경로
CORS(app, resources={r"/*": {"origins": "*"}})  # 모든 출처에 대해 CORS를 활성화합니다.

# 로그 디렉토리와 파일 설정
log_dir = '/home/gds/printer/log'
if not os.path.exists(log_dir):
    os.makedirs(log_dir)
log_file = os.path.join(log_dir, 'app.log')

# 로깅 설정
logging.basicConfig(filename=log_file, level=logging.DEBUG,
                    format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s')
app.logger.addHandler(logging.StreamHandler())
app.logger.setLevel(logging.DEBUG)

def slice_stl(file_path, output_dir, options=None):
    """
    STL 파일을 PrusaSlicer를 사용하여 슬라이스하는 함수
    """
    # PrusaSlicer 명령어 생성
    prusa_slicer_path = '/usr/bin/PrusaSlicer'

    command = [
        prusa_slicer_path, '-g',
        '--filament-type', options.get('filament_type'),
        '--idle-temperature', options.get('idle_temperature'),
        '--first-layer-temperature', options.get('first_layer_temperature'),
        '--temperature', options.get('temperature'),
        '--first-layer-bed-temperature', options.get('first_layer_bed_temperature'),
        '--bed-temperature', options.get('bed_temperature'),
        '--fill-density', options.get('fill_density')+"%",
        '--fill-pattern', options.get('fill_pattern'),
        '--infill-anchor', options.get('infill_anchor'),
        '--infill-anchor-max', options.get('infill_anchor_max'),
        '--top-fill-pattern', options.get('top_fill_pattern'),
        '--bottom-fill-pattern', options.get('bottom_fill_pattern'),
        '--skirts', options.get('skirts'),
        '--skirt-distance', options.get('skirt_distance'),
        '--skirt-height', options.get('skirt_height'),
        '--draft-shield', options.get('draft_shield'),
        '--min-skirt-length', options.get('min_skirt_length'),
        '--brim-type', options.get('brim_type'),
        '--brim-width', options.get('brim_width'),
        '--brim-separation', options.get('brim_separation'),
        '--support-material', options.get('support_material'),
        '--support-material-auto', options.get('support_material_auto'),
        '--support-material-threshold', options.get('support_material_threshold'),
        '--support-material-extrusion-width', options.get('support_material_extrusion_width'),
        '--raft-first-layer-density', options.get('raft_first_layer_density'),
        '--raft-first-layer-expansion', options.get('raft_first_layer_expansion'),
        '--perimeter-speed', options.get('perimeter_speed'),
        '--small-perimeter-speed', options.get('small_perimeter_speed'),
        '--external-perimeter-speed', options.get('external_perimeter_speed'),
        '--infill-speed', options.get('infill_speed'),
        '--solid-infill-speed', options.get('solid_infill_speed'),
        '--top-solid-infill-speed', options.get('top_solid_infill_speed'),
        '--support-material-speed', options.get('support_material_speed'),
        '--support-material-interface-speed', options.get('support_material_interface_speed'),
        '--bridge-speed', options.get('bridge_speed'),
        '--gap-fill-speed', options.get('gap_fill_speed'),
        '--travel-speed', options.get('travel_speed'),
        '--first-layer-speed', options.get('first_layer_speed'),
        '--first-layer-speed-over-raft', options.get('first_layer_speed_over_raft'),
        f'"{file_path}"'
    ]

    # 출력 디렉토리는 PrusaSlicer가 인식할 수 있는 형식으로 설정
    command.extend(['-o', output_dir])

    # skirts가 0일 때 관련 옵션 제거
    if options.get('skirts') == '0':
        command = [opt for idx, opt in enumerate(command) if 'skirt' not in opt and (idx == 0 or 'skirt' not in command[idx - 1])]
        command = [opt for idx, opt in enumerate(command) if 'skirts' not in opt and (idx == 0 or 'skirts' not in command[idx - 1])]

    # brim-type이 no_brim일 때 관련 옵션 제거
    if options.get('brim_type') == 'no_brim':
        command = [opt for idx, opt in enumerate(command) if 'brim' not in opt and (idx == 0 or 'brim' not in command[idx - 1])]
        command = [opt for idx, opt in enumerate(command) if 'draft' not in opt and (idx == 0 or 'draft' not in command[idx - 1])]
    

    # 이 코드 처리 중에 --support-material 이나 --support-material-auto가 true,false에 따라서 다르게 동작해야 되는데, 둘중 하나 통과하는 부분에서
    # command option이 사라진것 같아 확인 필요
    i = 0
    while i < len(command):
        if command[i] == '--support-material' and options.get('support_material') == 'true':
            command[i + 1] = ''  # Set the next index to empty string
            i += 1  # Move to the next index to skip removing '--support-material'
            continue

        if command[i] == '--support-material-auto' and options.get('support_material_auto') == 'true':
            command[i + 1] = ''  # Set the next index to empty string
            i += 1  # Move to the next index to skip removing '--support-material-auto'
            continue

        if command[i] == '--support-material' and options.get('support_material') == 'false':
            command.pop(i)  # Remove '--support-material'
            command.pop(i)  # Remove the value after '--support-material'
            continue

        if command[i] == '--support-material-auto' and options.get('support_material_auto') == 'false':
            command.pop(i)  # Remove '--support-material-auto'
            command.pop(i)  # Remove the value after '--support-material-auto'
            continue

        i += 1

    app.logger.debug("command : %s", command)       

    try:
        result = subprocess.run(' '.join(command), shell=True, capture_output=True, text=True, check=True)
        app.logger.debug("PrusaSlicer output: %s", result.stdout)
        return True
    except subprocess.CalledProcessError as e:
        app.logger.error("PrusaSlicer error: %s", e.stderr)
        return False

@app.route('/slice', methods=['POST'])
def slice_model():
    if 'file' not in request.files:
        app.logger.error('No file part')
        return jsonify({'error': 'No file part'})

    file = request.files['file']
    if file.filename == '':
        app.logger.error('No selected file')
        return jsonify({'error': 'No selected file'})

    # STL 파일을 저장할 경로 설정
    upload_dir = '/home/gds/printer/stl'
    if not os.path.exists(upload_dir):
        os.makedirs(upload_dir)

    file_path = os.path.join(upload_dir, file.filename)
    file.save(file_path)
    app.logger.debug('File saved to %s', file_path)

    options = {
        'filament_type': request.form.get('filament_type'),
        'idle_temperature': request.form.get('idle_temperature'),
        'first_layer_temperature': request.form.get('first_layer_temperature'),
        'temperature': request.form.get('temperature'),
        'first_layer_bed_temperature': request.form.get('first_layer_bed_temperature'),
        'bed_temperature': request.form.get('bed_temperature'),
        'fill_density': request.form.get('fill_density'),
        'fill_pattern': request.form.get('fill_pattern'),
        'infill_anchor': request.form.get('infill_anchor'),
        'infill_anchor_max': request.form.get('infill_anchor_max'),
        'top_fill_pattern': request.form.get('top_fill_pattern'),
        'bottom_fill_pattern': request.form.get('bottom_fill_pattern'),
        'skirts': request.form.get('skirts'),
        'skirt_distance': request.form.get('skirt_distance'),
        'skirt_height': request.form.get('skirt_height'),
        'draft_shield': request.form.get('draft_shield'),
        'min_skirt_length': request.form.get('min_skirt_length'),
        'brim_type': request.form.get('brim_type'),
        'brim_width': request.form.get('brim_width'),
        'brim_separation': request.form.get('brim_separation'),
        'support_material': request.form.get('support_material'),
        'support_material_auto': request.form.get('support_material_auto'),
        'support_material_threshold': request.form.get('support_material_threshold'),
        'support_material_extrusion_width': request.form.get('support_material_extrusion_width'),
        'raft_first_layer_density': request.form.get('raft_first_layer_density'),
        'raft_first_layer_expansion': request.form.get('raft_first_layer_expansion'),
        'perimeter_speed': request.form.get('perimeter_speed'),
        'small_perimeter_speed': request.form.get('small_perimeter_speed'),
        'external_perimeter_speed': request.form.get('external_perimeter_speed'),
        'infill_speed': request.form.get('infill_speed'),
        'solid_infill_speed': request.form.get('solid_infill_speed'),
        'top_solid_infill_speed': request.form.get('top_solid_infill_speed'),
        'support_material_speed': request.form.get('support_material_speed'),
        'support_material_interface_speed': request.form.get('support_material_interface_speed'),
        'bridge_speed': request.form.get('bridge_speed'),
        'gap_fill_speed': request.form.get('gap_fill_speed'),
        'travel_speed': request.form.get('travel_speed'),
        'first_layer_speed': request.form.get('first_layer_speed'),
        'first_layer_speed_over_raft': request.form.get('first_layer_speed_over_raft')
    }

    # PrusaSlicer를 사용하여 STL 파일 슬라이스
    output_dir = '/home/gds/printer/gcode/'
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    success = slice_stl(file_path, output_dir, options)

    if success:
        # G-code 파일 경로
        gcode_file_path = os.path.join(output_dir, os.path.splitext(file.filename)[0] + '.gcode')
        return jsonify({'gcode_file_path': gcode_file_path})
    else:
        return jsonify({'error': 'Failed to slice the model'})

@app.route('/getGCodeFile', methods=['GET'])
def get_gcode_file():
    gcode_file_path = request.args.get('gcodeFilePath')
    if not gcode_file_path:
        return jsonify({'error': 'Missing gcode_file_path parameter'})

    try:
        with open(gcode_file_path, 'r') as file:
            gcode_string = file.read()

        return gcode_string
    except FileNotFoundError:
        return jsonify({'error': 'File not found'})
    
@app.route('/getPrusaConfig', methods=['GET'])
def get_prusa_config():
    config = {
        'printer1': {
            'url': os.getenv('PRUSA_PRINTER1_API_URL'),
            'api_key': os.getenv('PRUSA_PRINTER1_API_KEY')
        },
        'printer2': {
            'url': os.getenv('PRUSA_PRINTER2_API_URL'),
            'api_key': os.getenv('PRUSA_PRINTER2_API_KEY')
        }
    }
    return jsonify(config)

@app.route('/proxy', methods=['GET', 'POST', 'PUT', 'DELETE'])
def proxy_prusa_link():
    try:
        # 요청 URL과 파라미터 설정
        url = request.args.get('url')
        if not url:
            return Response("URL is required", status=400)

        # PrusaLink에 보낼 요청 설정
        headers = {key: value for key, value in request.headers.items() if key != 'Host'}
        response = requests.request(
            method=request.method,
            url=url,
            headers=headers,
            data=request.get_data(),
            cookies=request.cookies,
            stream=True  # 스트리밍 응답 처리
        )

        # PrusaLink에서 받은 스트리밍 응답을 클라이언트에 전달
        def generate():
            for chunk in response.iter_content(chunk_size=8192):
                yield chunk

        proxy_response = Response(stream_with_context(generate()), status=response.status_code)
        for key, value in response.headers.items():
            if key.lower() != 'content-encoding' and key.lower() != 'transfer-encoding':
                proxy_response.headers[key] = value

        return proxy_response
    except Exception as e:
        return Response(f"An error occurred: {str(e)}", status=500)
    

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000, debug=True)